"""Endpoints des références croisées des notes — Fusion #215 (ACC-1.3). Création par collage d'une URL profonde d'une autre application (Flowdeck, TikTak, Kanbante, ObsiGate) — zéro couplage serveur : l'URL est reconnue par les formes figées du contrat ``dracodev-commons/contrats/liens-profonds.md`` (voir :mod:`backend.external_refs`), la référence est stockée chez la note propriétaire et affichée dans sa section « Liens ». """ import logging from fastapi import APIRouter, Depends, HTTPException, Query from backend.auth.middleware import check_vault_access, require_auth from backend.external_refs import add_ref, delete_ref, get_ref, list_refs, parse_deep_link from backend.schemas import ExternalRef, ExternalRefCreateRequest, ExternalRefsResponse logger = logging.getLogger("obsigate") 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)" ) @router.get("/api/refs", response_model=ExternalRefsResponse) async def api_list_refs( vault: str = Query(...), path: str = Query(...), current_user=Depends(require_auth), ): """Références croisées posées sur une note.""" if not check_vault_access(vault, current_user): raise HTTPException(status_code=403, detail="Accès au vault refusé") username = current_user.get("username") or "" return {"refs": list_refs(username, vault, path)} @router.post("/api/refs", response_model=ExternalRef, status_code=201) async def api_add_ref( payload: ExternalRefCreateRequest, current_user=Depends(require_auth), ): """Crée une référence sur une note à partir d'une URL profonde collée.""" if not check_vault_access(payload.vault, current_user): raise HTTPException(status_code=403, detail="Accès au vault refusé") parsed = parse_deep_link(payload.url) if parsed is None: raise HTTPException(status_code=422, detail=_FORMES_ACCEPTEES) target_app, target_type, target_id, default_label = parsed label = (payload.label or "").strip() or default_label username = current_user.get("username") or "" return add_ref( username, payload.vault, payload.path, target_app, target_type, target_id, payload.url.strip(), label, ) @router.delete("/api/refs/{ref_id}", response_model=ExternalRef) async def api_delete_ref( ref_id: str, current_user=Depends(require_auth), ): """Retire une référence d'une note.""" username = current_user.get("username") or "" ref = get_ref(username, ref_id) if ref is None: raise HTTPException(status_code=404, detail="Lien introuvable") if not check_vault_access(ref["owner_vault"], current_user): raise HTTPException(status_code=403, detail="Accès au vault refusé") removed = delete_ref(username, ref_id) if removed is None: # course improbable : disparue entre get et delete raise HTTPException(status_code=404, detail="Lien introuvable") return removed