Premiere version fonctionnelle, demarrable avec `docker compose up -d --build` sur http://localhost:8080. Architecture - Frontend React 18 / TypeScript strict / Vite / Tailwind, canvas @xyflow/react, etat Zustand, persistance IndexedDB via Dexie, auto-layout dagre. - Backend FastAPI / SQLAlchemy 2 / Pydantic v2 / SQLite, qui sert aussi les fichiers statiques du frontend compile. - Image Docker multi-stage, un seul service, bind mount ./data pour SQLite. Local-first Les cartes restent dans le navigateur. Le serveur ne recoit une carte que sur demande explicite de partage. Partage Deux secrets distincts : share_token public en lecture seule dans l'URL, et management_token conserve uniquement cote client, transmis en en-tete pour les modifications et stocke hashe en base. Fonctionnalites Tableau de bord, editeur complet (undo/redo, copier/coller, raccourcis, branches repliables, recherche), 9 themes, 4 dispositions automatiques, panneau de proprietes, import/export .mmap, export PNG/SVG/PDF, impression, page publique en lecture seule, PWA. Qualite 25 tests pytest et 43 tests vitest au vert, ruff et ESLint sans erreur, TypeScript strict sans erreur. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
99 lines
2.7 KiB
Python
99 lines
2.7 KiB
Python
"""API REST de partage de cartes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Response, status
|
|
|
|
from app.api.deps import DbSession, ManagementToken
|
|
from app.schemas.share import (
|
|
ShareCreatedOut,
|
|
ShareCreateIn,
|
|
SharePublicOut,
|
|
ShareUpdatedOut,
|
|
)
|
|
from app.services import shares as share_service
|
|
|
|
router = APIRouter(prefix="/shares", tags=["shares"])
|
|
|
|
|
|
def _share_path(share_token: str) -> str:
|
|
return f"/s/{share_token}"
|
|
|
|
|
|
@router.post(
|
|
"",
|
|
response_model=ShareCreatedOut,
|
|
status_code=status.HTTP_201_CREATED,
|
|
summary="Publier un snapshot de carte et obtenir un lien non repertorie",
|
|
)
|
|
def create_share(payload: ShareCreateIn, session: DbSession) -> ShareCreatedOut:
|
|
share, management_token = share_service.create_share(
|
|
session, title=payload.title, document=payload.map
|
|
)
|
|
return ShareCreatedOut(
|
|
share_token=share.share_token,
|
|
management_token=management_token,
|
|
share_path=_share_path(share.share_token),
|
|
title=share.title,
|
|
created_at=share.created_at,
|
|
updated_at=share.updated_at,
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/{share_token}",
|
|
response_model=SharePublicOut,
|
|
summary="Lire une carte partagee (public, lecture seule)",
|
|
)
|
|
def read_share(share_token: str, session: DbSession) -> SharePublicOut:
|
|
share = share_service.get_share(session, share_token)
|
|
return SharePublicOut(
|
|
share_token=share.share_token,
|
|
title=share.title,
|
|
map_version=share.map_version,
|
|
map=share_service.load_map_data(share),
|
|
created_at=share.created_at,
|
|
updated_at=share.updated_at,
|
|
)
|
|
|
|
|
|
@router.put(
|
|
"/{share_token}",
|
|
response_model=ShareUpdatedOut,
|
|
summary="Mettre a jour le snapshot partage (necessite le jeton de gestion)",
|
|
)
|
|
def update_share(
|
|
share_token: str,
|
|
payload: ShareCreateIn,
|
|
session: DbSession,
|
|
management_token: ManagementToken,
|
|
) -> ShareUpdatedOut:
|
|
share = share_service.update_share(
|
|
session,
|
|
share_token,
|
|
management_token=management_token,
|
|
title=payload.title,
|
|
document=payload.map,
|
|
)
|
|
return ShareUpdatedOut(
|
|
share_token=share.share_token,
|
|
share_path=_share_path(share.share_token),
|
|
title=share.title,
|
|
updated_at=share.updated_at,
|
|
)
|
|
|
|
|
|
@router.delete(
|
|
"/{share_token}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
response_class=Response,
|
|
summary="Supprimer le partage (necessite le jeton de gestion)",
|
|
)
|
|
def delete_share(
|
|
share_token: str,
|
|
session: DbSession,
|
|
management_token: ManagementToken,
|
|
) -> Response:
|
|
share_service.delete_share(session, share_token, management_token=management_token)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|