Rendez vos réunions d'équipe hebdomadaires infiniment plus fun avec des grilles de bingo partagées en temps réel.
- Fonctionnalités
- Stack technique
- Architecture
- Structure du projet
- Développement local
- CI / CD GitHub
- Déploiement en production
- Configuration Apache
- Variables d'environnement
- Base de données
- Sécurité
- Commandes utiles
- Créer une équipe et inviter des membres via un code d'invitation unique
- Rejoindre une équipe existante avec le code
- Rôles :
OWNER,ADMIN,MEMBER
- Grille de taille entièrement configurable (lignes × colonnes, de 2×2 à 10×10)
- Case centrale FREE optionnelle (activable uniquement sur les grilles de taille impaire)
- Remplissage depuis une banque de 35 phrases classiques de réunion (prédéfinies)
- Possibilité d'ajouter ses propres phrases avec un emoji
- Génération aléatoire des cases à partir des phrases sélectionnées
- Toute l'équipe partage la même grille en temps réel via Socket.io
- Cliquer une case la coche instantanément pour tous les participants
- Détection automatique du bingo : lignes, colonnes, diagonales (sur grilles carrées)
- Célébration animée avec confettis et bannière quand un bingo est détecté
- Affichage des membres en ligne pendant la session
- Toutes les grilles passées sont conservées avec leur date et leur taux de complétion
- Progression visible (nombre de cases cochées / total)
| Couche | Technologie |
|---|---|
| Framework | Next.js 16 (App Router) |
| Langage | TypeScript |
| Styles | Tailwind CSS |
| Animations | Framer Motion + react-confetti |
| Authentification | NextAuth.js v4 (email/mot de passe) |
| Base de données | PostgreSQL 16 via Prisma ORM |
| Temps réel | Socket.io (serveur custom Node.js) |
| Déploiement | Docker Compose |
| Reverse proxy | Apache 2.4 |
Internet
│
│ HTTPS :443
▼
┌─────────────┐
│ Apache 2.4 │ ← SSL termination, security headers, HSTS
│ (hôte) │
└──────┬──────┘
│ HTTP 127.0.0.1:3000
│ WS 127.0.0.1:3000 (Socket.io)
▼
┌─────────────────────────────┐
│ Docker network: internal │
│ │
│ ┌─────────────────────┐ │
│ │ markit_app │ │
│ │ Next.js + Socket.io│ │
│ │ port 3000 │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ markit_db │ │
│ │ PostgreSQL 16 │ │
│ │ (non exposé) │ │
│ └─────────────────────┘ │
└─────────────────────────────┘
- L'application est uniquement accessible via
127.0.0.1:3000depuis l'hôte — jamais depuis Internet directement. - La base de données n'expose aucun port vers l'extérieur.
- Apache gère le SSL, les redirections HTTP→HTTPS et les en-têtes de sécurité.
markit/
├── .github/
│ ├── workflows/ci.yml # Pipeline CI/CD GitHub Actions
│ └── dependabot.yml # Mises à jour automatiques (npm, Actions, Docker)
├── apache/
│ └── markit.conf # Configuration VirtualHost Apache
├── prisma/
│ ├── migrations/ # Migrations Prisma versionnées
│ ├── schema.prisma # Schéma de la base de données
│ └── seed.mjs # Données initiales (35 phrases par défaut)
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ ├── account/ # Profil / mot de passe
│ │ │ ├── admin/ # Admin (users, invites)
│ │ │ ├── auth/ # NextAuth + inscription email
│ │ │ ├── cards/ # API grilles (récupérer, activer, cocher cases)
│ │ │ └── teams/ # API équipes (CRUD, rejoindre, phrases)
│ │ ├── account/ # Configuration du compte
│ │ ├── admin/ # Console admin
│ │ ├── auth/
│ │ │ ├── signin/ # Page de connexion
│ │ │ └── signup/ # Page d'inscription
│ │ ├── dashboard/
│ │ │ ├── page.tsx # Liste des équipes
│ │ │ └── teams/[teamId]/
│ │ │ ├── page.tsx # Liste des grilles de l'équipe
│ │ │ └── create/ # Créateur de grille
│ │ └── play/[cardId]/ # Page de jeu en temps réel
│ ├── components/
│ │ ├── admin/
│ │ ├── account-settings-form/
│ │ ├── dashboard/
│ │ ├── landing-hero/
│ │ └── navbar/
│ ├── lib/
│ │ ├── account.ts # Profil et validation mot de passe
│ │ ├── api-auth.ts # Guards session API
│ │ ├── auth.ts # Configuration NextAuth
│ │ ├── bingo.ts # Logique de détection bingo
│ │ ├── prisma.ts # Client Prisma singleton
│ │ ├── schemas/ # Schémas Zod (entrées API)
│ │ └── socket.ts # Client Socket.io
│ └── types/
│ └── index.ts # Types TypeScript partagés
├── .env.example # Template des variables d'environnement
├── docker-compose.yml # Orchestration des containers
├── Dockerfile # Image de l'application
├── next.config.js # Configuration Next.js + security headers
├── server.js # Serveur custom Node.js (Next.js + Socket.io)
└── tailwind.config.ts
- Node.js 24 (voir
.nvmrc;engines:>=24 <25) - Docker + Docker Compose
npm
# 1. Cloner le projet
git clone <repo> markit && cd markit
# 2. Installer les dépendances
npm install
# 3. Copier et configurer les variables d'environnement
cp .env.example .envÉditer .env (les valeurs par défaut fonctionnent en dev) :
DATABASE_URL="postgresql://markit:markit_password@localhost:5432/markit"
PORT=3000
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="<générer avec : openssl rand -base64 32>"Si le port 3000 est déjà pris (autre app locale), change PORT et NEXTAUTH_URL ensemble, par ex. PORT=3001 et NEXTAUTH_URL="http://localhost:3001".
# 4. Démarrer la base de données PostgreSQL
docker compose up postgres -d
# 5. Appliquer les migrations (ou db:push en prototypage)
npm run db:migrate:deploy
# 6. Charger les 35 phrases par défaut (+ admin si ADMIN_* défini)
npm run db:seed
# 7. Lancer le serveur de développement
npm run devL'application est disponible sur http://localhost:3000.
Avant de pousser, lancer les contrôles locaux :
npm run ci # Node, NEXTAUTH_SECRET, Prisma generate, lint, markdownlint, typecheck, tests
npm run ci:full # idem + audit npm (niveau high+, sans deps de dev)Husky exécute npm run ci au pre-push et commitlint au commit-msg (Conventional Commits).
Au premier démarrage Docker, scripts/docker-entrypoint.sh applique prisma migrate deploy, lance le seed, puis démarre le serveur. Définis ADMIN_EMAIL / ADMIN_PASSWORD (≥ 12 caractères) pour créer le compte admin initial (mustChangePassword → redirection /account).
- Connexion admin →
/accountsi première connexion - Dashboard → créer / rejoindre une équipe
- Créer une grille → jouer → cases temps réel
- Admin → générer une invitation → signup
Le workflow .github/workflows/ci.yml tourne sur chaque push et pull request vers main (les changements sous .cursor/ sont ignorés).
| Job | Déclencheur | Rôle |
|---|---|---|
quality |
push / PR sur main |
npm ci puis npm run ci:full |
docker |
PR sur main uniquement |
build Docker (validation, sans push d'image) |
release |
push sur main (après quality) |
semantic-release, puis push image GHCR si une version est publiée |
npm run ci:full enchaîne :
- Vérification de la version Node.js
- Présence / format de
NEXTAUTH_SECRET prisma generate- ESLint (
npm run lint) - Markdownlint (
npm run lint:md) - TypeScript (
npm run typecheck) - Audit npm (
npm run audit:ci, sévérité ≥ high, deps de prod uniquement)
En local, npm run ci saute l'audit réseau ; npm run ci:full le réactive (comme en CI).
Sur une pull request, le Dockerfile est construit avec Buildx pour valider l'image. Rien n'est poussé sur GHCR à cette étape (le push productif reste dans release). Le cache de build est partagé via GHCR (buildcache) et le cache GitHub Actions.
Config : release.config.cjs. Sur chaque push réussi sur main, semantic-release calcule la version à partir des Conventional Commits (feat:, fix:, chore:, …) et peut publier :
- un tag Git
vX.Y.Z - une GitHub Release
- la mise à jour de
CHANGELOG.md,package.jsonetpackage-lock.json - un commit
chore(release): … [skip ci]
Ensuite, si une release a bien été publiée, l'image Docker est poussée sur ghcr.io/<org>/markit avec les tags vX.Y.Z, X.Y et latest (ce dernier uniquement pour une version stable, pas une pre-release).
Secrets (Settings → Secrets and variables → Actions) :
RELEASE_APP_ID— ID de la GitHub App utilisée pour publier les releasesRELEASE_APP_PRIVATE_KEY— clé privée de cette App
La GitHub App pousse les commits / tags de release (utile avec une protection de branche). Les images GHCR utilisent GITHUB_TOKEN (permissions packages: write sur les jobs docker et release).
Vérifier aussi Settings → Actions → General → Workflow permissions → Read and write permissions si besoin pour GHCR.
.github/dependabot.yml ouvre des PRs chaque lundi pour :
| Écosystème | Cible | Groupes / ignore |
|---|---|---|
npm |
package.json / lockfile |
groupes next, prisma, dev-tools ; majeures @types/node et typescript ignorées |
github-actions |
pins SHA des workflows | — |
docker |
Dockerfile (y compris digests) |
majeures de l'image node ignorées |
Les commits / titres de PR suivent chore(deps): … (pas de bump de version semantic-release).
À activer sur GitHub (Settings → Advanced Security, ou onglet Security) :
- Dependency graph
- Dependabot alerts
- Dependabot security updates
- Dependabot version updates (détecte
dependabot.ymlsurmain)
Puis Settings → Actions → General : Allow GitHub Actions to create and approve pull requests si tu veux que Dependabot puisse relancer la CI / rebaser proprement.
npm run ci
git checkout -b feat/ma-feature
git add .
git commit -m "feat: description courte"
git push -u origin feat/ma-featureOuvrir une PR vers main, puis merger (idéalement en squash-merge avec un titre conventional). semantic-release s'exécute sur le push résultant vers main.
- Docker + Docker Compose
- Apache 2.4 avec les modules :
proxy,proxy_http,proxy_wstunnel,rewrite,headers,ssl - Un nom de domaine pointant sur le serveur
- Certbot (Let's Encrypt) pour le certificat SSL
git clone <repo> /opt/markit && cd /opt/markit
cp .env.example .env# Générer les secrets
openssl rand -base64 32 # → POSTGRES_DB_PASSWORD
openssl rand -base64 32 # → NEXTAUTH_SECRETÉditer /opt/markit/.env :
POSTGRES_USER=markit
POSTGRES_DB=markit
POSTGRES_DB_PASSWORD=<secret généré>
NEXTAUTH_URL=https://markit.example.com
NEXTAUTH_SECRET=<secret généré>docker compose up -d --buildLes containers démarrent, les migrations sont appliquées automatiquement et les phrases par défaut sont chargées.
Vérifier que tout tourne :
docker compose ps
docker compose logs -f appActiver les modules nécessaires :
a2enmod proxy proxy_http proxy_wstunnel rewrite headers sslObtenir le certificat SSL :
certbot certonly --standalone -d markit.example.comCopier et activer la configuration Apache :
# Adapter le ServerName dans le fichier
cp /opt/markit/apache/markit.conf /etc/apache2/sites-available/markit.conf
# Remplacer markit.example.com par ton domaine réel
nano /etc/apache2/sites-available/markit.conf
a2ensite markit.conf
apache2ctl configtest # Vérifier la syntaxe
systemctl reload apache2Le site est maintenant accessible sur https://markit.example.com.
Certbot installe un cron automatique. Vérifier avec :
certbot renew --dry-runLe fichier apache/markit.conf configure :
| Fonctionnalité | Détail |
|---|---|
| Redirect HTTP→HTTPS | RewriteRule permanent (301) |
| SSL/TLS | TLS 1.2 et 1.3 uniquement, ciphers modernes |
| HSTS | max-age=63072000; includeSubDomains; preload (2 ans) |
| Proxy HTTP | ProxyPass vers 127.0.0.1:3000 |
| Proxy WebSocket | RewriteRule vers ws://127.0.0.1:3000 pour Socket.io |
| En-têtes sécurité | X-Frame-Options, X-Content-Type-Options, Referrer-Policy |
| Headers forwarded | X-Forwarded-Proto: https transmis à Next.js |
Important : remplacer
markit.example.compar ton domaine réel dans le fichier.
| Variable | Obligatoire | Description |
|---|---|---|
POSTGRES_USER |
production | Utilisateur PostgreSQL (défaut : markit) |
POSTGRES_DB |
production | Nom de la base (défaut : markit) |
POSTGRES_DB_PASSWORD |
oui | Mot de passe PostgreSQL (openssl rand -base64 32 OK — encodé au démarrage Docker) |
DATABASE_URL |
optionnel | URL complète ; en prod Docker, laisser vide (construite par l'entrypoint). En dev local, URL vers localhost |
PORT |
optionnel | Port HTTP (défaut 3000). En dev et Docker local : adapter NEXTAUTH_URL en conséquence |
NEXTAUTH_URL |
oui | URL publique du site (https://... en prod, http://localhost:<PORT> en dev) |
NEXTAUTH_SECRET |
oui | Clé de signature JWT — générer avec openssl rand -base64 32 |
User → compte utilisateur
Team → équipe avec code d'invitation
TeamMember → appartenance utilisateur↔équipe (rôle : OWNER/ADMIN/MEMBER)
Phrase → phrase de la banque (isDefault=true pour les phrases communes)
BingoCard → grille de bingo (rows × cols, freeCenter)
Cell → case de la grille (phrase + position)
CheckedCell → case cochée (par quel utilisateur, quand)
npm run db:migrate:deploy # Appliquer les migrations (prod / Docker)
npm run db:migrate # Créer une migration (dev)
npm run db:push # Sync schéma sans migration (prototypage)
npm run db:seed # Charger les phrases + admin si configuré
npm run db:studio # Ouvrir Prisma Studio
npm run db:generate # Regénérer le client Prisma# Dump
docker exec markit_db pg_dump -U markit markit > backup_$(date +%Y%m%d).sql
# Restauration
docker exec -i markit_db psql -U markit markit < backup_20260101.sql| Mesure | Implémentation | Détail |
|---|---|---|
| HTTPS forcé | Redirect Apache 301 + HSTS | 2 ans, includeSubDomains, preload |
| Cookies sécurisés | Secure + HttpOnly auto si HTTPS |
Via useSecureCookies NextAuth |
| Mots de passe | bcrypt (coût 12) | ~300ms/tentative, résistant aux rainbow tables |
| Sessions JWT | Signées NEXTAUTH_SECRET, 7 jours |
Token invalidé si le secret change |
| Autorisation API | Vérification d'appartenance à l'équipe | Protection contre les attaques IDOR |
| Authentification Socket.io | JWT NextAuth vérifié côté serveur | Le userName est résolu serveur, non falsifiable |
| CORS Socket.io | Restreint au domaine NEXTAUTH_URL en prod |
* seulement en dev |
| CSP | Séparée dev/prod, sans unsafe-eval en prod |
Protège contre XSS |
| X-Frame-Options | SAMEORIGIN |
Anti-clickjacking |
| X-Content-Type-Options | nosniff |
Anti-MIME sniffing |
| X-Forwarded-For | Écrasé par Apache | Empêche la falsification d'IP côté client |
| Taille des requêtes | LimitRequestBody 1MB Apache |
Protège contre les DoS simples |
| Longueurs de champ | Vérifiées côté serveur sur toutes les API | Empêche les payloads surdimensionnés |
| Ports isolés | App sur 127.0.0.1, BDD sans port public |
Inatteignables depuis Internet |
| En-têtes serveur | X-Powered-By et Server supprimés |
Ne révèle pas la stack |
| TLS | TLS 1.2/1.3 uniquement, ciphers AEAD | SSLv3/TLS 1.0/1.1 désactivés |
-
Rate limiting sur l'authentification — implémenter avec Fail2ban ou
mod_ratelimitApache sur/api/auth/signinet/api/auth/registerpour limiter les tentatives de brute force.Exemple Fail2ban (
/etc/fail2ban/filter.d/markit-auth.conf) :[Definition] failregex = ^<HOST> .* "POST /api/auth/callback/credentials HTTP.*" 401 ignoreregex =
-
Soumission HSTS preload — le header
preloadest positionné mais la soumission au registre https://hstspreload.org/ doit être faite manuellement après vérification que le domaine est stable. -
Mises à jour régulières :
docker compose pull && docker compose up -d --build npm audit fix -
Monitoring des logs — surveiller
markit_error.loget les logs Docker pour détecter des comportements anormaux.
# ── Développement ─────────────────────────────────────────────
npm run dev # Serveur de développement (port 3000)
npm run build # Build de production
npm run lint # Linter ESLint
npm run lint:md # Markdownlint
npm run typecheck # Vérification TypeScript
npm run test # Tests unitaires (bingo, account, authz)
npm run ci # Contrôles locaux (sans audit réseau)
npm run ci:full # Contrôles CI complets (+ audit npm)
npm run release # semantic-release (utilisé par le job CI)
# ── Base de données ────────────────────────────────────────────
npm run db:migrate:deploy # Appliquer les migrations
npm run db:migrate # Créer + appliquer une migration (dev)
npm run db:push # Synchroniser schéma (prototypage)
npm run db:seed # Charger les phrases par défaut
npm run db:studio # Interface graphique Prisma Studio
# ── Compte utilisateur ─────────────────────────────────────────
# Page /account : prénom + changement de mot de passe (tous les utilisateurs)
# ── Docker ─────────────────────────────────────────────────────
docker compose up -d # Démarrer tous les services
docker compose up -d --build # Rebuild + démarrer
docker compose down # Arrêter les services
docker compose logs -f app # Logs de l'application
docker compose logs -f postgres # Logs de la base de données
docker compose restart app # Redémarrer l'application
# ── Sauvegarde BDD ─────────────────────────────────────────────
docker exec markit_db pg_dump -U markit markit > backup.sql
docker exec -i markit_db psql -U markit markit < backup.sql