Les utilisateurs étaient stockés uniquement dans Redis (CacheUserRepository), ce qui exposait à une perte totale des comptes en cas de restart Redis, FLUSHDB ou perte du volume Docker. Les tables student_guardians et teacher_assignments référençaient des user IDs sans FK réelle. PostgreSQL devient la source de vérité via DoctrineUserRepository (DBAL, upsert ON CONFLICT). CachedUserRepository décore l'interface existante avec le pattern cache-aside : lectures Redis d'abord → miss → PostgreSQL → populate Redis ; écritures PostgreSQL d'abord → mise à jour Redis. Si Redis est indisponible, l'application continue via PostgreSQL seul. Une commande de migration (app:migrate-users-to-postgres) permet de copier les données Redis existantes vers PostgreSQL de manière idempotente.
313 lines
14 KiB
YAML
313 lines
14 KiB
YAML
# This file is the entry point to configure your own services.
|
|
# Files in the packages/ subdirectory configure your dependencies.
|
|
|
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
|
parameters:
|
|
tenant.base_domain: '%env(TENANT_BASE_DOMAIN)%'
|
|
app.url: '%env(APP_URL)%'
|
|
|
|
services:
|
|
# default configuration for services in this file
|
|
_defaults:
|
|
autowire: true # Automatically injects dependencies in your services.
|
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
|
bind:
|
|
# Bind activation tokens cache pool (7-day TTL)
|
|
Psr\Cache\CacheItemPoolInterface $activationTokensCache: '@activation_tokens.cache'
|
|
# Bind users cache pool (no TTL - persistent data)
|
|
Psr\Cache\CacheItemPoolInterface $usersCache: '@users.cache'
|
|
# Bind refresh tokens cache pool (7-day TTL)
|
|
Psr\Cache\CacheItemPoolInterface $refreshTokensCache: '@refresh_tokens.cache'
|
|
# Bind password reset tokens cache pool (1-hour TTL)
|
|
Psr\Cache\CacheItemPoolInterface $passwordResetTokensCache: '@password_reset_tokens.cache'
|
|
# Bind sessions cache pool (7-day TTL)
|
|
Psr\Cache\CacheItemPoolInterface $sessionsCache: '@sessions.cache'
|
|
# Bind student guardians cache pool (no TTL - persistent data)
|
|
Psr\Cache\CacheItemPoolInterface $studentGuardiansCache: '@student_guardians.cache'
|
|
# Bind named message buses
|
|
Symfony\Component\Messenger\MessageBusInterface $eventBus: '@event.bus'
|
|
Symfony\Component\Messenger\MessageBusInterface $commandBus: '@command.bus'
|
|
Symfony\Component\Messenger\MessageBusInterface $queryBus: '@query.bus'
|
|
|
|
# makes classes in src/ available to be used as services
|
|
# this creates a service per class whose id is the fully-qualified class name
|
|
App\:
|
|
resource: '../src/'
|
|
exclude:
|
|
- '../src/DependencyInjection/'
|
|
- '../src/Entity/'
|
|
- '../src/Kernel.php'
|
|
# Exclude Domain layers - they should be pure PHP with no framework deps
|
|
- '../src/*/Domain/'
|
|
|
|
# Domain services need to be registered explicitly to avoid framework coupling
|
|
# Example: App\Administration\Application\Command\:
|
|
# resource: '../src/Administration/Application/Command/'
|
|
|
|
# Tenant services
|
|
App\Shared\Infrastructure\Tenant\TenantResolver:
|
|
arguments:
|
|
$baseDomain: '%tenant.base_domain%'
|
|
|
|
# TenantRegistry est configuré par environnement :
|
|
# - dev: config/packages/dev/tenant.yaml (tenants de test)
|
|
# - prod: à configurer via admin ou env vars
|
|
|
|
App\Shared\Infrastructure\Tenant\Command\CreateTenantDatabaseCommand:
|
|
arguments:
|
|
$masterDatabaseUrl: '%env(DATABASE_URL)%'
|
|
|
|
App\Shared\Infrastructure\Tenant\Command\TenantMigrateCommand:
|
|
arguments:
|
|
$projectDir: '%kernel.project_dir%'
|
|
|
|
# Administration services
|
|
# Bind Repository interfaces to their implementations
|
|
App\Administration\Domain\Repository\ActivationTokenRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Redis\RedisActivationTokenRepository
|
|
|
|
App\Administration\Infrastructure\Persistence\Cache\CachedUserRepository:
|
|
arguments:
|
|
$inner: '@App\Administration\Infrastructure\Persistence\Doctrine\DoctrineUserRepository'
|
|
|
|
App\Administration\Domain\Repository\UserRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Cache\CachedUserRepository
|
|
|
|
App\Administration\Infrastructure\Console\MigrateUsersToPostgresCommand:
|
|
arguments:
|
|
$source: '@App\Administration\Infrastructure\Persistence\Cache\CacheUserRepository'
|
|
$target: '@App\Administration\Infrastructure\Persistence\Doctrine\DoctrineUserRepository'
|
|
|
|
App\Administration\Application\Port\PasswordHasher:
|
|
alias: App\Administration\Infrastructure\Security\SymfonyPasswordHasher
|
|
|
|
# Clock interface binding
|
|
App\Shared\Domain\Clock:
|
|
alias: App\Shared\Infrastructure\Clock\SystemClock
|
|
|
|
# Domain policies (need explicit registration as Domain is excluded from autowiring)
|
|
App\Administration\Domain\Policy\ConsentementParentalPolicy:
|
|
autowire: true
|
|
|
|
# Email handlers
|
|
App\Administration\Infrastructure\Messaging\SendActivationConfirmationHandler:
|
|
arguments:
|
|
$appUrl: '%app.url%'
|
|
|
|
App\Administration\Infrastructure\Messaging\SendPasswordResetEmailHandler:
|
|
arguments:
|
|
$appUrl: '%app.url%'
|
|
|
|
App\Administration\Infrastructure\Messaging\SendPasswordResetConfirmationHandler:
|
|
arguments:
|
|
$appUrl: '%app.url%'
|
|
|
|
App\Shared\Infrastructure\Tenant\TenantUrlBuilder:
|
|
arguments:
|
|
$appUrl: '%app.url%'
|
|
$baseDomain: '%tenant.base_domain%'
|
|
|
|
# Audit Logger Service (writes to append-only audit_log table)
|
|
App\Shared\Application\Port\AuditLogger:
|
|
alias: App\Shared\Infrastructure\Audit\AuditLogger
|
|
|
|
App\Shared\Infrastructure\Audit\AuditLogger:
|
|
arguments:
|
|
$appSecret: '%env(APP_SECRET)%'
|
|
|
|
# Audit log handlers (use AuditLogger to write to database)
|
|
App\Shared\Infrastructure\Audit\Handler\AuditAuthenticationHandler:
|
|
arguments:
|
|
$appSecret: '%env(APP_SECRET)%'
|
|
|
|
# JWT Authentication
|
|
App\Administration\Infrastructure\Security\JwtPayloadEnricher:
|
|
tags:
|
|
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated }
|
|
|
|
App\Administration\Infrastructure\Security\DatabaseUserProvider:
|
|
arguments:
|
|
$userRepository: '@App\Administration\Domain\Repository\UserRepository'
|
|
|
|
# Refresh Token Repository
|
|
App\Administration\Domain\Repository\RefreshTokenRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Redis\RedisRefreshTokenRepository
|
|
|
|
# Password Reset Token Repository
|
|
App\Administration\Domain\Repository\PasswordResetTokenRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Redis\RedisPasswordResetTokenRepository
|
|
|
|
# Session Repository
|
|
App\Administration\Domain\Repository\SessionRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Redis\RedisSessionRepository
|
|
|
|
# Class Repository (Story 2.1 - Gestion des classes)
|
|
App\Administration\Domain\Repository\ClassRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Doctrine\DoctrineClassRepository
|
|
|
|
# Subject Repository (Story 2.2 - Gestion des matières)
|
|
App\Administration\Domain\Repository\SubjectRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Doctrine\DoctrineSubjectRepository
|
|
|
|
# Period Configuration Repository (Story 2.3 - Gestion des périodes)
|
|
App\Administration\Domain\Repository\PeriodConfigurationRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Doctrine\DoctrinePeriodConfigurationRepository
|
|
|
|
# Grading Configuration Repository (Story 2.4 - Mode de notation)
|
|
App\Administration\Domain\Repository\GradingConfigurationRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Doctrine\DoctrineGradingConfigurationRepository
|
|
|
|
# Teacher Assignment (Story 2.8 - Affectation enseignants)
|
|
App\Administration\Domain\Repository\TeacherAssignmentRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Doctrine\DoctrineTeacherAssignmentRepository
|
|
|
|
App\Administration\Application\Port\TeacherAssignmentChecker:
|
|
alias: App\Administration\Infrastructure\Service\RepositoryTeacherAssignmentChecker
|
|
|
|
# Student Guardian Repository (Story 2.7 - Liaison parents-enfants)
|
|
App\Administration\Infrastructure\Persistence\Cache\CacheStudentGuardianRepository:
|
|
arguments:
|
|
$inner: '@App\Administration\Infrastructure\Persistence\Doctrine\DoctrineStudentGuardianRepository'
|
|
|
|
App\Administration\Domain\Repository\StudentGuardianRepository:
|
|
alias: App\Administration\Infrastructure\Persistence\Cache\CacheStudentGuardianRepository
|
|
|
|
# GradeExistenceChecker (stub until Notes module exists)
|
|
App\Administration\Application\Port\GradeExistenceChecker:
|
|
alias: App\Administration\Infrastructure\Service\NoOpGradeExistenceChecker
|
|
|
|
# ActiveRoleStore (session-scoped cache for active role switching)
|
|
App\Administration\Application\Port\ActiveRoleStore:
|
|
alias: App\Administration\Infrastructure\Service\CacheActiveRoleStore
|
|
|
|
# GeoLocation Service (null implementation - no geolocation)
|
|
App\Administration\Application\Port\GeoLocationService:
|
|
alias: App\Administration\Infrastructure\Service\NullGeoLocationService
|
|
|
|
# Password Reset Processor with rate limiters
|
|
App\Administration\Infrastructure\Api\Processor\RequestPasswordResetProcessor:
|
|
arguments:
|
|
$passwordResetByEmailLimiter: '@limiter.password_reset_by_email'
|
|
$passwordResetByIpLimiter: '@limiter.password_reset_by_ip'
|
|
|
|
# Login handlers
|
|
App\Administration\Infrastructure\Security\LoginSuccessHandler:
|
|
tags:
|
|
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccess }
|
|
|
|
App\Administration\Infrastructure\Security\LoginFailureHandler:
|
|
tags:
|
|
- { name: security.authentication_failure_handler, firewall: api_login }
|
|
|
|
# Rate Limiter (délai Fibonacci + CAPTCHA + blocage IP)
|
|
App\Shared\Infrastructure\RateLimit\LoginRateLimiter:
|
|
arguments:
|
|
$cache: '@cache.rate_limiter'
|
|
|
|
App\Shared\Infrastructure\RateLimit\LoginRateLimiterInterface:
|
|
alias: App\Shared\Infrastructure\RateLimit\LoginRateLimiter
|
|
|
|
# Rate Limit Listener (vérifie le rate limit AVANT authentification)
|
|
App\Shared\Infrastructure\RateLimit\LoginRateLimitListener:
|
|
arguments:
|
|
$rateLimiterCache: '@cache.rate_limiter'
|
|
|
|
# Turnstile CAPTCHA Validator
|
|
# failOpen: true en dev (ne pas bloquer si API down), false en prod (sécurité)
|
|
App\Shared\Infrastructure\Captcha\TurnstileValidator:
|
|
arguments:
|
|
$secretKey: '%env(TURNSTILE_SECRET_KEY)%'
|
|
$failOpen: '%env(bool:default::TURNSTILE_FAIL_OPEN)%'
|
|
|
|
App\Shared\Infrastructure\Captcha\TurnstileValidatorInterface:
|
|
alias: App\Shared\Infrastructure\Captcha\TurnstileValidator
|
|
|
|
# =============================================================================
|
|
# Monitoring & Observability (Story 1.8)
|
|
# =============================================================================
|
|
|
|
# Prometheus CollectorRegistry - uses Redis for persistence between requests
|
|
Prometheus\Storage\Redis:
|
|
factory: ['App\Shared\Infrastructure\Monitoring\PrometheusStorageFactory', 'createRedisStorage']
|
|
arguments:
|
|
$redisUrl: '%env(REDIS_URL)%'
|
|
|
|
Prometheus\CollectorRegistry:
|
|
arguments:
|
|
$storageAdapter: '@Prometheus\Storage\Redis'
|
|
|
|
# Sentry/GlitchTip PII scrubber callback
|
|
App\Shared\Infrastructure\Monitoring\SentryBeforeSendCallback: ~
|
|
|
|
# Infrastructure Health Checker - shared service for health checks (DRY)
|
|
App\Shared\Infrastructure\Monitoring\InfrastructureHealthChecker:
|
|
arguments:
|
|
$redisUrl: '%env(REDIS_URL)%'
|
|
|
|
# Interface alias for InfrastructureHealthChecker (allows testing with stubs)
|
|
App\Shared\Infrastructure\Monitoring\InfrastructureHealthCheckerInterface:
|
|
alias: App\Shared\Infrastructure\Monitoring\InfrastructureHealthChecker
|
|
|
|
# Health Check Controller - uses shared InfrastructureHealthChecker
|
|
App\Shared\Infrastructure\Monitoring\HealthCheckController: ~
|
|
|
|
# Metrics Controller - restricted to internal networks in production
|
|
App\Shared\Infrastructure\Monitoring\MetricsController:
|
|
arguments:
|
|
$appEnv: '%kernel.environment%'
|
|
|
|
# Health Metrics Collector - exposes health_check_status gauge
|
|
App\Shared\Infrastructure\Monitoring\HealthMetricsCollector: ~
|
|
|
|
# Interface alias for HealthMetricsCollector (allows testing with stubs)
|
|
App\Shared\Infrastructure\Monitoring\HealthMetricsCollectorInterface:
|
|
alias: App\Shared\Infrastructure\Monitoring\HealthMetricsCollector
|
|
|
|
# Sentry context enricher - adds tenant/user/correlation_id to error reports
|
|
# Explicitly registered to ensure HubInterface dependency is resolved
|
|
App\Shared\Infrastructure\Monitoring\SentryContextEnricher:
|
|
arguments:
|
|
$sentryHub: '@Sentry\State\HubInterface'
|
|
|
|
# Monolog processors for structured logging
|
|
App\Shared\Infrastructure\Monitoring\CorrelationIdLogProcessor:
|
|
tags:
|
|
- { name: monolog.processor }
|
|
|
|
App\Shared\Infrastructure\Monitoring\PiiScrubberLogProcessor:
|
|
tags:
|
|
- { name: monolog.processor }
|
|
|
|
# =============================================================================
|
|
# Messenger & Async (Story 2.5b)
|
|
# =============================================================================
|
|
|
|
# Fibonacci retry strategy for async transport
|
|
App\Shared\Infrastructure\Messenger\FibonacciRetryStrategy: ~
|
|
|
|
# Dead-letter alert: sends admin email when message exhausts all retries
|
|
App\Shared\Infrastructure\Messenger\DeadLetterAlertHandler:
|
|
arguments:
|
|
$adminEmail: '%env(ADMIN_ALERT_EMAIL)%'
|
|
tags:
|
|
- { name: kernel.event_listener, event: Symfony\Component\Messenger\Event\WorkerMessageFailedEvent }
|
|
|
|
# Messenger metrics middleware (handled/failed counters)
|
|
App\Shared\Infrastructure\Messenger\MessengerMetricsMiddleware: ~
|
|
|
|
# Messenger queue metrics collector (messages waiting gauge)
|
|
App\Shared\Infrastructure\Monitoring\MessengerMetricsCollector: ~
|
|
|
|
# =============================================================================
|
|
# Test environment overrides
|
|
# =============================================================================
|
|
when@test:
|
|
services:
|
|
# Use null rate limiter in test environment to avoid IP blocking during E2E tests
|
|
App\Shared\Infrastructure\RateLimit\LoginRateLimiterInterface:
|
|
alias: App\Shared\Infrastructure\RateLimit\NullLoginRateLimiter
|
|
|
|
App\Shared\Infrastructure\RateLimit\NullLoginRateLimiter:
|
|
autowire: true
|