from __future__ import annotations from typing import Literal from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from sqlalchemy.orm import Session from app.db.session import get_db from app.schemas import Envelope from app.schemas.project import ProjectCreate, ProjectDeleteResult, ProjectList, ProjectRead, ProjectUpdate from app.services.project_service import ProjectService from app.utils.response import envelope router = APIRouter(prefix="/projects", tags=["projects"]) @router.get("", response_model=Envelope[ProjectList]) def list_projects( request: Request, limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), name: str | None = Query(default=None, min_length=1, max_length=255), project_status: Literal["active", "archived", "all"] = Query(default="active", alias="status"), db: Session = Depends(get_db), ): principal = getattr(request.state, "auth_principal", None) if principal is not None and principal.role == "guest": project = ProjectService.get_project(db, principal.project_id) status_matches = bool( project is not None and (project_status == "all" or project.status == project_status) ) name_matches = bool( project is not None and (name is None or name.casefold() in project.name.casefold()) ) matches = project is not None and status_matches and name_matches visible = [project] if matches and offset == 0 else [] return envelope( { "items": [ProjectRead.model_validate(item).model_dump() for item in visible[:limit]], "total": 1 if matches else 0, "limit": limit, "offset": offset, } ) projects, total = ProjectService.list_projects( db, limit=limit, offset=offset, name=name, project_status=project_status, ) return envelope({"items": [ProjectRead.model_validate(item).model_dump() for item in projects], "total": total, "limit": limit, "offset": offset}) @router.post("", status_code=status.HTTP_201_CREATED, response_model=Envelope[ProjectRead]) def create_project(payload: ProjectCreate, db: Session = Depends(get_db)): project = ProjectService.create_project(db, payload) return envelope(ProjectRead.model_validate(project).model_dump()) @router.get("/{project_id}", response_model=Envelope[ProjectRead]) def get_project(project_id: UUID, db: Session = Depends(get_db)): project = ProjectService.get_project(db, project_id) if not project: raise HTTPException(status_code=404, detail="Project not found") return envelope(ProjectRead.model_validate(project).model_dump()) @router.patch("/{project_id}", response_model=Envelope[ProjectRead]) def update_project(project_id: UUID, payload: ProjectUpdate, db: Session = Depends(get_db)): project = ProjectService.update_project(db, project_id, payload) if not project: raise HTTPException(status_code=404, detail="Project not found") return envelope(ProjectRead.model_validate(project).model_dump()) @router.delete( "/{project_id}", status_code=status.HTTP_200_OK, response_model=Envelope[ProjectDeleteResult], ) def delete_project(project_id: UUID, db: Session = Depends(get_db)): if not ProjectService.delete_project(db, project_id): raise HTTPException(status_code=404, detail="Project not found") return envelope({"deleted": True})