urmi-ai/backend/app/routes_urmiport.py
2026-10-02 22:39:04 +05:30

180 lines
5.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from .auth import current_user
from .database import get_db
from .models import UrmiportReport, UrmiportReportGroup, UserAccount
from .urmiport_service import (
CreateUrmiportGroupPayload,
UpdateUrmiportGroupPayload,
build_urmiport_api_docs,
get_group_by_slug,
get_report,
get_user_group,
hash_password,
normalize_group_slug,
serialize_group,
serialize_report,
verify_password,
)
router = APIRouter(prefix="/api/v1/urmiport", tags=["urmiport"])
class UnlockReportRequest(BaseModel):
password: str = Field(min_length=1, max_length=120)
@router.get("/docs")
def urmiport_docs() -> dict:
return build_urmiport_api_docs()
@router.get("/groups")
def list_report_groups(
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> list[dict]:
groups = db.scalars(
select(UrmiportReportGroup)
.where(UrmiportReportGroup.user_id == user.id)
.order_by(UrmiportReportGroup.created_at.desc())
).all()
counts = dict(
db.execute(
select(UrmiportReport.group_id, func.count(UrmiportReport.id))
.where(UrmiportReport.user_id == user.id)
.group_by(UrmiportReport.group_id)
).all()
)
return [serialize_group(group, counts.get(group.id, 0)) for group in groups]
@router.post("/groups")
def create_report_group(
payload: CreateUrmiportGroupPayload,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
try:
slug = normalize_group_slug(payload.report_group)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
existing = db.scalar(
select(UrmiportReportGroup).where(
UrmiportReportGroup.user_id == user.id,
UrmiportReportGroup.slug == slug,
)
)
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Report group already exists")
if payload.default_access == "password" and not payload.password:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="password is required")
group = UrmiportReportGroup(
user_id=user.id,
slug=slug,
display_name=payload.display_name or slug,
default_access=payload.default_access,
password_hash=hash_password(payload.password) if payload.default_access == "password" else None,
id_mode=payload.id_mode,
)
db.add(group)
db.commit()
db.refresh(group)
return serialize_group(group, 0)
@router.patch("/groups/{slug}")
def update_report_group(
slug: str,
payload: UpdateUrmiportGroupPayload,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
group = get_user_group(db, user, slug)
if payload.display_name is not None:
group.display_name = payload.display_name.strip() or group.slug
if payload.default_access is not None:
group.default_access = payload.default_access
if payload.default_access == "password":
if payload.password:
group.password_hash = hash_password(payload.password)
elif not group.password_hash:
raise HTTPException(status_code=422, detail="password is required")
else:
group.password_hash = None
elif payload.password:
group.password_hash = hash_password(payload.password)
if payload.id_mode is not None:
group.id_mode = payload.id_mode
db.commit()
db.refresh(group)
count = db.scalar(
select(func.count(UrmiportReport.id)).where(UrmiportReport.group_id == group.id)
) or 0
return serialize_group(group, count)
@router.delete("/groups/{slug}")
def delete_report_group(
slug: str,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> dict:
group = get_user_group(db, user, slug)
db.delete(group)
db.commit()
return {"success": True}
@router.get("/groups/{slug}/reports")
def list_group_reports(
slug: str,
db: Session = Depends(get_db),
user: UserAccount = Depends(current_user),
) -> list[dict]:
group = get_user_group(db, user, slug)
reports = db.scalars(
select(UrmiportReport)
.where(UrmiportReport.group_id == group.id)
.order_by(UrmiportReport.updated_at.desc())
).all()
return [serialize_report(report, group) for report in reports]
@router.get("/view/{group_slug}/{report_id}")
def view_report_metadata(
group_slug: str,
report_id: str,
db: Session = Depends(get_db),
) -> dict:
group = get_group_by_slug(db, group_slug)
report = get_report(db, group, report_id)
return {
"report_group": group.slug,
"report_id": report.report_id,
"access": report.access_mode,
"requires_password": report.access_mode == "password",
"html": report.html_body if report.access_mode == "public" else None,
}
@router.post("/view/{group_slug}/{report_id}/unlock")
def unlock_report(
group_slug: str,
report_id: str,
payload: UnlockReportRequest,
db: Session = Depends(get_db),
) -> dict:
group = get_group_by_slug(db, group_slug)
report = get_report(db, group, report_id)
if report.access_mode != "password":
return {"html": report.html_body}
if not verify_password(payload.password, report.password_hash):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid password")
return {"html": report.html_body}