Ajoute modifie_le aux objets sensibles et fait transmettre la version affichée par le frontend pour refuser les écritures périmées avec un message HTTP 409. Ajoute une table historique_modifications et un service d'historique métier afin de journaliser automatiquement les raisons des changements importants sans intervention de l'utilisateur. Expose l'historique métier dans le module Journal, distinct du journal technique, et documente les processus utilisateurs ainsi qu'une présentation sobre aux membres.
136 lines
6.4 KiB
Python
136 lines
6.4 KiB
Python
"""
|
|
Groupe Méditation — API Backend
|
|
District 87-16, Région 87
|
|
100% logiciel libre · Hébergé au Québec
|
|
"""
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from sqlalchemy import text
|
|
|
|
from app.core.config import settings
|
|
from app.core.audit import AuditMiddleware
|
|
from app.core.database import engine, Base
|
|
from app.routers import admin, auth, membres, postes, accueil, invitations, collectes, reunions, depenses, calendrier, rapports, rotations, parametres, presences, pv, rapports_rsg, jetons, anniversaires, commandes_jetons, litterature, ventes_litterature, contributions, config_repartition, propositions, operations_bancaires, evenements, notifications, journal
|
|
|
|
# Import models pour que create_all crée toutes les tables
|
|
from app.models.sync_log import SyncLog # noqa: F401
|
|
from app.models.proposition import Proposition # noqa: F401
|
|
from app.models.operation_bancaire import OperationBancaire # noqa: F401
|
|
from app.models.evenement import Evenement # noqa: F401
|
|
from app.models.candidature import Candidature # noqa: F401
|
|
from app.models.poste_module import PosteModule # noqa: F401
|
|
from app.models.reserve import Reserve # noqa: F401
|
|
from app.models.transaction_comptable import TransactionComptable # noqa: F401
|
|
from app.models.mouvement_reserve import MouvementReserve # noqa: F401
|
|
from app.models.journal_action import JournalAction # noqa: F401
|
|
from app.models.push_subscription import PushSubscription # noqa: F401
|
|
from app.models.destinataire_contribution import DestinataireContribution # noqa: F401
|
|
from app.models.rapport_adopte import RapportAdopte # noqa: F401
|
|
from app.models.vente_jeton import VenteJeton # noqa: F401
|
|
from app.models.historique_modification import HistoriqueModification # noqa: F401
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
await conn.execute(text(
|
|
"ALTER TABLE groupes "
|
|
"ADD COLUMN IF NOT EXISTS assemblee_affaires_ordre VARCHAR(20) DEFAULT 'premiere'"
|
|
))
|
|
await conn.execute(text("ALTER TABLE propositions ADD COLUMN IF NOT EXISTS date_adoption DATE"))
|
|
await conn.execute(text("ALTER TABLE propositions ADD COLUMN IF NOT EXISTS etant_donne_que TEXT"))
|
|
await conn.execute(text("ALTER TABLE propositions ADD COLUMN IF NOT EXISTS groupe_a_decide_de TEXT"))
|
|
await conn.execute(text("ALTER TABLE propositions DROP COLUMN IF EXISTS type_proposition"))
|
|
await conn.execute(text("ALTER TABLE membres ADD COLUMN IF NOT EXISTS notification_preferences JSON"))
|
|
await conn.execute(text("ALTER TABLE jetons ADD COLUMN IF NOT EXISTS reunion_id UUID REFERENCES reunions(id)"))
|
|
await conn.execute(text("ALTER TABLE ventes_litterature ADD COLUMN IF NOT EXISTS reunion_id UUID REFERENCES reunions(id)"))
|
|
await conn.execute(text("ALTER TABLE invitations ADD COLUMN IF NOT EXISTS cible_type VARCHAR(40)"))
|
|
await conn.execute(text("ALTER TABLE invitations ADD COLUMN IF NOT EXISTS cible_id UUID"))
|
|
for table in (
|
|
"propositions",
|
|
"depenses",
|
|
"collectes",
|
|
"reunions",
|
|
"pv_reunions",
|
|
"reserves",
|
|
"litterature",
|
|
"destinataires_contributions",
|
|
"envois_contributions",
|
|
):
|
|
await conn.execute(text(
|
|
f"ALTER TABLE {table} "
|
|
"ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMPTZ DEFAULT now() NOT NULL"
|
|
))
|
|
await conn.execute(text(
|
|
"ALTER TABLE envois_contributions "
|
|
"ALTER COLUMN destination TYPE VARCHAR(120) USING destination::text"
|
|
))
|
|
await conn.execute(text("DROP TABLE IF EXISTS votes"))
|
|
await conn.execute(text("DROP TABLE IF EXISTS options_sondage"))
|
|
await conn.execute(text("DROP TABLE IF EXISTS sondages"))
|
|
await conn.execute(text("DROP TABLE IF EXISTS decisions"))
|
|
await conn.execute(text(
|
|
"INSERT INTO destinataires_contributions (id, groupe_id, nom, protege, actif, cree_le) "
|
|
"SELECT CAST(md5(random()::text || clock_timestamp()::text) AS uuid), g.id, 'District 87-16', true, true, now() "
|
|
"FROM groupes g "
|
|
"WHERE NOT EXISTS ("
|
|
" SELECT 1 FROM destinataires_contributions d "
|
|
" WHERE d.groupe_id = g.id AND d.nom = 'District 87-16'"
|
|
")"
|
|
))
|
|
yield
|
|
await engine.dispose()
|
|
|
|
|
|
app = FastAPI(
|
|
title="Groupe Méditation API",
|
|
description="API du Groupe Méditation — District 87-16, Région 87",
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
app.add_middleware(AuditMiddleware)
|
|
|
|
# Montage des routes
|
|
app.include_router(auth.router, prefix="/api")
|
|
app.include_router(admin.router, prefix="/api")
|
|
app.include_router(membres.router, prefix="/api")
|
|
app.include_router(postes.router, prefix="/api")
|
|
app.include_router(accueil.router, prefix="/api")
|
|
app.include_router(invitations.router, prefix="/api")
|
|
app.include_router(collectes.router, prefix="/api")
|
|
app.include_router(reunions.router, prefix="/api")
|
|
app.include_router(depenses.router, prefix="/api")
|
|
app.include_router(calendrier.router, prefix="/api")
|
|
app.include_router(rapports.router, prefix="/api")
|
|
app.include_router(rotations.router, prefix="/api")
|
|
app.include_router(parametres.router, prefix="/api")
|
|
app.include_router(presences.router, prefix="/api")
|
|
app.include_router(pv.router, prefix="/api")
|
|
app.include_router(rapports_rsg.router, prefix="/api")
|
|
app.include_router(jetons.router, prefix="/api")
|
|
app.include_router(anniversaires.router, prefix="/api")
|
|
app.include_router(commandes_jetons.router, prefix="/api")
|
|
app.include_router(litterature.router, prefix="/api")
|
|
app.include_router(ventes_litterature.router, prefix="/api")
|
|
app.include_router(contributions.router, prefix="/api")
|
|
app.include_router(config_repartition.router, prefix="/api")
|
|
app.include_router(propositions.router, prefix="/api")
|
|
app.include_router(operations_bancaires.router, prefix="/api")
|
|
app.include_router(evenements.router, prefix="/api")
|
|
app.include_router(notifications.router, prefix="/api")
|
|
app.include_router(journal.router, prefix="/api")
|
|
|
|
|
|
@app.get("/api/health")
|
|
async def root_health():
|
|
return {"status": "ok", "app": "Groupe Méditation", "version": "0.1.0"}
|