GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
89 lines
3.5 KiB
Python
89 lines
3.5 KiB
Python
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})
|