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 (NestAppModule+app.listen()).RUN_MODE=worker: démarre un worker BullMQ viacreateApplicationContext(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ésactiveScheduleModuleviaScheduleRuntimeModule.EVENT_EMITTER_ENABLED: active/désactiveEventEmitterModuleviaEventEmitterRuntimeModule.
Où définir ces variables ?
- Référence :
.env.example(copier vers.env). - Docker compose : les fichiers
infra/docker-compose.dev.ymletinfra/docker-compose.prod.ymlpeuvent override ces variables par service (API vs worker).
C’est recommandé : par exempleQUEUE_WORKERS_ENABLED=falsesur l’API ettruesur 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
requireInvitationCodeis 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) viaNotificationsService - WebSocket event
messages:newfor instant React Query cache invalidation - Email notification to the photographer with message content and reply link
- In-app notification (
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 UserGET /api/public/booking/:userId/availability- Créneaux disponiblesPOST /api/public/booking/:userId/book- Créer une réservationPOST /api/public/booking/cancel/:token- Annuler une réservation
Endpoints protégés :
GET /api/bookings- Liste des bookings de l'utilisateurGET /api/bookings/stats- Statistiques des bookingsPATCH /api/bookings/:eventId- Mettre à jour un bookingDELETE /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émarreAppModuleet appelleapp.listen().RUN_MODE=worker: démarre unWorkerModuleminimal viaNestFactory.createApplicationContext()(pas delisten()).
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|workerQUEUE_ENABLED: active/désactive BullMQQUEUE_WORKERS_ENABLED: active/désactive le chargement des processorsQUEUE_BOARD_ENABLED: active/désactive Bull BoardSCHEDULE_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 :
TOKEN→useExisting: OrchestratorServiceOrchestratorServicedépend de services qui injectentTOKEN
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) avecuseExisting(même instance) ouuseClass(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 + fallbackEventEmitter2)ScheduleRuntimeModule(schedule on/off + appel uniqueScheduleModule.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 deQueueService)QueueClientModule(API) :QueueCoreModule+QueueService+ émission de progress via WebSocketQueueWorkerModule(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(dansbackend/)
Adding a New Module
- Create module directory:
src/my-module/ - Create files:
my-module.module.ts- NestJS modulemy-module.controller.ts- API endpointsmy-module.service.ts- Business logicdto/- Data transfer objects
- Add OpenAPI schemas in
openapi/components/ - Add OpenAPI paths in
openapi/paths/ - Run
npm run openapi:generateto generate types - Write unit tests in
__tests__/
Database Migrations
- Create migration in
infra/liquibase/changes/XXXX_description/ - Add
up.sqlanddown.sql - Update
infra/liquibase/changelog-master.yaml - 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.getOrCreateQuoteLinkto generate time-limited/clientlinks (currently used for quotes). - Tokens are stored in
client_access_tokenswith a TTL controlled byCLIENT_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 asACCEPTED.
- 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:
- Compiles TypeScript to JavaScript (
nest build) - Automatically copies the logo from
src/assets/aaperture-logo.pngtodist/assets/vianest-cli.jsonconfiguration
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:
-
GitLab CI builds with root context:
docker build -f backend/Dockerfile -t $IMAGE . -
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 frombackend/) - Copies logo from
frontend/public/assets/tosrc/assets/as fallback if needed - NestJS build process then automatically copies
src/assets/todist/assets/vianest-cli.json - If logo is not found: build continues, emails will use text fallback
- Copies entire build context to
-
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.pngin 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.