"""Endpoints des références croisées — Fusion ACC-1.3 (Flowdeck). Création par collage d'une URL profonde d'une autre application (ObsiGate, TikTak, Kanbante, Flowdeck) — zéro couplage serveur : l'URL est reconnue par les formes figées du contrat ``dracodev-commons/contrats/liens-profonds.md`` (voir ``app.services.external_refs``). La route ``GET /collections/{id}`` honore la forme canonique du même contrat en redirigeant vers la vue native de la base (``/db/{id}``). """ from __future__ import annotations import logging from fastapi import APIRouter, Body, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from app.auth.session import SessionManager from app.db import get_conn from app.services import external_refs as refs_svc from app.services.permission_manager import PermissionManager logger = logging.getLogger("flowdeck.external_refs") router = APIRouter(tags=["ExternalRefs"]) _FORMES_ACCEPTEES = ( "URL non reconnue — formes acceptées : TikTak ?task= ou ?list=, " "ObsiGate #file=, Flowdeck /pages/ ou /collections/, " "issue Gitea (carte Kanbante)" ) def _user(request: Request) -> dict: user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) if not user or not user.get("id"): raise HTTPException(401, "Session requise") return user def _check(owner_type: str, owner_id: int, user: dict, edit: bool) -> None: pm = PermissionManager(user["id"]) if owner_type == "flowdeck.page": ok = pm.can_edit_page(owner_id) if edit else pm.can_view_page(owner_id) elif owner_type == "flowdeck.project": ok = pm.can_edit_collection(owner_id) if edit else pm.can_view_collection(owner_id) else: raise HTTPException(400, "Type de propriétaire non géré ici") if not ok: raise HTTPException(403, "Accès refusé") def _list(owner_type: str, owner_id: int, request: Request): user = _user(request) _check(owner_type, owner_id, user, edit=False) return {"refs": refs_svc.list_refs(owner_type, owner_id)} def _add(owner_type: str, owner_id: int, request: Request, payload: dict): user = _user(request) _check(owner_type, owner_id, user, edit=True) parsed = refs_svc.parse_deep_link(payload.get("url") or "") if parsed is None: raise HTTPException(422, _FORMES_ACCEPTEES) target_app, target_type, target_id, default_label = parsed label = (payload.get("label") or "").strip() or default_label ref = refs_svc.add_ref( user["id"], owner_type, owner_id, target_app, target_type, target_id, (payload.get("url") or "").strip(), label, ) return JSONResponse(ref, status_code=201) @router.get("/api/pages/{page_id}/refs") def list_page_refs(page_id: int, request: Request): return _list("flowdeck.page", page_id, request) @router.post("/api/pages/{page_id}/refs") def add_page_ref(page_id: int, request: Request, payload: dict = Body(...)): return _add("flowdeck.page", page_id, request, payload) @router.get("/api/collections/{collection_id}/refs") def list_collection_refs(collection_id: int, request: Request): return _list("flowdeck.project", collection_id, request) @router.post("/api/collections/{collection_id}/refs") def add_collection_ref(collection_id: int, request: Request, payload: dict = Body(...)): return _add("flowdeck.project", collection_id, request, payload) @router.delete("/api/refs/{ref_id}") def delete_ref(ref_id: int, request: Request): user = _user(request) ref = refs_svc.get_ref(ref_id) if ref is None: raise HTTPException(404, "Lien introuvable") _check(ref["owner_type"], int(ref["owner_id"]), user, edit=True) refs_svc.delete_ref(ref_id) return {"deleted": ref_id} @router.get("/collections/{collection_id}") def view_collection_deep_link(collection_id: int, request: Request): """Forme canonique Fusion (ACC-0.1) : /collections/ → /db/.""" _user(request) with get_conn() as conn: row = conn.execute( "SELECT id FROM collections WHERE id = ?", (collection_id,) ).fetchone() if not row: # Réponse directe, comme view_page : le gestionnaire global de # l'app convertirait une HTTPException 404 en redirection 302. return HTMLResponse("

Collection introuvable

", status_code=404) return RedirectResponse(f"/db/{collection_id}", status_code=303)