SysOps: deploy-all — 2026-06-09 10:41 UTC
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
"""Projects + UI preferences API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.services import projects, ui_preferences, nas_folders
|
||||
|
||||
router = APIRouter(tags=["projects-api"])
|
||||
|
||||
|
||||
class ProjectCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
client_id: Optional[int] = None
|
||||
description: str = ""
|
||||
created_by: str = "ceo"
|
||||
project_type: str = "general"
|
||||
priority: str = "normal"
|
||||
|
||||
|
||||
class ProjectUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
client_id: Optional[int] = None
|
||||
project_type: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
|
||||
|
||||
class AssetCreate(BaseModel):
|
||||
asset_type: str
|
||||
title: str
|
||||
ref_id: Optional[str] = None
|
||||
file_path: Optional[str] = None
|
||||
payload: dict[str, Any] = Field(default_factory=dict)
|
||||
created_by: str = "ceo"
|
||||
source_agent: Optional[str] = None
|
||||
|
||||
|
||||
class LinkPhotoBody(BaseModel):
|
||||
project_id: int
|
||||
created_by: str = "ceo"
|
||||
|
||||
|
||||
class LinkNasFileBody(BaseModel):
|
||||
title: str
|
||||
file_path: str
|
||||
asset_type: str = "document"
|
||||
|
||||
|
||||
class PreferencesBody(BaseModel):
|
||||
dashboard_layout: Optional[list[str]] = None
|
||||
global_viz_mode: Optional[str] = None
|
||||
viz_modes: Optional[dict[str, str]] = None
|
||||
locale: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("/projects")
|
||||
async def projects_page(request: Request):
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from pathlib import Path
|
||||
templates = Jinja2Templates(directory=str(Path(__file__).resolve().parent.parent.parent / "templates"))
|
||||
return templates.TemplateResponse("projects.html", {"request": request, "page_title": "Projecten"})
|
||||
|
||||
|
||||
@router.get("/api/projects/types")
|
||||
def api_project_types() -> dict[str, Any]:
|
||||
return {"items": nas_folders.list_types()}
|
||||
|
||||
|
||||
@router.get("/api/projects/stats")
|
||||
def api_project_stats() -> dict[str, Any]:
|
||||
return {"ok": True, "stats": projects.project_stats()}
|
||||
|
||||
|
||||
@router.get("/api/projects")
|
||||
def api_list_projects(client_id: Optional[int] = None, limit: int = 50) -> dict[str, Any]:
|
||||
items = projects.list_projects(client_id=client_id, limit=limit)
|
||||
return {"items": items, "count": len(items)}
|
||||
|
||||
|
||||
@router.post("/api/projects")
|
||||
def api_create_project(body: ProjectCreate) -> dict[str, Any]:
|
||||
try:
|
||||
row = projects.create_project(
|
||||
body.name,
|
||||
client_id=body.client_id,
|
||||
description=body.description,
|
||||
created_by=body.created_by,
|
||||
project_type=body.project_type,
|
||||
priority=body.priority,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
return {"ok": True, "project": row}
|
||||
|
||||
|
||||
@router.get("/api/projects/{project_id}")
|
||||
def api_get_project(project_id: int) -> dict[str, Any]:
|
||||
row = projects.get_project(project_id)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
return {"project": row}
|
||||
|
||||
|
||||
@router.patch("/api/projects/{project_id}")
|
||||
def api_update_project(project_id: int, body: ProjectUpdate) -> dict[str, Any]:
|
||||
row = projects.update_project(project_id, **body.model_dump(exclude_none=True))
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
return {"ok": True, "project": row}
|
||||
|
||||
|
||||
@router.post("/api/projects/{project_id}/ensure-nas")
|
||||
def api_ensure_nas(project_id: int) -> dict[str, Any]:
|
||||
row = projects.get_project(project_id)
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
client_name = row.get("client_name")
|
||||
try:
|
||||
paths = nas_folders.ensure_project_folder(
|
||||
project_id,
|
||||
row["name"],
|
||||
row.get("client_id"),
|
||||
client_name,
|
||||
row.get("project_type") or "general",
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
return {"ok": True, **paths}
|
||||
|
||||
|
||||
@router.post("/api/projects/{project_id}/assets")
|
||||
def api_add_asset(project_id: int, body: AssetCreate) -> dict[str, Any]:
|
||||
if not projects.get_project(project_id):
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
try:
|
||||
asset = projects.add_asset(
|
||||
project_id,
|
||||
body.asset_type,
|
||||
body.title,
|
||||
ref_id=body.ref_id,
|
||||
file_path=body.file_path,
|
||||
payload=body.payload,
|
||||
created_by=body.created_by,
|
||||
source_agent=body.source_agent,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
return {"ok": True, "asset": asset}
|
||||
|
||||
|
||||
@router.post("/api/projects/{project_id}/link-nas-file")
|
||||
def api_link_nas_file(project_id: int, body: LinkNasFileBody) -> dict[str, Any]:
|
||||
if not projects.get_project(project_id):
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
asset = projects.add_asset(
|
||||
project_id,
|
||||
body.asset_type,
|
||||
body.title,
|
||||
file_path=body.file_path,
|
||||
created_by="ceo",
|
||||
)
|
||||
return {"ok": True, "asset": asset}
|
||||
|
||||
|
||||
@router.post("/api/projects/photos/{photo_id}/link")
|
||||
def api_link_photo(photo_id: int, body: LinkPhotoBody) -> dict[str, Any]:
|
||||
try:
|
||||
projects.link_photo_to_project(photo_id, body.project_id, body.created_by)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get("/api/preferences/ui")
|
||||
def api_get_ui_preferences() -> dict[str, Any]:
|
||||
return ui_preferences.get_preferences("ceo")
|
||||
|
||||
|
||||
@router.put("/api/preferences/ui")
|
||||
def api_save_ui_preferences(body: PreferencesBody) -> dict[str, Any]:
|
||||
allowed = {m["id"] for m in ui_preferences.VIZ_MODES}
|
||||
if body.global_viz_mode and body.global_viz_mode not in allowed:
|
||||
raise HTTPException(status_code=400, detail="Invalid viz mode")
|
||||
if body.locale and body.locale not in ("nl", "en"):
|
||||
raise HTTPException(status_code=400, detail="Invalid locale (nl or en)")
|
||||
prefs = ui_preferences.save_preferences(
|
||||
"ceo",
|
||||
dashboard_layout=body.dashboard_layout,
|
||||
global_viz_mode=body.global_viz_mode,
|
||||
viz_modes=body.viz_modes,
|
||||
locale=body.locale,
|
||||
)
|
||||
return {"ok": True, "preferences": prefs}
|
||||
Reference in New Issue
Block a user