feat: Persister les utilisateurs en PostgreSQL avec cache-aside Redis
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.
This commit is contained in:
71
backend/migrations/Version20260214100000.php
Normal file
71
backend/migrations/Version20260214100000.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Creates the users table in PostgreSQL.
|
||||
*
|
||||
* Users were previously stored only in Redis (CacheUserRepository).
|
||||
* This migration establishes PostgreSQL as the source of truth,
|
||||
* enabling referential integrity, SQL queries, and data durability.
|
||||
*/
|
||||
final class Version20260214100000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create users table in PostgreSQL for durable user storage';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
first_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
last_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
roles JSONB NOT NULL DEFAULT '[]',
|
||||
hashed_password TEXT,
|
||||
statut VARCHAR(30) NOT NULL DEFAULT 'pending',
|
||||
school_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
date_naissance DATE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
activated_at TIMESTAMPTZ,
|
||||
invited_at TIMESTAMPTZ,
|
||||
blocked_at TIMESTAMPTZ,
|
||||
blocked_reason TEXT,
|
||||
consentement_parent_id UUID,
|
||||
consentement_eleve_id UUID,
|
||||
consentement_date TIMESTAMPTZ,
|
||||
consentement_ip VARCHAR(45),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(tenant_id, email)
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX idx_users_tenant ON users(tenant_id)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX idx_users_tenant_statut ON users(tenant_id, statut)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX idx_users_created_at ON users(created_at)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE IF EXISTS users
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user