groupe-meditation/backend/app/main.py
Daniel Allaire 69309acd13 Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.

Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.

Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.

Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00

120 lines
5.9 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
@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"))
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"}