Rend une instance exposee sur Internet exploitable sans la transformer en infrastructure : le produit reste local-first, sans compte et sans telemetrie. Protection du service : limitation de debit en memoire par client et par famille d'endpoint (health et ready jamais limites), interrupteur SHARING_ENABLED, capacite maximale de partages, taille propre aux partages. X-Forwarded-For n'est cru que si TRUST_PROXY_HEADERS l'autorise, et on lit alors la derniere valeur, celle ajoutee par le proxy. Expiration facultative des partages (24 h a 90 jours, ou jamais) : un lien expire repond 410 et la page publique le dit, sans autre detail. Le proprietaire peut prolonger ; aucune purge automatique. Protection des liens non repertories : robots.txt, X-Robots-Tag et meta robots sur /s/ et /api/, Referrer-Policy no-referrer sur tout le site, redaction des jetons dans les journaux d'acces, share_token porte a 128 bits (les anciens restent valides). Schema gere par Alembic, applique au demarrage. Une base creee avant Alembic est estampillee puis migree : verifie sur la base reelle de developpement, 4 partages inchanges et expires_at NULL. Administration par CLI locale au conteneur (stats, liste, suppression, purge, sauvegarde a chaud via l'API SQLite, restauration prudente) : aucune interface /admin exposee. Le jeton de gestion peut enfin etre exporte dans un fichier .openmind-share puis reimporte depuis un autre navigateur, apres verification aupres du serveur. Il n'est toujours jamais ajoute a un export .mmap. Ajoute une page /privacy dont le texte est centralise, et corrige un bug trouve en observant les logs : Alembic, lance dans le processus applicatif, reappliquait la configuration de logs d'alembic.ini et desactivait les loggers existants, ce qui faisait disparaitre les journaux d'acces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
"""Endpoints de sante et de configuration publique.
|
|
|
|
Aucune limitation de debit n'est posee ici : le healthcheck du conteneur doit
|
|
rester fiable meme quand une instance subit un afflux de requetes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import JSONResponse
|
|
from sqlalchemy import text
|
|
|
|
from app.api.deps import AppSettings, DbSession
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(tags=["system"])
|
|
|
|
# Durees proposees dans l'interface. `None` = permanent.
|
|
SHARE_EXPIRY_OPTIONS: list[int | None] = [1, 7, 30, 90, None]
|
|
|
|
|
|
@router.get("/health", summary="Etat du service")
|
|
def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|
|
|
|
|
|
@router.get("/ready", summary="Le service peut-il repondre : base de donnees joignable")
|
|
def ready(session: DbSession): # type: ignore[no-untyped-def]
|
|
"""Complete /health : verifie que SQLite repond reellement.
|
|
|
|
Utile derriere un orchestrateur qui doit distinguer "processus vivant" de
|
|
"capable de servir", par exemple quand le volume de donnees est absent ou
|
|
en lecture seule.
|
|
"""
|
|
try:
|
|
session.execute(text("SELECT 1"))
|
|
except Exception:
|
|
logger.exception("Base de donnees injoignable")
|
|
return JSONResponse(status_code=503, content={"status": "unavailable"})
|
|
return {"status": "ready"}
|
|
|
|
|
|
@router.get("/config", summary="Configuration publique exposee au frontend")
|
|
def public_config(settings: AppSettings) -> dict[str, object]:
|
|
"""Aucun secret ici : uniquement ce dont l'interface a besoin."""
|
|
return {
|
|
"appName": settings.app_name,
|
|
"maxNodes": settings.max_nodes,
|
|
"maxMapSizeMb": settings.max_map_size_mb,
|
|
"maxShareSizeMb": settings.max_share_size_mb_effective,
|
|
"sharingEnabled": settings.sharing_enabled,
|
|
"allowPermanentShares": settings.allow_permanent_shares,
|
|
"defaultShareExpiryDays": settings.default_share_expiry_days,
|
|
"shareExpiryOptions": SHARE_EXPIRY_OPTIONS,
|
|
}
|