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
| 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 | Make (webhook) |
| 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é 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
- 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) |
| 9 | migration_arbitres.sql | Profil Arbitre + affectations (DC / PJ) + gestion unifiée des utilisateurs |
| 10 | migration_suivi.sql | Suivi administratif : lettres de mission, notes de frais, lien résultats |
| 11 | migration_connexions.sql | Journal de connexions/déconnexions (table connexions avec colonne evenement + fonctions enregistrer_connexion, list_connexions, purger_connexions) |
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)
MAKE_WEBHOOK: "https://hook.eu1.make.com/…" // mot de passe oublié + 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 |
MAKE_WEBHOOK | Webhook 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.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 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.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,MAKE_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, 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.
⚙️ 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(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 → 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 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é (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.
- Écran de connexion → « Mot de passe oublié ? » → l'app POST au webhook
MAKE_WEBHOOK(form-urlencoded) :type=recovery,email,redirect_to(URL dereset.html),app. - Make : Webhook → Routeur (sur
type) → HTTPPOST {SUPABASE_URL}/auth/v1/admin/generate_link(en-têtesapikey+Authorization: Bearer= service_role) → renvoieemail_otp(code à 6 chiffres). - 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 lienaction_link/token_hash. - Sur
reset.html: saisie email + code + nouveau mot de passe →verifyOtp({ email, token, type:'recovery' })puisupdateUser({ password })— clé publique uniquement.
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.
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
| 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 |
| lm_directeur_course / lm_president_jury | boolean | Lettre de mission envoyée (DC / PJ) |
| ndf_directeur_course / ndf_president_jury | boolean | Note de frais reçue (DC / PJ) |
| resultat_url | text | Lien vers les résultats (affiché sur la fiche publique) |
| 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 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ô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 Make ne se déclenche pas | Vé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 invalide | L'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 indisponible | Exé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. |
