Aller au contenu principal

Développement

Prérequis

  • Node.js 20+ (avec npm)
  • Docker (pour PostgreSQL local)
  • Git
  • Accès repo GitHub : https://github.com/fresquesystemique/LeHub

Installation locale

# 1. Clone le repo
git clone https://github.com/fresquesystemique/LeHub.git
cd LeHub

# 2. Copy .env.example et remplir variables
cp .env.example .env.local

# 3. Install dépendances
npm install

# 4. Lancer PostgreSQL en Docker
docker compose up -d postgres

# 5. Créer et appliquer les migrations
npx prisma migrate dev

# 6. (Optionnel) Seeder les données de dev
npm run db:seed

# 7. Lancer le serveur Next.js dev
npm run dev

L'app est accessible sur http://localhost:3000.

Variables d'environnement

Authentication

NomRôleExemple
NEXTAUTH_SECRETSecret NextAuth (min 32 chars)Générer avec openssl rand -base64 32
NEXTAUTH_URLURL de l'app (local)http://localhost:3000
AUTH_COOKIE_DOMAINDomaine partagé (prod seulement).fresquesystemique.org
AUTH_SESSION_COOKIE_NAMENom du cookie session__Secure-fresque.session-token

Database

NomRôleExemple
DATABASE_URLURL PostgreSQLpostgresql://<utilisateur>:<mot_de_passe>@localhost:5432/fresquesystemique
POSTGRES_PASSWORDMot de passe PostgresConfiguré dans docker-compose.yml

HelloAsso

NomRôleExemple
HELLOASSO_CLIENT_IDOAuth2 client IDObtenu depuis espace dev HelloAsso
HELLOASSO_CLIENT_SECRETOAuth2 client secretObtenu depuis espace dev HelloAsso
HELLOASSO_ORG_SLUGSlug organisation HelloAssola-fresque-systemique
HELLOASSO_ENVEnvironnement HelloAssosandbox (dev) ou production (prod)
HELLOASSO_WEBHOOK_TOKENToken sécurité webhookGénéré localement, doit matcher le webhook HelloAsso

Email (Resend)

NomRôleExemple
RESEND_API_KEYClé API Resendre_xxxxxxxx
RESEND_FROM_EMAILAdresse expéditeurnoreply@fresquesystemique.fr
RESEND_FROM_NAMENom expéditeurFresque Systémique

LinkedIn OAuth

NomRôleExemple
LINKEDIN_CLIENT_IDOAuth2 LinkedInObtenu depuis dev LinkedIn
LINKEDIN_CLIENT_SECRETOAuth2 LinkedIn secretObtenu depuis dev LinkedIn

Crons

NomRôleExemple
CRON_SECRETToken sécurité cronsGénérer avec openssl rand -base64 32

URLs croisées

NomRôleExemple
NEXT_PUBLIC_APP_URLURL publique Hub (changé à chaque requête)http://localhost:3000 (dev) ou https://hub.fresquesystemique.org (prod)
NEXT_PUBLIC_HUB_URLURL Hub (pour liens)https://hub.fresquesystemique.org
NEXT_PUBLIC_BOARD_URLURL LeBoard (pour SSO)https://board.fresquesystemique.org

Autres

NomRôleExemple
LESITE_URLURL LeSite (pour revalidation ISR)https://fresquesystemique.org
LESITE_REVALIDATE_SECRETToken revalidation ISR LeSitePartagé avec LeSite
CONTACT_NOTIFY_SECRETToken notifications contact LeSitePartagé avec LeSite
BOARD_SSO_SECRETToken SSO LeBoardPartagé avec LeBoard

Lancement

# Dev mode avec hot reload
npm run dev

# Build production
npm run build
npm run start

# Typecheck (utile pour dev)
npm run typecheck

# Lint
npm run lint

# Tests
npm test
npm run test:watch

Environnement dev.hub

Un environnement de dev partagé existe sur le VPS (routage Nginx réel).

Principe

  • Une seule base de données PostgreSQL partagée avec dev.board (même DB).
  • Stack Docker partagée (redis, postgres, nginx).
  • URLs hardcodées pour le routage.
  • Déploiement par push sur branche dev.

Accès (voir LeRunbook)

Les adresses et les accès sont dans LeRunbook.

Utilité

  • Tester des features en environnement proche-prod avant déploiement.
  • Vérifier les interactions Hub ↔ Board ↔ LeSite.
  • Tester les webhooks HelloAsso en sandbox.

Pièges de développement

1. hasProHabilitation() exige ses 3 arguments

Piège classique : la fonction hasProHabilitation(habilitationAnimation, cotisationStatus, cotisationExpiry) (dans lib/permissions.ts) n'a pas de défaut pour ses arguments. Un appel avec 2 arguments silencieusement retourne false (faux négatif invisible).

// ❌ MAUVAIS — retourne false sans erreur TypeScript
hasProHabilitation(member.habilitationAnimation, member.cotisationStatus)

// ✅ BON
hasProHabilitation(
member.habilitationAnimation,
member.cotisationStatus,
member.cotisationExpiry
)

2. Admins ont role='formateur', pas role='admin'

Toujours vérifier session.user.isAdmin pour les opérations admin, jamais seulement session.user.role.

// ❌ MAUVAIS
if (session.user.role === 'admin') { /* access admin */ }

// ✅ BON
if (session.user.isAdmin) { /* access admin */ }

3. Soft delete sur Workshop, Member, Participant

Queries doivent inclure deletedAt: null ou elles retourneront des entités "supprimées".

// ❌ MAUVAIS — inclut les workshops supprimés
await prisma.workshop.findMany({ where: { status: 'published' } })

// ✅ BON
await prisma.workshop.findMany({ where: { status: 'published', deletedAt: null } })

4. Unique composite [workshopId, email] sur Registration

Une même personne ne peut s'inscrire qu'une fois par atelier, même si elle annule. Pour retester une inscription, changer l'email de test ou changer d'atelier.

En prod, AUTH_COOKIE_DOMAIN=.fresquesystemique.org partage le cookie de session avec LeSite (pont auth). Localement, laisser vide — sinon impossible de tester login sur localhost:3000.

6. Timezone dans le code

Utiliser toujours lib/workshop-form.ts pour les calculs timezone (fct datetimeLocalToUTC()). Le stockage en DB est UTC, les formes locales convertissent.

import { datetimeLocalToUTC } from '@/lib/workshop-form'

const utcDate = datetimeLocalToUTC('2025-12-25 19:00', 'Europe/Paris')

7. Les templates emails sont codés (pas d'édition DB)

Les 14 templates emails sont durcodés dans lib/email-template-defaults.ts (blocs, charte, variables). Pour modifier un template, éditer le fichier source et redéployer. Aucune interface admin pour le contenu.

8. CSP en prod seulement

Un domaine externe absent de connect-src ne génère pas d'erreur avec npm run dev. Tester avec un build prod local :

npm run build
npm run start
# Puis vérifier DevTools pour les blocages CSP

9. Relation Board (propriété LeBoard)

Les modèles Board, CardPlacement, Connection, AnimatorSession, Lot, BoardLotDistribution, ModelCardTitle appartiennent à LeBoard (repo séparé). Le Hub y a accès en écriture directe (dans la même DB PostgreSQL).

Conséquence : les migrations schema pour ces modèles sont gérées par LeBoard. Le Hub ne doit jamais modifier le schema de ces modèles sans synchronisation LeBoard.

10. Path alias @/* vers racine repo

Dans les imports, utiliser l'alias :

// ✅ BON
import { prisma } from '@/lib/prisma'
import { sendEmail } from '@/lib/email'

// ❌ MAUVAIS
import { prisma } from '../../../lib/prisma'

L'alias est défini dans tsconfig.json et résout depuis la racine du repo.

11. Rate limiting sur les endpoints publics

  • /api/checkout : 10 requêtes / 10 minutes par IP.
  • /api/public/registrations/[token]/cancel : 10 tentatives / 15 minutes par IP.

Utilisé pour prévenir les abus. Tester en dev avec des IPs différentes si le limit est declenché.

12. Prisma generate après schéma change

Après modification manuelle de prisma/schema.prisma, lancer :

npx prisma generate

Sinon les changements ne sont pas reflétés dans le client généré.