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

CoucheTechnologie
FrontHTML / CSS / JavaScript « vanilla » (aucune dépendance de build)
Client base de données@supabase/supabase-js (chargé via CDN)
Base de données & AuthSupabase (PostgreSQL, Auth email/mot de passe, RLS)
Emails / automatisationsn8n (webhooks)
Polices / logoChivo + Roboto (Google Fonts), logo FFCV
Hébergement conseilléNetlify (ou Vercel, Cloudflare Pages, GitHub Pages)
Principe de sécurité : seule la clé publique (anon/publishable) est utilisée côté navigateur ; l'accès est verrouillé par les règles RLS. La clé 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

  1. Créer un projet sur supabase.com.
  2. Dans SQL Editor, exécuter dans l'ordre :
#FichierRôle
1schema.sqlTables courses, admins, fonction is_admin(), RLS
2seed.sqlImport des courses initiales (facultatif)
3migration_club.sqlColonne club
4migration_parametres.sqlTables types_evenement et statuts (paramétrables) + visibilité publique par statut
5migration_ligue_directeurs.sqlColonne ligue, renommage arbitredirecteur_course, ajout directeur_jury
6migration_admins.sqlFonctions de gestion des administrateurs (add_admin_by_email, remove_admin, list_admins)
7migration_proposition_publique.sqlAutorise la soumission publique (policy INSERT + grants)
8migration_statut_libre.sqlRetire la contrainte CHECK figée sur statut (statuts personnalisés)
Les migrations sont idempotentes : sans risque même relancées. Une nouvelle installation exécute tout dans l'ordre ; une base existante n'exécute que les migrations manquantes.

Créer le premier administrateur

  1. Supabase → Authentication → Users → Add user (cocher « Auto Confirm User »).
  2. Copier l'User UID.
  3. 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_URLURL du projet Supabase (Project Settings → API)
SUPABASE_ANON_KEYClé anon / publishable — publique, protégée par la RLS
N8N_RESET_WEBHOOKWebhook n8n appelé pour l'email de réinitialisation
N8N_PROPOSITION_WEBHOOKWebhook n8n notifié à chaque nouvelle proposition (facultatif)

Deux méthodes :

  • Édition directe de js/config.js.
  • Via .env + build : copier .env.example en .env, puis node build-config.js régénère js/config.js (pratique pour Netlify).
Ne jamais mettre la clé 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.bathttp://localhost:8080.
  • Ligne de commande : python -m http.server 8080 dans le dossier du projet.
PageURL locale
Publichttp://localhost:8080/index.html
Administrationhttp://localhost:8080/admin.html
Documentationhttp://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.js renseigné).
  • Depuis Git : importer le dépôt ; Netlify lit netlify.toml (node build-config.js). Définir SUPABASE_URL, SUPABASE_ANON_KEY, N8N_RESET_WEBHOOK, N8N_PROPOSITION_WEBHOOK dans Site settings → Environment variables.
Après déploiement, ajouter l'URL de production de 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 (seules directeur_course et directeur_jury sont mises à jour).
Sécurité : un arbitre ne peut modifier que ces deux colonnes, via la fonction 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 finEn cours
Date de fin dépasséePassé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).

  1. Écran de connexion → « Mot de passe oublié ? » → l'app POST au webhook N8N_RESET_WEBHOOK (form-urlencoded) : email, redirect_to (URL de reset.html), app.
  2. n8n : Webhook (POST) → HTTP Request POST {SUPABASE_URL}/auth/v1/admin/generate_link (en-têtes apikey + Authorization: Bearer = service_role, corps { "type":"recovery", "email":"{{ $json.body.email }}", "redirect_to":"{{ $json.body.redirect_to }}" }) → renvoie hashed_token.
  3. Send Email → lien {{ $node["Webhook"].json.body.redirect_to }}?token_hash={{ $json.hashed_token }}&type=recovery.
  4. reset.html : verifyOtp({ type:'recovery', token_hash }) puis updateUser({ password }) — avec la seule clé publique.

Workflow prêt à importer : n8n/workflow-mot-de-passe-oublie.json.

Le nœud Webhook doit être en HTTP Method POST. Ajouter l'URL de 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.

Si 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

ColonneTypeRôle
iduuidClé primaire
saisontextEx. « 2026/2027 »
date_debut / date_findateDates de la course
statuttextCode de statut (table statuts)
nomtextNom de l'événement
clubtextClub organisateur
liguetextLigue de rattachement
lieutextLieu
type_evenementtextType (table types_evenement)
classestext[]Classes concernées
directeur_coursetextDirecteur de course
directeur_jurytextPrésident de jury
ers_url / inscriptions_urltextLiens avis de course / inscription
proposeur_nom / _email / _messagetextAuteur d'une proposition
created_at / updated_attimestamptzHorodatage (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 anon peut insérer une course uniquement au statut PROPOSEE.
  • 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_role n'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ômeCause / Solution
« Configuration requise » sur la pagejs/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 POSTLe 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 indisponibleExécuter migration_admins.sql.