119 lines
4.4 KiB
Python
119 lines
4.4 KiB
Python
"""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/<id> → /db/<id>."""
|
|
_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("<h2>Collection introuvable</h2>", status_code=404)
|
|
return RedirectResponse(f"/db/{collection_id}", status_code=303)
|