Application de gestion du matériel pour un groupe scout SGDF. Inventaire, prêts, incidents, tableau de bord — interface entièrement en français.
Stack : Next.js 16 (App Router) · React 19 · TypeScript strict · Tailwind v4 · Prisma 7 · SQLite · better-auth
Licence : GNU AGPL v3 ou ultérieure — logiciel libre.
- Node.js 22+
- pnpm 11+ — si absent :
corepack enable && corepack prepare pnpm@latest --activate
git clone <url-du-repo>
cd piloti
pnpm installcp .env.example .envÉditer .env : les valeurs par défaut suffisent pour le développement local. Seule BETTER_AUTH_SECRET doit être changée (voir le fichier .env.example).
pnpm db:migrate # Crée dev.db et applique toutes les migrations
pnpm db:seed # Peuple avec des données de test (optionnel)pnpm dev # http://localhost:3000Premier lancement sans seed : l'application redirige automatiquement vers /setup pour créer le compte administrateur (comme n8n). Pas besoin de seed en production.
Avec seed — tous les comptes factices partagent le même mot de passe : celui de la variable d'environnement SEED_PASSWORD (sinon un mot de passe aléatoire, affiché en fin de pnpm db:seed). Comptes de test disponibles (liste non exhaustive — un chef par branche et douze familles supplémentaires sont aussi générés) :
| Rôle(s) | Statut | Connexion | |
|---|---|---|---|
admin@piloti.fr |
Admin | Actif | Oui |
thomas.martin@example.invalid |
Chef (Pionniers) | Actif | Oui |
julie.bernard@example.invalid |
Chef (Scouts) | Actif | Oui |
paul.durand@example.invalid |
Chef (Compagnons) | En attente | Non (compte non actif) |
chef.farfadets@piloti.fr |
Chef (Farfadets) + Trésorier | Actif | Oui |
rg@example.invalid |
Responsable de groupe | Actif | Oui |
materiel@example.invalid |
Responsable matériel | Actif | Oui |
secretaire@example.invalid |
Secrétaire | Actif | Oui |
membre.local@example.invalid |
Membre du local | Actif | Oui |
parent1@example.invalid, parent.test@example.invalid… |
Parent | Actif | Oui |
jeune.pionnier@example.invalid |
Jeune (Pionniers, 16 ans) | Actif | Oui |
jeune.compagnon@example.invalid |
Jeune (Compagnons, 19 ans) | Actif | Oui |
enfants @piloti.invalid (Farfadets/Louveteaux, < 15 ans) |
Jeune | Actif | Non (US-CM-01 : géré par un parent) |
pnpm dev # Serveur de développement (http://localhost:3000)
pnpm build # Build de production
pnpm start # Servir le build de production
pnpm lint # ESLint
pnpm typecheck # Vérification TypeScript (sans compilation)
pnpm db:migrate # Appliquer les migrations Prisma (crée dev.db si absent)
pnpm db:seed # Remplir la base avec des données de test réalistes
pnpm db:studio # Prisma Studio — interface graphique pour la base
pnpm db:reset # Réinitialiser la base et réappliquer toutes les migrations
pnpm db:generate # Régénérer le client Prisma (après modification du schéma)
pnpm icons:generate # Régénérer favicon.ico et les icônes PWA depuis les SVGpiloti/
├── prisma/
│ ├── schema.prisma # Schéma de base de données (SQLite)
│ ├── seed.ts # Données de test réalistes
│ └── migrations/ # Migrations versionnées
├── public/
│ ├── logo/ # SVG source du design system Piloti/SGDF
│ └── icons/ # PNG générés (PWA, apple-touch-icon)
├── scripts/
│ └── gen-icons.mjs # Génère favicon.ico + icônes PWA via sharp
├── src/
│ ├── app/
│ │ ├── (app)/ # Pages protégées (authentification requise)
│ │ │ ├── dashboard/ # Tableau de bord
│ │ │ ├── stock/ # Inventaire matériel
│ │ │ ├── prets/ # Prêts
│ │ │ ├── incidents/ # Signalements d'incidents
│ │ │ └── admin/ # Administration (inscriptions, utilisateurs, catégories, audit)
│ │ ├── (auth)/ # Pages publiques
│ │ │ ├── login/ # Connexion
│ │ │ ├── register/ # Demande d'accès (validation admin requise)
│ │ │ ├── setup/ # Premier lancement — création du compte admin
│ │ │ ├── forgot-password/
│ │ │ └── reset-password/
│ │ ├── api/
│ │ │ ├── auth/[...all]/ # Route better-auth (sessions, reset password…)
│ │ │ ├── upload/ # Upload de photos d'incidents
│ │ │ └── health/ # Health check Docker
│ │ ├── globals.css # Design tokens Tailwind v4 (@theme) + palette SGDF
│ │ ├── layout.tsx # Layout racine (polices, Toaster)
│ │ └── favicon.ico / icon.png / icon.svg # Favicon multi-format
│ ├── components/
│ │ ├── ui/ # Primitives shadcn/ui (Button, Input, Dialog…)
│ │ ├── layout/ # Sidebar, MobileHeader, UserMenu, nav-items
│ │ ├── dashboard/ # Widgets du tableau de bord
│ │ ├── equipment/ # CategoryChip, EquipmentForm
│ │ ├── loans/ # LoanCard
│ │ └── incidents/ # IncidentForm, IncidentTypeGrid
│ ├── lib/
│ │ ├── auth.ts # Configuration better-auth (emailAndPassword + Resend)
│ │ ├── auth-client.ts # Client better-auth (côté navigateur)
│ │ ├── db.ts # Client Prisma (singleton, adapter better-sqlite3)
│ │ ├── audit.ts # withAudit() — wrappeur transaction + AuditLog
│ │ ├── permissions.ts # can(user, "permission") — contrôle d'accès
│ │ ├── password-policy.ts # Schéma Zod + hint texte pour la politique de mdp
│ │ ├── enums.ts # Constantes TS (ROLES, UNITS, statuts…)
│ │ ├── get-current-user.ts
│ │ └── incident-categories.ts
│ ├── modules/
│ │ ├── admin/
│ │ │ ├── actions.ts # Server Actions admin (approuver, suspendre, supprimer…)
│ │ │ └── queries.ts # Requêtes admin (utilisateurs, audit log)
│ │ └── inventory/
│ │ ├── actions.ts # CRUD matériel
│ │ ├── loan-actions.ts
│ │ ├── incident-actions.ts
│ │ ├── category-actions.ts
│ │ ├── queries.ts
│ │ └── types.ts # Schémas Zod pour la validation
│ └── proxy.ts # Protection des routes (Next.js 16 = proxy.ts, pas middleware.ts)
└── traefik/
└── config/ # Configuration Traefik (security headers…)
⚠️ Next.js 16 a renommémiddleware.tsenproxy.ts. Tout exemple en ligne utilisantmiddleware.tsne fonctionnera pas.
Le proxy gère trois cas :
- Base vide (premier lancement) → redirige vers
/setup - Non authentifié → redirige vers
/login(sauf routes publiques) - Compte non ACTIVE → efface le cookie, redirige vers
/login
Toute modification de données passe par withAudit() qui execute la mutation ET crée une entrée AuditLog dans la même transaction Prisma. Jamais de mutation sans trace.
await withAudit(
(tx) => tx.equipment.update({ where: { id }, data }),
{ action: "EQUIPMENT_UPDATED", userId: user.id, equipmentId: id }
);import { can } from "@/lib/permissions";
if (!can(user, "admin.access")) return { error: "Accès refusé." };Pattern standard du projet :
// actions.ts
export async function monAction(
_prev: ActionResult,
formData: FormData,
): Promise<ActionResult> { ... }
// composant client
const [state, action, pending] = useActionState(monAction, { error: null });Définie dans src/lib/password-policy.ts : 12 caractères minimum, majuscule, minuscule, chiffre. Partagée entre l'inscription, le reset de mot de passe et le changement admin.
- Modifier
prisma/schema.prisma pnpm db:migrate(génère la migration + regénère le client)- Ajouter les requêtes dans
src/modules/<module>/queries.ts - Ajouter les Server Actions dans
src/modules/<module>/actions.ts(avecwithAudit) - Créer les composants dans
src/components/<module>/et les pages danssrc/app/(app)/
Créer src/app/(app)/ma-page/page.tsx. Le proxy protège automatiquement tout ce qui est sous (app)/.
Depuis l'interface admin → Catégories, ou directement dans Prisma Studio (pnpm db:studio).
- Docker Engine 29+ et Docker Compose v2
- Un tunnel Cloudflare Zero Trust configuré (zéro port exposé sur la machine)
- Un compte Resend avec domaine vérifié (pour les emails de reset de mot de passe)
Créer .env.production (ne jamais commiter ce fichier) :
DATABASE_URL="file:/data/piloti.db"
BETTER_AUTH_SECRET="<openssl rand -hex 32>"
BETTER_AUTH_URL="https://piloti.votre-domaine.fr"
TRAEFIK_DOMAIN="piloti.votre-domaine.fr"
CLOUDFLARE_TUNNEL_TOKEN="<token Zero Trust>"
RESEND_API_KEY="re_<votre-cle>"
RESEND_FROM_EMAIL="noreply@votre-domaine.fr"
# Optionnel — durée de conservation du journal d'audit, en années (vide = 10).
AUDIT_RETENTION_YEARS=""docker compose --env-file .env.production build
docker compose --env-file .env.production up -dLes migrations Prisma s'appliquent automatiquement au démarrage via le service migrate. L'application est accessible uniquement via le tunnel Cloudflare, sans aucun port exposé.
À la première ouverture dans le navigateur, l'application redirige vers /setup pour créer le compte administrateur. Aucun seed à lancer manuellement.
docker compose --env-file .env.production build app
docker compose --env-file .env.production up -d appdocker compose --env-file .env.production logs -f app # Logs en direct
docker compose --env-file .env.production down # Arrêter (données conservées)
docker compose --env-file .env.production down -v # Arrêter + effacer les donnéesInternet → Cloudflare CDN/WAF
→ cloudflared (tunnel sortant, pas de port entrant)
→ Traefik (reverse proxy interne, security headers)
→ Next.js (port 3000, réseau Docker isolé)
Réseaux Docker :
internal(internal: true) — app ↔ Traefik uniquement, pas d'accès Internettunnel— cloudflared ↔ Traefik
Volumes Docker :
piloti-data— base SQLite (/data/piloti.db)piloti-uploads— photos d'incidents (/app/public/uploads)
- Tailwind v4 : configuration CSS-first dans
src/app/globals.css(@theme inline), pas detailwind.config.*. Les tokensbg-forest,text-earth,bg-primary(shadcn) viennent tous du même bloc. - Prisma 7 : utilise l'adapter
better-sqlite3au lieu du moteur Rust natif — plus léger en Docker, pas de binaire plateforme-spécifique. - better-auth : gestion des sessions, hash scrypt des mots de passe, reset par email (Resend). Pas de plugin admin — les actions d'administration sont des Server Actions protégées par
can(). - Audit :
AuditLogest immuable et transactionnel. Chaque mutation de données laisse une trace avecuserId,action, et un champmetadataJSON libre.
Piloti est un logiciel libre distribué sous licence GNU AGPL v3 ou ultérieure (AGPL-3.0-or-later).
Copyright © 2026 Mathis Capart.
Vous êtes libre d'utiliser, déployer, étudier, modifier et redistribuer Piloti. En contrepartie, l'AGPL impose deux obligations :
- Toute redistribution — du code source comme d'une version modifiée — se fait sous cette même licence.
- Article 13, la clause réseau — si vous hébergez une version modifiée de Piloti et que des personnes s'en servent à distance, vous devez leur offrir l'accès au code source de cette version. C'est ce qui empêche qu'un fork amélioré de Piloti devienne un service fermé.
L'application expose déjà cet accès : footer de l'application (présent sur toute page authentifiée), footer des pages légales, et section dédiée des mentions légales.
Si vous modifiez Piloti, faites pointer
SOURCE_URLdanssrc/lib/legal/license.tsvers votre propre dépôt. C'est le code réellement exécuté qui doit être accessible, pas celui d'amont.
Le nom « Scouts et Guides de France », le sigle « SGDF » et les éléments visuels de l'association nationale sont des marques protégées. L'AGPL porte sur le droit d'auteur du code, jamais sur le droit des marques : un fork peut réutiliser ce code, il ne peut pas se présenter comme un outil officiel SGDF.
Les dépendances de production sont sous licences MIT, Apache-2.0, BSD-2-Clause, ISC et MPL-2.0 — toutes compatibles avec l'AGPL-3.0. Elles restent régies par leurs licences respectives.