Files
openmind/backend/app/db/session.py
snoopy 0b2e5e326d OpenMind v1 : cartes mentales libres, local-first et dockerisees
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>
2026-09-08 22:19:18 +02:00

63 lines
2.0 KiB
Python

"""Creation du moteur SQLAlchemy et de la session de requete."""
from __future__ import annotations
import logging
from collections.abc import Generator
from pathlib import Path
from sqlalchemy import Engine, create_engine, event
from sqlalchemy.orm import Session, sessionmaker
from app.config import get_settings
logger = logging.getLogger(__name__)
_SQLITE_FILE_PREFIX = "sqlite:///"
def _ensure_sqlite_directory(database_url: str) -> None:
"""Cree le dossier parent du fichier SQLite s'il n'existe pas encore."""
if not database_url.startswith(_SQLITE_FILE_PREFIX):
return
raw_path = database_url[len(_SQLITE_FILE_PREFIX) :]
if not raw_path or raw_path == ":memory:":
return
path = Path(raw_path)
if path.parent and str(path.parent) not in ("", "."):
path.parent.mkdir(parents=True, exist_ok=True)
def create_db_engine(database_url: str | None = None) -> Engine:
settings = get_settings()
url = database_url or settings.database_url
_ensure_sqlite_directory(url)
connect_args = {"check_same_thread": False} if url.startswith("sqlite") else {}
engine = create_engine(url, connect_args=connect_args, future=True, echo=False)
if url.startswith("sqlite"):
@event.listens_for(engine, "connect")
def _set_sqlite_pragma(dbapi_connection, _connection_record): # type: ignore[no-untyped-def]
cursor = dbapi_connection.cursor()
# WAL : meilleure resistance aux arrets brutaux du conteneur.
cursor.execute("PRAGMA journal_mode=WAL")
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
return engine
engine: Engine = create_db_engine()
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
def get_db() -> Generator[Session, None, None]:
"""Dependance FastAPI : ouvre une session et la referme systematiquement."""
session = SessionLocal()
try:
yield session
finally:
session.close()