Amana (أمانة) — « le dépôt confié ». Ce que l'on tient en garde pour autrui, et dont on doit pouvoir rendre compte.
Application locale complète pour la gestion administrative et financière d'une mosquée : membres, dons, cotisations, dépenses, ressources humaines, école coranique, bilans comptables, gestion des stocks, solidarité sociale et tableau de bord unifié.
Elle est construite autour de trois garanties, vérifiées par 935 tests automatisés :
- Intégrité financière — montants exacts en
NUMERIC, totaux calculés en SQL, écritures immuables corrigeables uniquement par contre-écriture motivée. - Traçabilité totale — chaque mutation écrit son audit dans la même transaction ; un audit qui échoue annule l'opération. Le journal est append-only, y compris en base.
- Refus par défaut — toute route exige une session ; la matrice
admin/tresorier/lecteurest appliquée côté serveur, à un seul endroit.
Le code est publié pour que d'autres mosquées et associations puissent s'en inspirer ou le réutiliser. Toutes les données présentes dans ce dépôt — seeds compris — sont fictives.
Session portée par un cookie HttpOnly, SameSite=Strict, valable huit heures.
Aucun jeton n'est accessible au code de la page : une injection de script ne
peut pas la dérober. L'algorithme de signature est épinglé (HS256) et un jeton
alg: none est rejeté.
Le compte est relu en base à chaque requête : rôle, statut et version d'authentification. Une désactivation, une suppression, une rétrogradation ou un changement de mot de passe prend effet dès la requête suivante, sans attendre l'expiration.
Refus par défaut sur toute l'API. La matrice est appliquée côté serveur, en
un seul point (backend/src/middleware/authorize.js) ; les gardes de
l'interface ne servent qu'à ne pas proposer une action vouée au refus.
| Capacité | admin |
tresorier |
lecteur |
|---|---|---|---|
| Lectures métier | oui | oui | oui |
| Écritures métier | oui | oui | non |
| Administration, utilisateurs, audit | oui | non | non |
401 signifie session absente ou invalide ; 403 signifie connecté sans
permission — et ne déconnecte jamais. Les routes publiques forment une liste
fermée de deux entrées : POST /api/auth/login et GET /api/health.
CRUD complet réservé aux administrateurs. Mots de passe hachés (bcrypt), politique appliquée côté serveur — au moins 12 caractères, minuscule, majuscule, chiffre et caractère spécial. L'inscription anonyme n'existe pas.
Le dernier administrateur actif ne peut être ni rétrogradé, ni désactivé, ni supprimé. Un compte porteur d'historique se désactive au lieu d'être effacé.
Chaque mutation écrit son entrée d'audit dans la même transaction que l'opération décrite : acteur, rôle, cible, valeurs avant et après, adresse IP et identifiant de corrélation. Un audit qui ne peut pas être écrit annule la mutation.
Le journal est append-only, garanti par des déclencheurs PostgreSQL : ni
UPDATE ni DELETE ne l'atteignent, quel que soit le chemin. Mots de passe,
hachages, jetons et cookies en sont exclus par redaction systématique.
Les montants circulent en chaînes EUR exactes ("125.00") et sont stockés
dans des domaines NUMERIC(12,2) bornés. Une précision différente est refusée,
jamais arrondie. Aucun total n'est calculé en JavaScript : toutes les sommes,
soustractions et comparaisons ont lieu en SQL.
Une opération comptabilisée est immuable. Sa correction passe par une
contre-écriture motivée, unique, de montant opposé — l'originale reste visible
et rapprochable. Les créations financières exigent un en-tête
Idempotency-Key : un double clic ou un rejeu réseau ne produit jamais deux
écritures.
Les dons et distributions d'aide sociale sont strictement séparés du solde général et n'entrent dans aucun total du tableau de bord ou du bilan annuel.
Toute mutation Social verrouille d'abord la caisse concernée, puis recalcule le disponible depuis le grand livre. Deux versements simultanés sont sérialisés : le disponible ne peut pas devenir négatif, y compris en concurrence.
Caisses, catégories de dépenses, classes et types de paiement s'administrent depuis l'interface. Chaque opération conserve le libellé au moment de sa saisie : renommer une référence ne réécrit jamais le passé. Une référence désactivée disparaît des nouvelles saisies tout en restant lisible dans l'historique ; une référence utilisée ne peut pas être supprimée.
Le document OpenAPI est dérivé du code et vérifié par les tests : format des
montants, en-têtes d'idempotence, codes d'erreur et routes réellement exposées.
/api-docs exige une session administrateur et reste désactivé en production
sauf activation explicite.
La racine / sert une page de présentation. L'application reste derrière la
session, aux mêmes adresses qu'auparavant (/dashboard, /membres, …) :
elle est montée sur une route de mise en page sans chemin, qui n'ajoute rien à
l'URL. Un visiteur non connecté n'atteint que deux écrans, cette page et
/login.
Elle est composée comme un registre — ce que le nom du logiciel raconte déjà.
Les intitulés de section reprennent les comptes du plan comptable associatif
(754 dons, 756 cotisations, 641 personnel, 12 résultat) : ce sont les
numéros qu'un trésorier lit toutes les semaines, pas une numérotation
décorative. L'élément d'ouverture est un journal de trésorerie qui se solde sur
un écart de rapprochement nul.
| Rôle | Choix |
|---|---|
| Titrage | Amiri — le caractère de la composition arabe, qui pose aussi أمانة |
| Texte courant | IBM Plex Sans |
| Chiffres et codes | IBM Plex Mono, chiffres tabulaires |
| Encre | #0E2119 — vert-noir des bandeaux |
| Papier | #E9EEE7 — vert-gris de registre |
| Filets | #A8321E carmin pour la colonne des sorties, #CFD9CB ailleurs |
Ses styles vivent dans src/pages/landing/landing.css, tous préfixés .lp
et écrits hors de Tailwind : l'application connectée possède déjà ses propres
.card, .btn-primary et .input-field dans src/index.css, et les deux
jeux de règles ne doivent pas se marcher dessus. La remise à zéro des marges y
passe par :where(), sans quoi .lp p { margin: 0 } l'emporterait sur chaque
règle de classe.
L'animation est réservée au journal d'ouverture et respecte
prefers-reduced-motion. Le formulaire d'inscription à la lettre confirme
localement : aucun service d'envoi n'est branché derrière.
| Couche | Technologie |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, Lucide React |
| Page publique | CSS préfixé .lp ; Amiri, IBM Plex Sans / Mono |
| Graphiques | Recharts |
| État global | Zustand (en mémoire, aucune persistance navigateur) |
| Client HTTP | Axios (cookie de session, withCredentials) |
| Backend | Node.js, Express |
| Base de données | PostgreSQL >= 14 (local, port 5432) |
| Authentification | Cookie de session HttpOnly signé (HS256), bcryptjs |
| Doc API | swagger-jsdoc + swagger-ui-express |
| Tests | Jest + Supertest (PostgreSQL réel), Vitest + Testing Library |
- Node.js >= 18
- PostgreSQL >= 14 installé et démarré sur le port 5432
- (macOS) Postgres.app ou Homebrew PostgreSQL
git clone https://github.com/sellihsen/amana.git
cd amananpm run install:allpsql -U postgres -c "CREATE DATABASE amana_db;"
# ou avec Postgres.app sur macOS :
psql -h localhost -U $(whoami) -c "CREATE DATABASE amana_db;"cp backend/.env.example backend/.envÉditer backend/.env. Toutes les variables marquées « REQUIS » dans
.env.example sont validées au démarrage : une valeur absente, vide ou
invalide interrompt le lancement de l'API plutôt que de recourir à une
valeur par défaut.
DB_HOST=localhost
DB_PORT=5432
DB_NAME=amana_db
DB_USER=votre_utilisateur_postgres
DB_PASSWORD=votre_mot_de_passe
FRONTEND_URL=http://localhost:5173
# Au moins 32 caractères. Générez-la, ne la réutilisez pas entre environnements :
# node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
JWT_SECRET=une_cle_aleatoire_longue
JWT_EXPIRES_IN=8hVariables optionnelles : PORT, NODE_ENV, SESSION_COOKIE_NAME,
REQUEST_BODY_LIMIT, API_DOCS_ENABLED, TEST_DB_NAME,
MAINTENANCE_DB_NAME, SEED_ADMIN_PASSWORD, SEED_ADMIN_EMAIL.
npm run migrateLe runner de migrations est verrouillé, transactionnel et suivi : il prend
un verrou advisory, refuse une migration dont le contenu a changé après
application (checksum), applique chaque fichier dans sa propre transaction et
enregistre le résultat dans schema_migrations. Une migration déjà appliquée
n'est jamais rejouée ; une base existante est adoptée sans être réécrite.
| Fichier | Contenu |
|---|---|
001_init.sql |
utilisateurs, membres, dons, cotisations, dépenses |
002_caisses.sql |
caisses dynamiques + liaison avec les dons |
003_rh.sql |
personnel et paiements de salaires |
004_madrasa.sql |
élèves et cotisations de l'école coranique |
005_config.sql |
catégories dépenses, classes madrasa, types paiement RH |
006_stock.sql |
produits et matériaux (gestion des stocks) |
007_projet.sql |
configuration du projet + affectation des caisses |
008_social.sql |
familles nécessiteuses, distributions, affectation Social |
009_logs.sql |
table logs_activite |
010_paiements_cree_par.sql |
colonne cree_par sur paiements_salaires |
011_add_numero_facture_to_depenses.sql |
numéro de facture sur les dépenses |
012_auth_audit_foundation.sql |
statut et version d'authentification des comptes, catalogue d'événements, journal d'audit append-only |
013_idempotency.sql |
déduplication (acteur, opération, clé) |
014_exact_eur.sql |
domaines monétaires exacts montant_eur_positif / montant_eur_non_negatif |
015_financial_ledger.sql |
grand livre append-only, reprise de l'historique, immutabilité des sources comptabilisées |
016_people_history_periods.sql |
RESTRICT à la place des cascades, période canonique Madrasa, unicités corrigées |
017_social_history.sql |
statut des familles et protections historiques des distributions |
018_stock_constraints.sql |
quantités entières non négatives |
019_config_references.sql |
référentiels configurables par clé étrangère + libellé snapshot |
020_performance_indexes.sql |
index des filtres d'audit, du grand livre et des périodes |
Une migration s'arrête avec un diagnostic si des données existantes ne peuvent pas être classées sans décision humaine : montant hors bornes, période ambiguë, doublon, stock négatif ou référence inconnue. Rien n'est corrigé silencieusement.
Le seed ne crée aucun identifiant par défaut : le mot de passe
administrateur doit être fourni explicitement, et le seed refuse de s'exécuter
avec NODE_ENV=production ou sur une base non migrée.
SEED_ADMIN_PASSWORD='UnMotDePasseFort!2026' npm run seed
# Jeu de démonstration complet (VIDE les données métier au préalable) :
SEED_ADMIN_PASSWORD='UnMotDePasseFort!2026' node backend/seeds/runSeed.jsLe seed crée un compte admin + 6 caisses (dont 1 Sociale) + 5 membres + 3 employés + 5 élèves + 5 produits stock + 2 familles nécessiteuses + 1 distribution sociale + historique financier + logs d'activité initiaux.
Ouvrir deux terminaux :
Terminal 1 - Backend :
npm run dev:backend
# API disponible sur http://localhost:3001Terminal 2 - Frontend :
npm run dev:frontend
# Interface sur http://localhost:5173
# / page publique de présentation
# /login connexion
# le reste exige une session| Champ | Valeur |
|---|---|
SEED_ADMIN_EMAIL (défaut admin@mosquee.local) |
|
| Mot de passe | SEED_ADMIN_PASSWORD — que vous fournissez |
| Rôle | admin |
Aucun mot de passe n'est écrit en dur dans le dépôt. La politique serveur exige au moins 12 caractères, avec minuscule, majuscule, chiffre et caractère spécial.
Ahmed Ben Ali, Youssef Mansouri, Karim Daoud, Nadia Chérif, Saïd El Amrani (inactif)
| Employé | Rôle | Salaire base |
|---|---|---|
| Hassan Omar | Imam | 2 200 EUR/mois |
| Mourad Bilal | Mouadhine | 1 300 EUR/mois |
| Fatou Aminata | Enseignante | 1 500 EUR/mois |
2 paiements seedés : 1 salaire Imam + 1 salaire Mouadhine
| Classe | Élèves |
|---|---|
| Éveil | Ibrahim Benali, Mehdi Saidani (inactif) |
| Débutants | Amina Chérif |
| Niveau 1 | Omar Mansouri |
| Niveau 2 | Sara Daoud |
10 cotisations seedées : mélange payé / en attente
Dons du Vendredi (Joumouah), Caisses fixes, Zakat al-Maal, Zakat al-Fitr, Caisse Orphelins, Fonds Solidarité Sociale
Chaque caisse porte une affectation déterminant le périmètre comptable :
| Affectation | Périmètre | Caisses concernées |
|---|---|---|
Chantier |
Projet de construction (mosquée) | Dons du Vendredi, Zakat al-Maal, Caisse Orphelins |
Fonctionnement |
Budget courant de la mosquée | Caisses fixes, Zakat al-Fitr |
Social |
Fonds solidarité & familles nécessiteuses | Fonds Solidarité Sociale |
Les dons sur les caisses Social sont exclus des agrégats du tableau de bord principal et isolés dans le module Solidarité & Social.
| Produit | Catégorie | Qté | Seuil | Statut |
|---|---|---|---|---|
| Sacs de Ciment | Construction | 5 | 10 | Stock critique |
| Briques | Construction | 200 | 100 | Stock OK |
| Peinture | Construction | 3 | 10 | Stock critique |
| Cahiers | Fournitures | 30 | 20 | Stock OK |
| Tableaux blancs | Fournitures | 2 | 1 | Stock OK |
- KPIs en temps réel : Solde, Total entrées, Total dépenses, Dons, Cotisations membres, Écolages Madrasa
- Solde = (Dons + Cotisations membres + Écolages Madrasa payés) - (Dépenses directes + Paiements salaires)
- Section RH : employés actifs, masse salariale, salaires du mois courant
- Section Madrasa : élèves actifs, écolages encaissés, paiements en attente
- Bandeau d'alerte stock : notification rouge tout en haut si des matériaux sont en stock critique
- Graphique dons mensuels (12 mois glissants, Recharts)
- Derniers dons, dernières sorties (dépenses + salaires), derniers paiements madrasa
- CRUD complet : nom, prénom, email, téléphone, adresse, date d'adhésion
- Statuts : actif / inactif / suspendu
- Recherche en temps réel
- Export Excel et PDF de la liste
- Enregistrement d'un don avec sélection de la caisse de destination (chargée dynamiquement depuis l'API)
- SearchableSelect : sélecteur de membre avec recherche textuelle
- Don anonyme ou lié à un membre
- Caisses 100% configurables depuis l'interface Admin
- Export Excel et PDF
- Suivi des cotisations annuelles ou mensuelles par membre
- Statuts : payée / en attente / annulée
- SearchableSelect pour la sélection du membre
- Export Excel et PDF
- Enregistrement par catégorie (configurable dynamiquement dans l'Admin)
- SearchableSelect pour le choix de la catégorie
- Colonne "Enregistré par" : nom de l'utilisateur ayant saisi la dépense
- Export Excel et PDF
- Fiches Personnel : nom, prénom, rôle/poste, salaire de base, statut actif/inactif
- Suppression bloquée si des paiements sont liés (désactivation recommandée)
- Historique des Paiements : tableau chronologique avec résumé par type
- Colonne "Saisi par" : nom de l'utilisateur ayant enregistré le paiement
- Types de paiement configurables dynamiquement dans l'Admin
- SearchableSelect pour le choix de l'employé et du type de paiement
- Export Excel et PDF des deux onglets
- Gestion des Élèves : inscription, fiche complète avec contact parent, classe, statut
- Classes configurables dynamiquement dans l'Admin
- Filtres par classe et par statut
- Suivi des Écolages : tableau des paiements mensuels par élève
- Contrainte d'unicité élève + mois (pas de doublon)
- Toggle statut payé <-> en attente en un clic
- Export Excel et PDF des deux onglets
- Registre des Familles nécessiteuses : CRUD complet avec fiche détaillée (ressources, composition, historique des aides)
- Distributions sociales : enregistrement des aides versées depuis les caisses Social
- Bilan Social : collecté / distribué / restant par caisse d'affectation Social
- Inventaire complet avec badge dynamique vert/rouge selon le seuil d'alerte
- Actions rapides : entrées / sorties en un clic
- Bandeau d'alerte global sur le tableau de bord
- Export Excel et PDF
- Caisses : CRUD avec toggle actif/inactif, affectation (
Chantier,Fonctionnement,Social) - Catégories de Dépenses, Classes Madrasa, Types de Paiement RH : configurables dynamiquement
- Budget du Projet : modification du budget prévisionnel et des capacités
- Gestion des Utilisateurs : CRUD complet avec mapping de rôle
- Historique des Actions : audit complet avec filtres (recherche, type, dates, pagination)
- ExportButtons composant réutilisable sur toutes les pages de liste
- Bibliothèques
xlsx,jspdfetjspdf-autotablecôté client
amana/
+-- package.json # Scripts racine (install:all, dev:*, migrate, seed)
+-- README.md
|
+-- backend/
| +-- src/
| | +-- index.js # Écoute réseau uniquement
| | +-- app.js # Construction de l'app Express (importable par les tests)
| | +-- config/
| | | +-- database.js # Pool PostgreSQL
| | | +-- env.js # Validation fail-fast de la configuration
| | | \-- swagger.js # Document OpenAPI partagé
| | +-- middleware/
| | | +-- auth.js # Session cookie, compte relu en base
| | | +-- authorize.js # Matrice des capacités (autorité unique)
| | | +-- errorHandler.js # Erreurs opaques + forme unique
| | | \-- requestId.js # Identifiant de corrélation
| | +-- utils/
| | | +-- transaction.js # BEGIN/COMMIT/ROLLBACK, connexion unique
| | | +-- audit.js # Catalogue d'événements, écriture transactionnelle
| | | +-- money.js # Validation et formatage EUR exact
| | | +-- idempotency.js # Clés et empreintes de requête
| | | +-- posting.js # Comptabilisation : source + grand livre + audit
| | | +-- references.js # Références actives et snapshots
| | | +-- password.js # Politique de mot de passe serveur
| | | \-- errors.js # Codes d'erreur du contrat
| | +-- queries/
| | | \-- finances.js # Agrégats SQL canoniques (source unique)
| | \-- routes/
| | +-- auth.js # login, logout, me
| | +-- membres.js # CRUD /api/membres
| | +-- dons.js # POST /api/dons (comptabilisé)
| | +-- cotisations.js # CRUD /api/cotisations
| | +-- depenses.js # POST /api/depenses (comptabilisé)
| | +-- ecrituresFinancieres.js # Grand livre + contre-écritures
| | +-- caisses.js # GET /api/caisses + CRUD /api/admin/caisses
| | +-- finances.js # GET /api/finances/resume
| | +-- dashboard.js # GET /api/dashboard
| | +-- personnel.js # CRUD personnel + paiements de salaires
| | +-- eleves.js # CRUD élèves + écolages
| | +-- bilans.js # GET /api/bilans/generate
| | +-- options.js # GET /api/options (références actives)
| | +-- stock.js # CRUD stock + /mouvements
| | +-- social.js # Familles, distributions, bilan Social
| | \-- admin/
| | +-- config.js # CRUD /api/admin/config/:type
| | +-- projet.js # GET/PUT /api/admin/projet
| | +-- logs.js # GET /api/admin/audit-events
| | \-- users.js # CRUD /api/admin/users
| +-- migrations/ # 20 fichiers SQL (001 -> 020), forward-only
| +-- seeds/ # run.js, runSeed.js, guard.js
| +-- tests/
| | +-- unit/ # Règles pures, sans base
| | +-- integration/ # API + PostgreSQL réel
| | \-- helpers/ # Base jetable, sessions, fixtures
| \-- .env
|
\-- frontend/
+-- index.html
+-- vite.config.js # Proxy /api -> localhost:3001
+-- tailwind.config.js
\-- src/
+-- main.jsx
+-- App.jsx # Routeur React Router v6 : / publique, le reste sous session
+-- components/
| +-- RoleGuard.jsx # Garde de présentation + hook useCapacite
| +-- BoutonContreEcriture.jsx# Contre-écriture motivée
| +-- BilanSocial.jsx # Collecté / distribué / disponible
| +-- JournalAudit.jsx # Journal d'audit, lecture seule
| +-- MouvementStock.jsx # Entrée / sortie de stock
| +-- BandeauAlertesStock.jsx # Alertes issues du tableau de bord
| +-- MessageHistorique.jsx # Erreurs HISTORY_EXISTS / INACTIVE_REFERENCE
| +-- ExportButtons.jsx
| +-- SearchableSelect.jsx
| \-- layout/
| +-- Layout.jsx
| +-- Sidebar.jsx # Navigation : TdB, Membres, Dons, Cotisations, Dépenses, RH, Stocks, Madrasa, Social, Bilans, Admin
| \-- Header.jsx
+-- pages/
| +-- landing/
| | +-- LandingPage.jsx # Page publique servie sur /
| | \-- landing.css # Styles préfixés .lp, hors Tailwind
| +-- LoginPage.jsx
| +-- DashboardPage.jsx
| +-- MembresPage.jsx
| +-- DonsPage.jsx
| +-- CotisationsPage.jsx
| +-- DepensesPage.jsx
| +-- RHPage.jsx
| +-- MadrasaPage.jsx
| +-- BilansPage.jsx
| +-- StockPage.jsx
| +-- SocialPage.jsx
| \-- AdminPage.jsx
+-- services/
| +-- api.js # Axios, cookie de session
| \-- operations.js # Envois financiers + clé d'idempotence
+-- utils/
| +-- money.js # Format EUR unique (formatage, pas calcul)
| +-- permissions.js # Capacités de présentation
| +-- options.js # Références actives + libellé historique
| +-- export.js # Projection XLSX / PDF partagée
| \-- periode.js # Périodes mensuelles canoniques
+-- store/
| \-- authStore.js # Session en mémoire, sans persistance
\-- test/ # Suites Vitest + setup jsdom
L'API est documentée avec Swagger UI (OpenAPI 3.0). Accès local :
http://localhost:3001/api-docs
/api-docs exige une session administrateur et reste désactivé en
production, sauf API_DOCS_ENABLED=true.
Pour tester les routes protégées :
- Connectez-vous à l'interface (
http://localhost:5173) avec un compteadmin - Ouvrez
/api-docsdans le même navigateur — le cookie de session suit automatiquement, il n'y a aucun jeton à copier
Les créations financières exigent en plus un en-tête Idempotency-Key :
saisissez-y une valeur unique par opération.
| Méthode | Endpoint | Description |
|---|---|---|
POST |
/api/auth/login |
Connexion, dépose le cookie de session |
POST |
/api/auth/logout |
Fermeture de session |
GET |
/api/auth/me |
Compte et rôle courants |
GET |
/api/dashboard |
Toutes les données agrégées du tableau de bord |
GET |
/api/finances/resume |
Résumé financier global |
GET/POST/PUT/DELETE |
/api/membres |
CRUD membres |
GET/POST/DELETE |
/api/dons |
CRUD dons |
GET/POST/PUT |
/api/cotisations |
CRUD cotisations membres |
GET/POST/DELETE |
/api/depenses |
CRUD dépenses |
GET |
/api/caisses |
Caisses actives (formulaires) |
GET/POST/PUT/DELETE |
/api/admin/caisses |
CRUD caisses (admin) |
GET/POST/PUT/DELETE |
/api/personnel |
CRUD fiches personnel |
GET/POST/DELETE |
/api/personnel/paiements |
Paiements de salaires |
GET/POST/PUT/DELETE |
/api/eleves |
CRUD élèves Madrasa |
GET/POST/PUT/DELETE |
/api/eleves/cotisations |
Cotisations Madrasa |
GET |
/api/bilans/generate?annee=XXXX |
Bilan comptable annuel |
GET |
/api/options |
Listes configurables |
GET/POST/PUT/DELETE |
/api/stock |
CRUD produits stock |
POST |
/api/stock/:id/mouvements |
Entrée ou sortie de stock |
GET/POST |
/api/ecritures-financieres |
Grand livre : recherche |
POST |
/api/ecritures-financieres/:id/contre-ecritures |
Contre-écriture motivée |
GET |
/api/admin/audit-events |
Journal d'audit (admin) |
GET |
/api/stock/alertes |
Produits en stock critique |
GET |
/api/social/bilan |
Bilan des caisses sociales |
GET/POST/PUT/DELETE |
/api/social/familles |
CRUD familles nécessiteuses |
GET/POST |
/api/social/distributions |
Enregistrement et liste des aides |
GET/PUT |
/api/admin/projet |
Budget et capacités du projet (admin) |
GET/POST/PUT/DELETE |
/api/admin/config/:type |
CRUD configurations (admin) |
GET |
/api/admin/logs |
Historique des actions avec filtres (admin) |
GET/POST/PUT/DELETE |
/api/admin/users |
Gestion des utilisateurs (admin) |
GET |
/api/health |
Health check |
TOTAL ENTRÉES = Dons (hors caisses Social)
+ Cotisations membres (statut = 'payee')
+ Cotisations Madrasa (statut_paiement = 'payé')
TOTAL DÉPENSES = Dépenses directes
+ Paiements de salaires (tous)
SOLDE = Total Entrées - Total Dépenses
Les dons sur les caisses d'affectation Social sont exclus des agrégats du tableau de bord et gérés dans le module Solidarité & Social.
Toutes les données financières sont recalculées en temps réel à chaque appel API. Pas de cache serveur.
# Serveur
PORT=3001
FRONTEND_URL=http://localhost:5173
# PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_NAME=amana_db
DB_USER=votre_utilisateur
DB_PASSWORD=votre_mot_de_passe
# JWT
JWT_SECRET=<au moins 32 caractères, généré aléatoirement>
JWT_EXPIRES_IN=8hImportant : Le fichier
.envest exclu du dépôt Git. Ne commitez jamais de secrets.
Les chemins « argent » et « accès » sont couverts par des tests automatisés
exécutés contre un vrai PostgreSQL (base jetable créée puis détruite à
chaque exécution ; son nom doit se terminer par _test).
# Suite complète : backend + frontend + build
npm run test:ci
# Backend seul (Jest + Supertest + PostgreSQL réel)
npm test --prefix backend
npm run test:unit --prefix backend # règles pures, sans base
npm run test:integration --prefix backend # API + base, séquentiel
# Frontend seul (Vitest + Testing Library)
npm run test:run --prefix frontendLa base de test est configurée par backend/.env.test (ou les variables
d'environnement en CI) :
TEST_DB_NAME=mosquee_test # DOIT se terminer par _test
MAINTENANCE_DB_NAME=postgresKEEP_TEST_DB=1 conserve la base après l'exécution pour inspection.
L'autorisation est décidée côté serveur, à un seul endroit
(backend/src/middleware/authorize.js). Les gardes de l'interface ne servent
qu'à ne pas proposer une action vouée à un refus.
| Capacité | admin |
tresorier |
lecteur |
|---|---|---|---|
| Lectures métier | oui | oui | oui |
| Écritures métier (dons, dépenses, membres, stock…) | oui | oui | non |
| Administration, utilisateurs, audit, référentiels | oui | non | non |
- La session est portée par un cookie HttpOnly, SameSite=Strict, valable huit heures. Aucun jeton n'est accessible au code de la page.
- Le rôle est relu en base à chaque requête : une désactivation, une suppression ou une rétrogradation prend effet immédiatement.
401= session absente ou invalide ;403= connecté mais sans permission. Un403ne déconnecte jamais.- L'inscription anonyme est fermée (
POST /api/auth/register→410). Les comptes sont créés par un administrateur. - Le dernier administrateur actif ne peut être ni rétrogradé, ni désactivé, ni supprimé.
Toute opération d'argent écrit, dans une seule transaction : la ligne métier, son écriture au grand livre, son entrée d'audit et sa clé d'idempotence. Si l'un échoue, rien n'est enregistré.
- Les montants circulent en chaînes EUR exactes (
"125.00"). Une précision différente est refusée, jamais arrondie. - Les créations financières exigent un en-tête
Idempotency-Key: un double clic ou un rejeu réseau ne produit jamais deux écritures. - Une opération comptabilisée est immuable : elle ne peut être ni modifiée ni supprimée.
Corriger une erreur se fait par contre-écriture motivée :
POST /api/ecritures-financieres/{id}/contre-ecritures
Idempotency-Key: <clé unique>
{ "motif": "Erreur de caisse lors de la saisie" }L'écriture d'origine est conservée ; une écriture inverse du même montant est ajoutée. Une écriture ne peut être contrepassée qu'une seule fois.
Le périmètre SOCIAL est strictement séparé du solde général : les dons et distributions d'aide sociale n'entrent jamais dans les totaux généraux, et le disponible d'une caisse Social ne peut pas devenir négatif, y compris lors de deux versements simultanés.
Chaque liste exporte exactement la collection filtrée affichée, aux formats XLSX et PDF, avec le même format de montant (EUR) et de date (français). Les accents et le symbole € sont préservés dans les deux formats. Les exports sont produits côté interface ; aucun point d'API supplémentaire n'est exposé.
Toute mutation laisse une trace : acteur, rôle, action, cible, valeurs avant et après, adresse IP et identifiant de corrélation. Le journal est append-only — aucune route, et aucune commande SQL, ne peut le modifier ou l'effacer.
Consultation : Administration → Journal d'audit, ou
GET /api/admin/audit-events (filtres : type d'événement, acteur, résultat,
entité, dates, recherche libre, pagination).
Les mots de passe, hashes, jetons et cookies ne sont jamais consignés.
Pour installer Amana sur un serveur — reverse proxy, TLS, service, pare-feu et sauvegardes — voir deploy/README.md.
./deploy/installer.sh amana.mondomaine.frLes modèles de deploy/ ne contiennent aucun secret ni valeur propre à une
installation : ils sont destinés à être repris tels quels.
Les données financières et personnelles n'existent qu'en un seul exemplaire : l'immutabilité du grand livre protège contre la falsification, pas contre la perte. Une panne de disque ou une suppression accidentelle emporte tout.
backend/scripts/sauvegarde.shFormat custom de pg_dump — compressé, restaurable table par table.
Chaque sauvegarde est vérifiée avant d'être conservée : archive lisible, inventaire non vide, et présence effective des neuf tables porteuses de données financières. Une archive qui échoue à ces contrôles fait échouer le script plutôt que d'être gardée : un fichier corrompu est pire que pas de sauvegarde, car il donne une fausse assurance.
Rétention : les 7 dernières quotidiennes, plus 4 hebdomadaires (le dimanche). Les anciennes ne sont supprimées qu'après validation de la nouvelle.
Planification — quotidienne à 3h30 :
30 3 * * * AMANA_BACKUP_DIR=/var/backups/amana /chemin/backend/scripts/sauvegarde.sh >> /var/log/amana-sauvegarde.log 2>&1# Test : restaure vers une base jetable, sans toucher à la production
backend/scripts/restaurer.sh
# Une archive précise
backend/scripts/restaurer.sh /var/backups/amana/quotidien/amana-20260805-123211.dump
# Restauration réelle — DESTRUCTIVE, demande confirmation explicite
backend/scripts/restaurer.sh <archive> --vers-productionLe mode par défaut est le test : il restaure dans amana_restauration_essai,
affiche les effectifs par table et recalcule le solde général. Comparer ce solde
à celui de la production prouve que les montants sont intacts — pas seulement
que les lignes sont présentes.
En mode --vers-production, le script sauvegarde d'abord l'état courant avant
de l'écraser, et exige que le nom de la base soit saisi pour confirmer.
Une sauvegarde jamais restaurée n'est pas une sauvegarde, c'est un fichier. Testez la restauration périodiquement — la commande sans argument ne présente aucun risque.
Vérifié : après restauration, les déclencheurs append-only du grand livre
et du journal d'audit sont toujours actifs — un UPDATE sur une écriture ou un
DELETE sur l'audit reste refusé.
Les sauvegardes résident sur la même machine que la base. Elles protègent
d'une erreur de manipulation ou d'une migration ratée, pas de la perte de la
VM. Pour une vraie continuité, copier /var/backups/amana vers un stockage
distant (Azure Blob, rsync vers une autre machine) reste à faire.