Contribuer à Club'O'Clock
Merci de l'intérêt que tu portes au projet. Club'O'Clock est développé pour les clubs sportifs associatifs : les contributions les plus utiles viennent souvent de gens qui l'utilisent réellement dans leur club.
Avant tout : la langue du projet
Le projet est en français — code, commentaires, commits, issues, documentation, interface. C'est un choix assumé : les utilisateurs finaux sont des bénévoles de clubs français, et l'écart entre le vocabulaire du code et celui du terrain est une source de bugs. Merci de t'y tenir dans tes contributions.
Signaler un bug
Ouvre une issue avec :
- ce que tu faisais, ce que tu attendais, ce qui s'est passé ;
- ton rôle dans l'app (admin, coach, athlète, parent) — beaucoup de comportements en dépendent ;
- navigateur / appareil si c'est un problème d'affichage, mobile ou desktop ;
- les logs pertinents (
storage/logs/laravel.log), expurgés de toute donnée personnelle.
⚠️ Jamais de données réelles d'adhérents dans une issue : ni nom, ni email, ni capture d'écran non floutée. Reproduis avec le jeu de démonstration (COMPTES_DEMO.md) quand c'est possible.
Un bug, pas une friction. Cette section vise les défauts : le code ne fait pas ce que le PRD ou le design annoncent déjà. Quand l'usage révèle plutôt un manque que le cadrage n'avait pas vu — quelque chose qui reste à arbitrer —, la place est le carnet Retours terrain, pas une issue. Un même constat peut mériter les deux.
Pour une faille de sécurité, n'ouvre pas d'issue : suis SECURITY.md.
Proposer une fonctionnalité
Le périmètre est cadré par le PRD, qui distingue le périmètre V1 (§3.1) de ce qui en est explicitement exclu (§3.2). Avant d'écrire du code :
- vérifie que ta proposition n'est pas déjà hors périmètre — plusieurs exclusions sont délibérées, avec leur raison ;
- ouvre une issue de discussion avant une grosse PR. Une fonctionnalité développée puis refusée sur le principe, c'est du temps perdu pour tout le monde.
Ce que le projet ne fera pas
Quelques partis pris structurants, pour t'éviter un travail voué au refus :
- Pas de multi-tenant. Un club = une instance. Aucune notion de
tenant_id, aucune isolation logique multi-club. C'est ce qui garde le modèle de données et les autorisations simples. - Pas de stockage de numéro de téléphone ni de certificat médical, et pas de numéro de licence fédérale — minimisation RGPD. Ces données vivent sur l'extranet de la fédération.
- Pas d'application native iOS/Android : c'est une PWA installable.
- Pas de dépendance à un service non-UE dans le chemin critique.
Développer
Mise en route
Suis doc/INSTALL.md (§2), puis charge le jeu de démonstration — il couvre tous les cas tordus (liste d'attente, quotas, mineurs sous tutelle, comptes suspendus) :
php artisan migrate:fresh --seed
php artisan db:seed --class=DemoSeederSans rien installer sur ta machine ? doc/DOCKER_LOCAL.md décrit un poste entièrement conteneurisé — PHP et ses extensions, MySQL, les navigateurs de Playwright — à partir de deux
Dockerfileversionnés. C'est un outil de poste : la cible de déploiement du projet reste le mutualisé sans Docker.
Faire évoluer le schéma
La source de vérité est database/migrations/. Une évolution passe par une nouvelle migration — ne jamais éditer une migration déjà livrée, des instances l'ont déjà jouée.
database/schema/mysql-schema.sql et database/schema/mariadb-schema.sql sont des artefacts dérivés que Laravel charge au début de migrate pour reconstruire vite (tests, base neuve). Il y en a un par moteur parce que la CI joue la suite sur les deux : MySQL 8.4 (celui de la production) et MariaDB 11.4. Après toute migration qui touche au schéma, régénère les deux, sinon les tests repartent d'un schéma périmé :
# Chaque dump se régénère SUR SON PROPRE MOTEUR : mysqldump et mariadb-dump écrivent des
# dialectes différents, et régénérer l'un depuis l'autre produit un fichier que la CI rejette.
DB_CONNECTION=mysql php artisan schema:dump
DB_CONNECTION=mariadb php artisan schema:dumpLes deux moteurs tournent en conteneur ; adapte
DB_HOST/DB_PORTà ton installation. Le client en ligne de commande du moteur visé (mysql-client/mariadb-client) doit être celui queschema:dumptrouve, sinon le dump sort dans le mauvais dialecte.
La porte de qualité
Toute PR doit passer composer check — c'est le prérequis, pas une formalité :
composer check # pint (style) + phpstan (niveau 5) + dérive schéma/front + suite de testsLes trois séparément, si tu veux itérer plus vite :
vendor/bin/pint # corrige le style
vendor/bin/phpstan analyse --memory-limit=1G # analyse statique
composer test # tests (hors groupe `destructif`)
composer test-destructif # le groupe `destructif`, seulPourquoi deux passages de tests.
demo:resetexécutemigrate:fresh— un DROP/CREATE de toutes les tables. Joué au milieu de la suite, il traverse les transactions ouvertes deRefreshDatabaseet fait échouer des tests qui n'ont aucun rapport (un deadlock InnoDB 1213 a été observé sur l'activation d'invitation). Les tests concernés portent donc l'attribut#[Group('destructif')]: exclus de la suite principale parphpunit.xml, rejoués seuls ensuite.composer checkenchaîne les deux — la couverture est identique, la porte cesse d'être erratique. Un test qui rougit une fois sur dix sans cause réelle est pire qu'absent : il apprend à ignorer le rouge.Tout nouveau test qui détruit ou reconstruit la base doit rejoindre ce groupe.
Tests navigateur (E2E) — hors porte de qualité
composer check couvre la logique serveur, pas le rendu. Un harnais Playwright rejoue les parcours critiques dans un vrai navigateur : il clique, attend les mises à jour Livewire, vérifie l'état en base et prend des captures aux deux formats.
php artisan serve # prérequis : serveur + base de démo seedée
node tests/E2E/run.mjs # suites non destructivesIl est volontairement hors de composer check : il exige un serveur, une base seedée et un navigateur — l'y intégrer rendrait la porte de qualité fragile pour de mauvaises raisons. La référence de non-régression reste PHPUnit. Détails et conventions d'écriture : tests/E2E/README.md.
Playwright est en devDependencies : jamais installé ni exécuté sur l'hébergement mutualisé. Pour l'exécuter sans installer de navigateur sur ta machine, voir doc/DOCKER_LOCAL.md §4.
Attentes sur le code
- Des tests. Toute correction de bug apporte un test qui échouait avant. Toute fonctionnalité apporte les tests de son comportement, y compris les refus (qui n'a pas le droit de faire quoi).
- Fidélité au design system. Les écrans réutilisent les classes existantes de
club-app.css(scard,seg,chip,ifield,btn-*…). Avant d'écrire une classe maison, vérifie qu'elle n'existe pas déjà. Toute valeur de couleur ou d'espacement passe par un token (var(--…)), jamais en dur. - Mobile ET desktop. L'app est utilisée majoritairement au téléphone. Un écran n'est fini que s'il rend correctement dans les deux formats.
- Vocabulaire du modèle. Le statut d'inscription est
participating|waitlist|cancelled— pas d'autre valeur. La récurrence passe par le générateurSessionTemplate, pas par des RRULE.
Branches et versions
main est toujours déployable — c'est la branche que les clubs installent, et la CI y rejoue composer check à chaque push. On ne commite jamais directement dessus.
Une branche courte par sujet, fusionnée après revue, puis supprimée :
feat/… nouvelle fonctionnalité fix/… correction de bug
docs/… documentation seule chore/… outillage, dépendancesPas de branche
develop. Elle sert à intégrer le travail de plusieurs équipes et à maintenir plusieurs versions en parallèle — ni l'un ni l'autre ici : un mainteneur, et chaque club déploie lemaincourant. Ce serait une branche de plus à synchroniser pour rien.
Les versions sont des tags semver sur main. Ce que chaque incrément signifie se lit du point de vue de celui qui met à jour son instance :
| Incrément | Pour le club qui déploie |
|---|---|
| MAJEUR — 2.0.0 | Une action manuelle est nécessaire : nouvelle variable .env obligatoire, version de PHP, manipulation irréversible. À lire avant de déployer. |
| MINEUR — 1.1.0 | Fonctionnalité nouvelle. Migrations jouées par la checklist habituelle, rien à décider. |
| CORRECTIF — 1.0.1 | Correction de bug, aucune migration. |
Les versions sortent quand un lot cohérent est prêt, sans cadence annoncée : le projet est maintenu bénévolement par une seule personne, et un rythme promis puis non tenu vaut moins que pas de rythme du tout. Une exception : un correctif de sécurité part seul et sans attendre (cf. SECURITY.md).
Commits et PR
- Messages de commit en français, à l'impératif, préfixés par la zone touchée :
fix(planning): …,feat(admin): …,docs: …. - Une PR = un sujet. Une PR qui corrige un bug et refactorise trois fichiers est difficile à relire, donc lente à fusionner.
- Décris le comportement observable : ce qui change pour l'utilisateur, pas seulement pour le code. Une capture avant/après aide beaucoup sur un changement visuel.
Le site du projet
https://cluboclock.ratelet.fr réunit la vitrine et la documentation. Il se construit depuis ce dépôt, et se déploie tout seul : le workflow .github/workflows/pages.yml se déclenche à chaque poussée sur main qui touche doc/, site/ ou les documents de la racine.
Pour le voir avant de pousser :
./site/build-local.sh --serve # http://localhost:8080Le script assemble _site/ — la vitrine à la racine, la documentation sous /doc/ — et c'est lui que le workflow appelle. Ce qui marche en local marche en CI, puisque ce sont les mêmes étapes. Il échoue si un lien est mort, si la police n'est pas servie ou s'il manque un fichier.
Deux réglages ne vivent pas dans le dépôt et n'ont à être faits qu'une fois :
- GitHub → Settings → Pages → Source = GitHub Actions. Sans lui, le job
deployéchoue en fin de course. - Chez le registrar du domaine : un enregistrement
CNAMEdecluboclockversstephanfo.github.io.(le point final compte). Le fichierCNAMEde l'artefact, lui, est écrit parbuild-local.sh— il ne se modifie pas à la main. Une fois le DNS propagé, cocher Enforce HTTPS dans les réglages Pages.
Le sous-domaine
demo.est indépendant : il pointe vers l'hébergement mutualisé et n'est pas concerné par GitHub Pages.
Licence
Le projet est sous AGPL-3.0. En contribuant, tu acceptes que ta contribution soit distribuée sous cette licence.
Concrètement, l'AGPL implique que si tu déploies une version modifiée accessible en réseau, tu dois en proposer le code source à ses utilisateurs (§13). C'est délibéré : ce que les clubs s'apportent mutuellement doit rester disponible aux clubs.