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}