Aller au contenu principal

Backend Documentation

Overview

The backend is built with NestJS following Clean Architecture and Domain Driven Design principles.

Project Structure

backend/src/
├── __tests__/ # Unit tests
├── _generated/ # Generated types from OpenAPI
├── auth/ # Authentication module
├── password-reset/ # Password reset module
├── common/ # Shared utilities
├── client-messages/ # Client portal messaging
├── contacts/ # Contact management
├── contact-files/ # Contact file management
├── db/ # Database service
├── lead-forms/ # Customizable contact forms
├── duplicates/ # Duplicate management
├── extraction/ # OCR and LLM extraction
├── notifications/ # Notification system
├── session-files/ # Session file management
├── sessions/ # Session management
├── storage/ # File storage (R2)
├── stripe/ # Stripe integration (subscriptions, payments)
├── tags/ # Tag system
├── users/ # User management
├── analytics/ # Analytics and reporting
├── audit/ # Audit logging
├── availability/ # Availability slots management
├── booking/ # Public booking system
├── cache/ # Redis cache
├── export/ # Data export (CSV, Excel, JSON)
├── gdpr/ # GDPR/RGPD compliance module
├── google-calendar/ # Google Calendar integration
├── pdf/ # PDF generation service
├── rate-limiting/ # API rate limiting
└── websocket/ # WebSocket gateway

Key Modules

Authentication (auth/)

  • JWT token generation and validation
  • Google OAuth integration
  • User guards (ActiveUserGuard)
  • Authentication helpers
  • Password reset endpoints

See: backend/src/auth/README.md for detailed documentation

Password Reset (password-reset/)

  • Secure password reset token generation
  • Token encryption and storage
  • Token validation with atomic operations
  • Automatic cleanup of expired tokens
  • Email-based password reset flow

See: backend/src/password-reset/README.md for detailed documentation

Database (db/)

  • Kysely query builder
  • Type-safe database access
  • Connection management

See: backend/src/db/README.md for detailed documentation

Storage (storage/)

  • Cloudflare R2 integration
  • File upload/download
  • File validation

See: backend/src/storage/README.md for detailed documentation

ApiModule : réduire les imports via des “group modules”

backend/src/api.module.ts ne liste plus des dizaines de modules métier directement. À la place, il importe quelques modules “groupes” dans backend/src/api/ :

  • ApiPlatformModule : auth/users/permissions/communication/websocket…
  • ApiDomainModule : modules métier principaux (sessions/contacts/workflows…)
  • ApiDocumentsModule : documents (quotes/invoices/emails/export…)
  • ApiIntegrationsModule : intégrations externes (Google/Notion/OpenAI/Stripe/PayPal/Discord…)
  • ApiOpsModule : cross-cutting / ops (backup/migrations/search/agent/storage…)

Ces group modules ré-exportent leurs imports pour que les controllers déclarés dans ApiModule (ex: AppController) puissent résoudre leurs dépendances sans devoir importer chaque module “un par un”.

En complément, les controllers “root” qui tirent beaucoup de dépendances transitives (ex: AppController) sont isolés dans ApiRootControllersModule (backend/src/api/api-root-controllers.module.ts). ApiModule l’importe, mais garde ses controllers “feature” séparés.

Variables d’environnement runtime (API vs Worker)

Ces flags contrôlent ce qui est chargé au boot (et donc le risque de cycles DI / temps de démarrage).

RUN_MODE

Le même entrypoint backend/src/main.ts gère les deux modes :

  • RUN_MODE=api : démarre l’API HTTP (Nest AppModule + app.listen()).
  • RUN_MODE=worker : démarre un worker BullMQ via createApplicationContext(WorkerModule) (pas de serveur HTTP).

Queue (BullMQ)

  • QUEUE_ENABLED : active/désactive la queue (et donc BullMQ).
  • QUEUE_WORKERS_ENABLED : active le chargement des processors (typiquement uniquement côté worker).
  • QUEUE_BOARD_ENABLED : active le dashboard Bull Board (uniquement utile côté API).

Features optionnelles

  • SCHEDULE_ENABLED : active/désactive ScheduleModule via ScheduleRuntimeModule.
  • EVENT_EMITTER_ENABLED : active/désactive EventEmitterModule via EventEmitterRuntimeModule.

Où définir ces variables ?

  • Référence : .env.example (copier vers .env).
  • Docker compose : les fichiers infra/docker-compose.dev.yml et infra/docker-compose.prod.yml peuvent override ces variables par service (API vs worker).
    C’est recommandé : par exemple QUEUE_WORKERS_ENABLED=false sur l’API et true sur le worker.

Users (users/)

  • User CRUD operations
  • Profile management
  • Company information
  • Password management
  • Email validation

Invitations (invitations/)

  • Invitation balance management (5 invitations by default, can earn more via progression)
  • Invitation creation with token and unique 6-character code
  • Invitation acceptance via token (email link) or code (manual entry)
  • Integration with registration (email and Google OAuth)
  • Automatic friendship creation when invitation is accepted
  • Referrer rewards (XP, coins, additional invitations)

Registration Workflow:

  • Email registration: Token or code can be provided, invitation is validated before account creation
  • Google OAuth registration: If requireInvitationCode is enabled in admin settings, users must enter invitation code before completing Google OAuth flow
  • Invitation is only accepted after all validations pass (including Gmail registration check)

See: backend/src/invitations/README.md for detailed documentation

Sessions (sessions/)

  • Session CRUD operations
  • Session contacts management
  • Session providers management
  • Session tags

Client Messages (client-messages/)

  • Client-to-photographer messaging via the client portal
  • Photographer-to-client messaging from the contact view
  • Real-time notifications when a client sends a message:
    • In-app notification (CLIENT_MESSAGE_RECEIVED) via NotificationsService
    • WebSocket event messages:new for instant React Query cache invalidation
    • Email notification to the photographer with message content and reply link

Contacts (contacts/)

  • Contact CRUD operations
  • Location data management
  • Contact-session relationships

See: backend/src/contacts/README.md for detailed documentation

Lead Forms (lead-forms/)

  • Customizable contact form management
  • Dynamic field configuration (TEXT, EMAIL, PHONE, TEXTAREA, DATE, SELECT, NUMBER, CHECKBOX)
  • Form styling customization
  • Public form submission endpoints
  • Automatic email notifications (recap to owner, auto-reply to submitter)
  • Contact creation from submissions
  • Workflow integration
  • Google Analytics support

See: backend/src/lead-forms/README.md for detailed documentation

Tags (tags/)

  • Tag CRUD operations
  • Reusable tagging system

Duplicates (duplicates/)

  • Duplicate registration management
  • Duplicate status tracking

Extraction (extraction/)

  • OCR text extraction
  • LLM-based structured data extraction

See: backend/src/extraction/README.md for detailed documentation

WebSocket (websocket/)

  • Real-time communication
  • Event-based messaging
  • User-specific notifications

See: backend/src/websocket/README.md for detailed documentation

Quotes & Invoices (quotes-invoices/)

  • Quote and invoice management
  • PDF generation
  • Email sending via Gmail API
  • Scheduled emails
  • Variable replacement in email templates

See: backend/src/quotes-invoices/README.md for detailed documentation

Stripe (stripe/)

  • Subscription management (FREE, BASIC, PRO plans)
  • Payment method collection via SetupIntent
  • Payment method attachment and management
  • Invoice payment processing
  • Webhook event handling
  • Payment history tracking

See: backend/src/stripe/README.md for detailed documentation

Environment Variables:

  • STRIPE_SECRET_KEY - Stripe secret key (required)
  • STRIPE_WEBHOOK_SECRET - Webhook signing secret (required for webhooks)
  • STRIPE_PUBLISHABLE_KEY - Stripe publishable key (for backend, optional)
  • VITE_STRIPE_PUBLISHABLE_KEY - Stripe publishable key for frontend (required for payment forms)

Note: Stripe Price IDs are managed via the admin interface. Go to Admin Panel > Subscription Plans to configure Price IDs for each plan.

Scheduled Emails (scheduled-emails/)

  • Deferred email sending
  • Email scheduling and processing
  • Annual re-sending
  • Attachment handling

See: backend/src/scheduled-emails/README.md for detailed documentation

Analytics (analytics/)

  • Dashboard statistics (revenue, quotes, sessions, invoices)
  • Revenue analytics with breakdown by status and period
  • Conversion analytics (quote to invoice conversion rates)
  • Session analytics with status and period breakdown
  • PDF report generation and export

See: backend/src/analytics/README.md for detailed documentation

Audit (audit/)

  • Comprehensive audit logging of user actions
  • Data modification tracking
  • Entity history and traceability
  • Admin audit log interface

See: backend/src/audit/README.md for detailed documentation

Cache (cache/)

  • Redis-based caching for performance
  • Permission caching
  • Frequently accessed data caching
  • Cache invalidation strategies

See: backend/src/cache/README.md for detailed documentation

Export (export/)

  • Data export in multiple formats (CSV, Excel, JSON)
  • GDPR-compliant data export
  • Bulk export of sessions, contacts, quotes, invoices

See: backend/src/export/README.md for detailed documentation

GDPR (gdpr/)

  • GDPR/RGPD compliance module
  • User consent management (NECESSARY, FUNCTIONAL, ANALYTICS, MARKETING, THIRD_PARTY)
  • GDPR request handling (ACCESS, RECTIFICATION, ERASURE, PORTABILITY, OBJECTION, RESTRICTION)
  • Data export and portability
  • Secure data deletion with audit logs
  • Email verification for sensitive operations
  • Protection against race conditions and security vulnerabilities

See: backend/src/gdpr/README.md for detailed documentation

PDF (pdf/)

  • PDF generation from HTML content
  • Support for quotes, invoices, contracts, and analytics reports
  • Automatic upload to R2 storage
  • Signed URL generation for secure downloads

See: backend/src/pdf/README.md for detailed documentation

Rate Limiting (rate-limiting/)

  • API protection against abuse
  • Configurable rate limits per endpoint
  • Redis-based rate limiting
  • IP and user-based limiting

See: backend/src/rate-limiting/README.md for detailed documentation

Availability (availability/)

  • Availability slot management
  • Recurring slot support
  • Slot availability checking
  • Expansion of recurring slots into individual occurrences

Booking (booking/)

  • Public booking system : Permet aux clients de réserver des créneaux via une interface publique
  • BookingController : Gère les réservations publiques (création, annulation)
  • BookingsController : Gère les bookings pour les utilisateurs authentifiés (liste, statistiques, édition, suppression)
  • BookingCancelTokensService : Gère les tokens d'annulation sécurisés
  • Création d'événements Google Calendar avec préfixe [MEETING]
  • Filtrage des créneaux disponibles (exclut les créneaux déjà réservés)
  • Expansion des créneaux récurrents en occurrences individuelles
  • Cache temporaire pour gérer le délai de propagation de Google Calendar
  • Notifications email et in-app pour les réservations et annulations

Endpoints publics :

  • GET /api/public/booking/:userId/info - Informations publiques du User
  • GET /api/public/booking/:userId/availability - Créneaux disponibles
  • POST /api/public/booking/:userId/book - Créer une réservation
  • POST /api/public/booking/cancel/:token - Annuler une réservation

Endpoints protégés :

  • GET /api/bookings - Liste des bookings de l'utilisateur
  • GET /api/bookings/stats - Statistiques des bookings
  • PATCH /api/bookings/:eventId - Mettre à jour un booking
  • DELETE /api/bookings/:eventId - Supprimer un booking

See: docs/BOOKING_SYSTEM.md for detailed documentation

Google Calendar (google-calendar/)

  • Google Calendar API integration
  • Event creation, update, and deletion
  • Calendar synchronization
  • Google Meet link generation
  • Booking event filtering

Development Guidelines

Runtime (API vs Worker) — règles de fonctionnement

En développement, le backend tourne en 2 processus distincts :

  • API: process HTTP NestJS qui expose les routes (listen()).
  • Worker: process NestJS sans serveur HTTP qui exécute les BullMQ processors.

Pourquoi séparer ?

  • Performance / boot time: le worker n’a pas besoin de charger tout AppModule (Search/Agent/etc.).
  • Stabilité: les processors (jobs longs, retries) ne doivent pas impacter le serveur HTTP.
  • Évolutivité: possibilité de scaler les workers horizontalement sans toucher à l’API.

Entrypoint unique

Le point d’entrée reste backend/src/main.ts, piloté par RUN_MODE:

  • RUN_MODE=api (défaut): démarre AppModule et appelle app.listen().
  • RUN_MODE=worker: démarre un WorkerModule minimal via NestFactory.createApplicationContext() (pas de listen()).

AppModule “thin” + ApiModule

  • AppModule : infra/runtime (config, logger, db, cache, runtime modules comme queue/event-emitter/schedule) + garde global.
  • ApiModule : controllers HTTP + modules métier.

Note NestJS : un module importé ne “voit” pas automatiquement les providers du module parent. Les briques infra partagées (ex: DbModule) doivent être globales ou importées explicitement dans les modules qui en ont besoin.

Docker Compose (dev)

Le fichier infra/docker-compose.dev.yml lance :

  • service api: RUN_MODE=api, QUEUE_WORKERS_ENABLED=false (enqueue uniquement)
  • service worker: RUN_MODE=worker, QUEUE_WORKERS_ENABLED=true (exécute les jobs)

Variables d’environnement “runtime”

  • RUN_MODE: api | worker
  • QUEUE_ENABLED: active/désactive BullMQ
  • QUEUE_WORKERS_ENABLED: active/désactive le chargement des processors
  • QUEUE_BOARD_ENABLED: active/désactive Bull Board
  • SCHEDULE_ENABLED: active/désactive @nestjs/schedule (cron jobs)
  • EVENT_EMITTER_ENABLED: active/désactive @nestjs/event-emitter (avec fallback)

Règles NestJS pour éviter les blocages de bootstrap (DI)

1) Ne pas binder un token vers un “orchestrateur” si ce token est ré-injecté en aval

Cas à éviter :

  • TOKENuseExisting: OrchestratorService
  • OrchestratorService dépend de services qui injectent TOKEN

Résultat: cycle DI qui peut faire “pendre” le bootstrap.

Règle : un token d’interface (*_TOKEN) doit pointer vers :

  • une implémentation leaf (bas niveau), ou
  • un adapter léger (lookup/service minimal) qui n’a pas de dépendances “haut niveau”.

1bis) Éviter les tokens “string” (ex: "SomeService")

Les tokens string masquent facilement des duplications de providers et rendent les cycles DI plus difficiles à détecter.

Règle : préférer :

  • l’injection directe de la classe (quand c’est safe), ou
  • un token d’interface Symbol (*_TOKEN) avec useExisting (même instance) ou useClass (impl leaf).

2) Préférer les modules “runtime” pour les dépendances optionnelles

Pour les briques qui peuvent être désactivées sans casser le boot :

  • QueueRuntimeModule (queue on/off)
  • EventEmitterRuntimeModule (event emitter on/off + fallback EventEmitter2)
  • ScheduleRuntimeModule (schedule on/off + appel unique ScheduleModule.forRoot())

Ces modules doivent rester globalement injectables et ne pas imposer de side effects coûteux au démarrage.

2bis) Queue : séparer “enqueue” vs “process”

Pour éviter que le worker charge des dépendances lourdes (WebSocket/HTTP), la queue est découpée en :

  • QueueCoreModule : BullMQ connection + registerQueue (pas de QueueService)
  • QueueClientModule (API) : QueueCoreModule + QueueService + émission de progress via WebSocket
  • QueueWorkerModule (Worker) : QueueCoreModule + QueueService + processors “base” + progress emitter no-op

Le choix se fait via QueueRuntimeModule.register() et les env vars QUEUE_ENABLED / QUEUE_WORKERS_ENABLED.

3) Worker minimal: éviter AppModule

Le worker doit importer uniquement :

  • les modules infra nécessaires (config/logger/db/cache/timeout…)
  • QueueRuntimeModule
  • les ProcessorModules (1 module par processor, dépendances strictes)

Voir aussi : Queue System.

Smoke tests de boot (CI)

Pour attraper les cycles DI / providers manquants tôt, on exécute un boot-check sur code compilé :

  • backend/src/scripts/boot-check.ts
  • commande : npm run test:boot (dans backend/)

Adding a New Module

  1. Create module directory: src/my-module/
  2. Create files:
    • my-module.module.ts - NestJS module
    • my-module.controller.ts - API endpoints
    • my-module.service.ts - Business logic
    • dto/ - Data transfer objects
  3. Add OpenAPI schemas in openapi/components/
  4. Add OpenAPI paths in openapi/paths/
  5. Run npm run openapi:generate to generate types
  6. Write unit tests in __tests__/

Database Migrations

  1. Create migration in infra/liquibase/changes/XXXX_description/
  2. Add up.sql and down.sql
  3. Update infra/liquibase/changelog-master.yaml
  4. Run migrations: infra/migrate-improved.sh

API Endpoints

All endpoints require:

  • JWT authentication (AuthGuard('jwt'))
  • Active user status (ActiveUserGuard) for most operations
  • Resource ownership verification for modifications

Validation

  • Use Zod schemas for DTO validation
  • Validate in controllers before service calls
  • Return clear error messages

Logging

  • Use structured logging with scopes and emojis
  • Log all important operations
  • Include user context in logs

Error Handling

  • Use NestJS exceptions (BadRequestException, ForbiddenException, etc.)
  • Return consistent error format
  • Log errors with context

Client access tokens

  • Use ClientAccessService.getOrCreateQuoteLink to generate time-limited /client links (currently used for quotes).
  • Tokens are stored in client_access_tokens with a TTL controlled by CLIENT_ACCESS_TOKEN_TTL_HOURS (default 72h).
  • Public resolver endpoints:
    • GET /client-access?token=... returns a sanitized payload (quote, session summary, owner profile).
    • GET /client-access/quotes/{quoteId}?token=... (ensures token matches the quote).
    • POST /client-access/quotes/{quoteId}/accept?token=... to mark a quote as ACCEPTED.
  • Never embed dashboard URLs in emails; always rely on the generated client link.

Build Process

Development Build

For local development, run:

npm run build

This command:

  1. Compiles TypeScript to JavaScript (nest build)
  2. Automatically copies the logo from src/assets/aaperture-logo.png to dist/assets/ via nest-cli.json configuration

The logo must be present in backend/src/assets/aaperture-logo.png and is automatically included in the build (see SYSTEM_EMAIL_TEMPLATES.md).

Production Build (Docker)

The Dockerfile is configured to handle builds from multiple contexts:

  1. GitLab CI builds with root context:

    docker build -f backend/Dockerfile -t $IMAGE .
  2. Dockerfile uses a shell script to detect build context and copy files accordingly:

    • Copies entire build context to /build-context/
    • Detects if files are in backend/ (build from root) or current directory (build from backend/)
    • Copies logo from frontend/public/assets/ to src/assets/ as fallback if needed
    • NestJS build process then automatically copies src/assets/ to dist/assets/ via nest-cli.json
    • If logo is not found: build continues, emails will use text fallback
  3. Deploy script (infra/deploy.sh) also uses root context when building locally

Logo Management

The Aaperture logo must be available at runtime for email templates. The system:

  • Searches for aaperture-logo.png in multiple locations
  • Embeds it as a CID attachment in emails (never uses external URLs)
  • Falls back to text "Aaperture" if logo not found

See SYSTEM_EMAIL_TEMPLATES.md for complete documentation.

Testing

Run tests:

npm run test

Write tests for:

  • Service methods
  • Controller endpoints
  • Business logic
  • Edge cases

Use in-memory implementations for repositories in tests.

Environment Variables

See .env.example for required variables:

  • Database connection
  • JWT configuration
  • Google OAuth
  • Cloudflare R2
  • Timeout configuration - See ENV_VARIABLES_TIMEOUT.md for all timeout-related variables

Note: OpenAI API keys are no longer configured via environment variables. Each user configures their own API key in the application's connections settings. The keys are encrypted and stored in the database.

Timeout Management: All timeout configurations are optional and have sensible defaults. See TIMEOUT_MANAGEMENT.md for details.