Vérification de l'accès…
1. Présentation
Application web du calendrier des courses de char à voile (FFCV). Elle permet :
- Consultation publique des courses à venir (vues liste et calendrier, filtres, export iCalendar).
- Proposition de course par tout club ou personne, soumise à validation.
- Validation des propositions par des administrateurs avant publication.
- Gestion complète des courses, des paramètres (statuts, types d'événement et couleurs) et des administrateurs, directement dans l'application.
Le front est 100 % statique (HTML/CSS/JS, sans build obligatoire) ; les données sont stockées dans Supabase (PostgreSQL + authentification + sécurité RLS). Les emails (mot de passe oublié, notification de proposition) sont envoyés via des webhooks n8n.
2. Architecture & pile technique
| Couche | Technologie |
|---|---|
| Front | HTML / CSS / JavaScript « vanilla » (aucune dépendance de build) |
| Client base de données | @supabase/supabase-js (chargé via CDN) |
| Base de données & Auth | Supabase (PostgreSQL, Auth email/mot de passe, RLS) |
| Emails / automatisations | n8n (webhooks) |
| Polices / logo | Chivo + Roboto (Google Fonts), logo FFCV |
| Hébergement conseillé | Netlify (ou Vercel, Cloudflare Pages, GitHub Pages) |
service_role n'existe que côté n8n.3. Structure des fichiers
Calendrier FFCV/
├── index.html Page publique (calendrier + proposition)
├── admin.html Espace administrateur
├── reset.html Réinitialisation du mot de passe
├── documentation.html Cette documentation
├── css/styles.css Styles (responsive, thème clair/sombre, charte FFCV)
├── js/
│ ├── config.js Identifiants Supabase + URLs webhooks (généré ou édité)
│ ├── common.js Utilitaires, paramètres, version applicative
│ ├── app.js Logique publique (liste, calendrier, iCal, proposition)
│ ├── admin.js Logique administrateur
│ └── reset.js Logique de réinitialisation du mot de passe
├── img/logo-ffcv.png Logo
├── supabase/
│ ├── schema.sql Schéma + sécurité (RLS)
│ ├── seed.sql Données d'amorçage (courses importées de l'Excel)
│ ├── migration_*.sql Migrations (voir §4)
│ └── gen_seed.py Générateur de seed.sql depuis l'Excel
├── n8n/workflow-mot-de-passe-oublie.json Workflow n8n (email de reset)
├── build-config.js Génère js/config.js depuis les variables d'environnement
├── netlify.toml Déploiement Netlify
├── serve.bat Lancement local (http://localhost:8080)
└── .env.example Modèle de variables d'environnement
4. Installation Supabase
- Créer un projet sur supabase.com.
- Dans SQL Editor, exécuter dans l'ordre :
| # | Fichier | Rôle |
|---|---|---|
| 1 | schema.sql | Tables courses, admins, fonction is_admin(), RLS |
| 2 | seed.sql | Import des courses initiales (facultatif) |
| 3 | migration_club.sql | Colonne club |
| 4 | migration_parametres.sql | Tables types_evenement et statuts (paramétrables) + visibilité publique par statut |
| 5 | migration_ligue_directeurs.sql | Colonne ligue, renommage arbitre → directeur_course, ajout directeur_jury |
| 6 | migration_admins.sql | Fonctions de gestion des administrateurs (add_admin_by_email, remove_admin, list_admins) |
| 7 | migration_proposition_publique.sql | Autorise la soumission publique (policy INSERT + grants) |
| 8 | migration_statut_libre.sql | Retire la contrainte CHECK figée sur statut (statuts personnalisés) |
Créer le premier administrateur
- Supabase → Authentication → Users → Add user (cocher « Auto Confirm User »).
- Copier l'User UID.
- SQL Editor :
insert into public.admins (user_id, email)
values ('COLLER-ICI-LE-USER-UID', 'admin@exemple.fr');
Les administrateurs suivants se créent ensuite dans l'application (voir §9).
5. Configuration
Les paramètres se trouvent dans js/config.js :
window.APP_CONFIG = {
SUPABASE_URL: "https://xxxx.supabase.co",
SUPABASE_ANON_KEY: "sb_publishable_…", // clé PUBLIQUE (RLS)
N8N_RESET_WEBHOOK: "https://…/webhook/…", // email « mot de passe oublié »
N8N_PROPOSITION_WEBHOOK: "https://…/webhook/…" // notification de proposition
};
| Clé | Description |
|---|---|
SUPABASE_URL | URL du projet Supabase (Project Settings → API) |
SUPABASE_ANON_KEY | Clé anon / publishable — publique, protégée par la RLS |
N8N_RESET_WEBHOOK | Webhook n8n appelé pour l'email de réinitialisation |
N8N_PROPOSITION_WEBHOOK | Webhook n8n notifié à chaque nouvelle proposition (facultatif) |
Deux méthodes :
- Édition directe de
js/config.js. - Via
.env+ build : copier.env.exampleen.env, puisnode build-config.jsrégénèrejs/config.js(pratique pour Netlify).
service_role dans config.js : elle donnerait un accès total. Elle reste uniquement dans n8n.6. Lancer en local
L'application doit être servie en HTTP (l'ouverture directe en file:// empêche le chargement des scripts).
- Windows : double-cliquer sur
serve.bat→http://localhost:8080. - Ligne de commande :
python -m http.server 8080dans le dossier du projet.
| Page | URL locale |
|---|---|
| Public | http://localhost:8080/index.html |
| Administration | http://localhost:8080/admin.html |
| Documentation | http://localhost:8080/documentation.html |
7. Déploiement (Netlify)
- Glisser-déposer : sur app.netlify.com → Add new site → Deploy manually → déposer le dossier (avec
js/config.jsrenseigné). - Depuis Git : importer le dépôt ; Netlify lit
netlify.toml(node build-config.js). DéfinirSUPABASE_URL,SUPABASE_ANON_KEY,N8N_RESET_WEBHOOK,N8N_PROPOSITION_WEBHOOKdans Site settings → Environment variables.
reset.html aux Redirect URLs de Supabase (Authentication → URL Configuration).8. Espace public (index.html)
- Affiche les courses dont le statut est marqué « public » (par défaut : À venir, En cours, À confirmer, Passée, Annulée). Les propositions en attente ne sont jamais visibles.
- Filtres : recherche, saison, classe, ligue, type d'événement, période (à venir / toutes / passées).
- Deux vues : Liste (groupée par mois) et Calendrier (grille mensuelle, navigation, clic sur un événement → détail).
- Couleur des tuiles selon le type d'événement (paramétrable).
- Export iCalendar :
- Global : bouton « 📅 Exporter (.ics) » → toutes les courses affichées (filtres appliqués).
- Par course : bouton « 📅 Ajouter à mon agenda » sur chaque fiche → un seul événement.
- Proposer une course : formulaire (nom, lieu, type, dates, ligue, classes, lien d'inscription, coordonnées du proposeur). Enregistrée au statut Proposée, puis notifiée au webhook (§12).
9. Espace administrateur (admin.html)
Connexion par email + mot de passe (comptes Supabase). Trois onglets :
Propositions
Liste des demandes en attente ; boutons Valider (publie, statut « À venir ») ou Refuser.
Toutes les courses
Tableau filtrable (recherche, saison, statut) avec colonnes Dates, Événement, Club, Ligue, Lieu, Type, Classes, Directeur de course, Président de jury, Statut. Actions Modifier / Supprimer, et + Nouvelle course. L'éditeur couvre tous les champs (avec autocomplétion des clubs et ligues déjà saisis).
⚙️ Paramètres
- Types d'événement : ajout / renommage / suppression, et couleur de la tuile.
- Statuts : libellé, couleur, ordre, case « Public » (visibilité), création de statuts personnalisés. Les statuts « système » ne sont pas supprimables.
- Administrateurs : ajouter (email + mot de passe initial — le compte est créé puis promu ; mot de passe vide = promotion d'un compte existant) et retirer (sauf soi-même).
9 bis. Espace Arbitre (arbitre.html)
Profil dédié à la saisie des affectations Directeur de course et Président de jury. Accessible aux comptes « Arbitre » (et aux administrateurs).
- Tableau de saisie : sélection d'une saison, puis saisie directe des deux affectations par course ; bouton « 💾 Enregistrer les modifications » (seules les lignes modifiées sont envoyées).
- Export CSV : « ⬇ Exporter la saison (CSV) » télécharge les courses de la saison (colonnes
id, saison, date_debut, date_fin, nom, lieu, type_evenement, directeur_course, directeur_jury, séparateur;, compatible Excel). - Import CSV : « ⬆ Importer un CSV » applique les affectations ; la correspondance se fait par la colonne
id(seulesdirecteur_courseetdirecteur_jurysont mises à jour).
affecter_directeurs() (SECURITY DEFINER, réservée aux arbitres et admins). Il ne peut ni supprimer, ni modifier les autres champs d'une course.Gestion des comptes arbitres : depuis l'espace administrateur → Paramètres → bloc « Arbitres » (ajouter par email + mot de passe initial, ou promouvoir un compte existant ; retirer le profil). Nécessite la migration migration_arbitres.sql.
10. Statuts & logique temporelle
Les statuts sont définis dans la table statuts (paramétrables). Un statut marqué soumis_dates (ex. « À venir ») voit son libellé calculé automatiquement selon les dates, à chaque affichage :
| Situation (vs aujourd'hui) | Libellé affiché |
|---|---|
| Date de début future | À venir |
| Aujourd'hui entre début et fin | En cours |
| Date de fin dépassée | Passée |
Les statuts « métier » (Proposée, Annulée, Non retenue, Refusée) ne changent jamais avec les dates. La colonne visible_public détermine quels statuts apparaissent dans le calendrier public (utilisée par la RLS).
11. Mot de passe oublié
Aucun secret n'est exposé dans le navigateur : c'est n8n qui génère le lien de récupération via l'API Admin Supabase (clé service_role gardée dans n8n).
- Écran de connexion → « Mot de passe oublié ? » → l'app POST au webhook
N8N_RESET_WEBHOOK(form-urlencoded) :email,redirect_to(URL dereset.html),app. - n8n : Webhook (POST) → HTTP Request
POST {SUPABASE_URL}/auth/v1/admin/generate_link(en-têtesapikey+Authorization: Bearer= service_role, corps{ "type":"recovery", "email":"{{ $json.body.email }}", "redirect_to":"{{ $json.body.redirect_to }}" }) → renvoiehashed_token. - Send Email → lien
{{ $node["Webhook"].json.body.redirect_to }}?token_hash={{ $json.hashed_token }}&type=recovery. reset.html:verifyOtp({ type:'recovery', token_hash })puisupdateUser({ password })— avec la seule clé publique.
Workflow prêt à importer : n8n/workflow-mot-de-passe-oublie.json.
reset.html aux Redirect URLs Supabase, sinon generate_link refuse le redirect_to.12. Webhook de proposition
À chaque proposition enregistrée avec succès, l'app envoie (fire-and-forget) les informations saisies au webhook N8N_PROPOSITION_WEBHOOK (form-urlencoded, champs sous $json.body) :
nom, lieu, ligue, type_evenement, date_debut, date_fin, saison, classes, inscriptions_url, proposeur_nom, proposeur_email, proposeur_message, submitted_at.
N8N_PROPOSITION_WEBHOOK est vide, aucun envoi n'est fait (la proposition est quand même enregistrée). Le webhook n8n doit être en POST.13. Base de données
Table courses
| Colonne | Type | Rôle |
|---|---|---|
| id | uuid | Clé primaire |
| saison | text | Ex. « 2026/2027 » |
| date_debut / date_fin | date | Dates de la course |
| statut | text | Code de statut (table statuts) |
| nom | text | Nom de l'événement |
| club | text | Club organisateur |
| ligue | text | Ligue de rattachement |
| lieu | text | Lieu |
| type_evenement | text | Type (table types_evenement) |
| classes | text[] | Classes concernées |
| directeur_course | text | Directeur de course |
| directeur_jury | text | Président de jury |
| ers_url / inscriptions_url | text | Liens avis de course / inscription |
| proposeur_nom / _email / _message | text | Auteur d'une proposition |
| created_at / updated_at | timestamptz | Horodatage (updated_at auto) |
Autres tables
admins:user_id,email,created_at.types_evenement:id,nom,couleur,ordre.statuts:code,libelle,couleur,ordre,visible_public,soumis_dates,selectionnable,systeme.
Fonctions
is_admin()— l'utilisateur courant est-il administrateur (utilisée par la RLS).add_admin_by_email(email),remove_admin(user_id),list_admins()— gestion des admins (SECURITY DEFINER, réservées aux admins).
14. Sécurité (RLS)
- Lecture publique : uniquement les courses dont le statut a
visible_public = true. - Proposition publique : le rôle
anonpeut insérer une course uniquement au statutPROPOSEE. - Modification / suppression / validation : réservées aux administrateurs (
is_admin()). - Paramètres (
types_evenement,statuts) : lecture publique, écriture réservée aux admins. - La clé
service_rolen'est utilisée que dans n8n (jamais côté navigateur).
15. Charte graphique
Identité visuelle de la Fédération Française de Char à Voile :
- Couleurs : bleu roi
#0035CF, rouge#F90305, vert#7CC68D, bleu marine#2B387E(variables CSS, thèmes clair et sombre). - Polices : Chivo (titres) et Roboto (texte), via Google Fonts.
- Logo :
img/logo-ffcv.png(propriété de la FFCV).
16. Versioning
La version applicative est définie en un seul endroit : constante APP_VERSION dans js/common.js. Elle s'affiche automatiquement dans le pied de page de toutes les pages. Version actuelle : v?.
Le projet est versionné avec Git ; l'historique documente chaque évolution.
17. Dépannage
| Symptôme | Cause / Solution |
|---|---|
| « Configuration requise » sur la page | js/config.js non renseigné (URL/clé Supabase). |
Proposition non enregistrée — erreur 42501 (RLS) | Exécuter migration_proposition_publique.sql (policy INSERT publique). |
Édition d'une course en erreur 400 (check_violation) | Statut personnalisé bloqué par une ancienne contrainte : exécuter migration_statut_libre.sql. |
Webhook n8n « ne se déclenche pas » — 404 not registered for POST | Le nœud Webhook doit être en HTTP Method POST. |
| Lien de reset « invalide » | Le mail doit contenir le lien avec token_hash issu de generate_link ; vérifier les Redirect URLs Supabase ; lien à usage unique. |
| URL n8n de test qui ne répond pas | /webhook-test/… ne réagit qu'en mode « Listen » ; activer le workflow et utiliser l'URL /webhook/… de production. |
| Gestion des administrateurs indisponible | Exécuter migration_admins.sql. |
