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 un webhook Make.

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 / automatisationsMake (webhook)
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é Make.

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
├── 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)
9migration_arbitres.sqlProfil Arbitre + affectations (DC / PJ) + gestion unifiée des utilisateurs
10migration_suivi.sqlSuivi administratif : lettres de mission, notes de frais, lien résultats
11migration_connexions.sqlJournal de connexions/déconnexions (table connexions avec colonne evenement + fonctions enregistrer_connexion, list_connexions, purger_connexions)
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)
  MAKE_WEBHOOK: "https://hook.eu1.make.com/…"   // mot de passe oublié + proposition
};
CléDescription
SUPABASE_URLURL du projet Supabase (Project Settings → API)
SUPABASE_ANON_KEYClé anon / publishable — publique, protégée par la RLS
MAKE_WEBHOOKWebhook Make unique — mot de passe oublié + proposition (champ type pour distinguer)

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 Make.

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, MAKE_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, puis des indicateurs de suivi en badge Oui/Non, et enfin Statut. Actions Modifier / Supprimer, et + Nouvelle course. L'éditeur couvre tous les champs (avec autocomplétion des clubs et ligues déjà saisis).

Colonnes de suivi (badge Oui vert / Non gris) :

  • ERC : l'avis de course (ERS) est renseigné (champ ers_url).
  • Inscr. : le lien d'inscription est renseigné (inscriptions_url).
  • Résultat : le lien des résultats est renseigné (resultat_url).
  • LM DC / LM PJ : lettre de mission émise pour le Directeur de course / le Président de jury (lm_directeur_course, lm_president_jury).
  • NDF DC / NDF PJ : note de frais traitée pour le Directeur de course / le Président de jury (ndf_directeur_course, ndf_president_jury).

Les infobulles (survol des en-têtes) rappellent le libellé complet de chaque colonne. Ces informations se règlent dans l'éditeur de course (liens et cases à cocher du bloc « Suivi administratif »).

Filtres par colonne

Sous la ligne d'en-tête, chaque colonne dispose de son propre filtre, qui se combine avec les autres (ET logique) et avec la recherche globale et la saison :

  • Champs texte (Dates, Événement, Lieu, Directeur de course, Président de jury) : filtrage par inclusion (« contient »).
  • Listes déroulantes (Club, Ligue, Type, Classes, Statut) : valeurs proposées d'après les données existantes ; le filtre Statut tient compte du statut effectif (À venir / En cours / Passée selon les dates).
  • Indicateurs de suivi (ERC, Inscr., Résultat, LM/NDF) : choix Tous / Oui / Non.

Le bouton ✖ Réinitialiser les filtres efface tous les filtres (colonnes, recherche, saison) en une fois.

Export Excel

Le bouton ⬇ Exporter (Excel) génère un fichier .xlsx reprenant exactement les lignes affichées (donc filtrées). L'export contient toutes les données utiles : dates (plage lisible + dates ISO début/fin), saison, événement, club, ligue, lieu, type, classes, directeur de course, président de jury, les indicateurs de suivi en Oui/Non ainsi que les URL (avis de course, inscription, résultats), et le statut effectif. Le fichier est nommé calendrier-ffcv_AAAAMMJJ-HHMM.xlsx et s'ouvre directement dans Excel ou LibreOffice.

L'export s'appuie sur la bibliothèque SheetJS (chargée via CDN). Sans connexion au CDN, le bouton affiche un message d'indisponibilité mais le reste de l'application fonctionne normalement.

⚙️ 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.
  • Utilisateurs & rôles : une liste unique où chaque personne porte deux cases à cocher, Admin et Arbitre — un même compte peut cumuler les deux. Ajout par email + mot de passe initial (ou mot de passe vide pour promouvoir un compte existant) avec choix des rôles ; retrait de tous les rôles. On ne peut pas retirer son propre rôle Admin (anti-verrouillage).
  • Journal de connexions : tableau des dernières entrées d'authentification d'utilisateurs identifiés — connexions (badge vert) et déconnexions (badge gris) — avec date & heure locale, email, rôles et navigateur/OS déduit. Les 200 dernières entrées sont listées.
    • Filtres : par utilisateur (liste des emails présents) et par type d'événement (connexion / déconnexion) ; bouton ↻ Rafraîchir.
    • Purge : suppression des entrées de plus de 30 / 90 / 180 jours, 1 an, ou de tout le journal (avec confirmation).

À chaque connexion réussie et à chaque déconnexion (page d'administration ou espace arbitre), l'application appelle la fonction enregistrer_connexion(user_agent, evenement) qui journalise uniquement l'événement de l'utilisateur courant (la déconnexion est enregistrée avant la fermeture de session). Le journal n'est lisible que par un administrateur (fonction list_connexions(), SECURITY DEFINER) ; la table n'est pas exposée directement via l'API. La purge s'appuie sur purger_connexions(avant), également réservée aux administrateurs.

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 ; une colonne Statut (pastille colorée, statut effectif selon les dates) situe chaque 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 → Utilisateurs & rôles, cochez la case « Arbitre » du compte (ou créez-le avec cette case). Nécessite la migration migration_arbitres.sql.

Connexion : un arbitre peut se connecter sur admin.html comme sur arbitre.html ; s'il n'est pas administrateur, il est automatiquement redirigé vers son espace arbitre.html.

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é (webhook Make)

Aucun secret n'est exposé dans le navigateur : c'est le scénario Make qui génère le lien de récupération via l'API Admin Supabase (clé service_role gardée dans Make). Le webhook Make est unique ; le champ type=recovery route vers ce scénario.

  1. Écran de connexion → « Mot de passe oublié ? » → l'app POST au webhook MAKE_WEBHOOK (form-urlencoded) : type=recovery, email, redirect_to (URL de reset.html), app.
  2. Make : WebhookRouteur (sur type) → HTTP POST {SUPABASE_URL}/auth/v1/admin/generate_link (en-têtes apikey + Authorization: Bearer = service_role) → renvoie email_otp (code à 6 chiffres).
  3. Email (modèle : make/email-reset.html) → contient le code {{email_otp}} et un simple lien vers la page (redirect_to, sans jeton). Ne pas inclure de lien action_link/token_hash.
  4. Sur reset.html : saisie email + code + nouveau mot de passeverifyOtp({ email, token, type:'recovery' }) puis updateUser({ password }) — clé publique uniquement.
⚠️ Ne pas mettre de lien de récupération dans l'email : action_link et email_otp partagent le même jeton à usage unique ; un antivirus de messagerie qui pré-ouvre le lien invalide aussi le code. Envoyer uniquement le code (texte non cliquable) résout le problème. Ajouter l'URL de reset.html aux Redirect URLs Supabase.

12. Webhook de proposition

À chaque proposition enregistrée avec succès, l'app envoie (fire-and-forget) les informations saisies au même webhook Make (MAKE_WEBHOOK) avec type=proposition, en form-urlencoded :

type, nom, lieu, ligue, type_evenement, date_debut, date_fin, saison, classes, inscriptions_url, proposeur_nom, proposeur_email, proposeur_message, submitted_at.

Si MAKE_WEBHOOK est vide, aucun envoi n'est fait (la proposition est quand même enregistrée). Côté Make, router sur type=proposition.

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
lm_directeur_course / lm_president_jurybooleanLettre de mission envoyée (DC / PJ)
ndf_directeur_course / ndf_president_jurybooleanNote de frais reçue (DC / PJ)
resultat_urltextLien vers les résultats (affiché sur la fiche publique)
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 Make (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 Make ne se déclenche pasVérifier que le scénario Make est actif et que le module Webhook accepte les requêtes POST ; contrôler la valeur de MAKE_WEBHOOK.
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.
Lien de reset invalideL'email doit contenir le lien reset.html?token_hash=…&type=recovery (jeton issu de generate_link) ; vérifier les Redirect URLs Supabase ; lien à usage unique.
Gestion des utilisateurs indisponibleExécuter migration_admins.sql et migration_arbitres.sql.
Un arbitre reçoit « pas de droits d'accès »Lui attribuer le rôle Arbitre (Paramètres → Utilisateurs & rôles) et vérifier que migration_arbitres.sql est exécutée.