Files
GameTime/server/migrations/0001_initial_schema.sql
Blomios 79d994bdd7 feat(server): schéma PostgreSQL sync-ready (ticket #49)
Ajoute la migration initiale (migrations/0001_initial_schema.sql,
incluant la table users nécessaire à l'adapter d'auth du ticket #48),
le runner de migration (bin/migrate.dart) et l'accès Postgres
(infrastructure/postgres/postgres_database.dart). dart pub get OK,
dart analyze clean, dart test vert (le test PostgreSQL réel est skip
faute de TEST_DATABASE_URL/Docker disponible dans ce sandbox ; SQL de
migration revu manuellement).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 10:05:24 +02:00

115 lines
3.8 KiB
PL/PgSQL

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS citext;
CREATE TABLE IF NOT EXISTS users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email citext NOT NULL UNIQUE,
password_hash text NOT NULL,
display_name text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
disabled_at timestamptz,
CONSTRAINT users_email_not_blank CHECK (length(trim(email::text)) > 0),
CONSTRAINT users_password_hash_not_blank CHECK (length(trim(password_hash)) > 0)
);
CREATE TABLE IF NOT EXISTS auth_sessions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash text NOT NULL UNIQUE,
issued_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz NOT NULL,
revoked_at timestamptz,
user_agent text,
device_label text,
CONSTRAINT auth_sessions_token_hash_not_blank CHECK (length(trim(token_hash)) > 0),
CONSTRAINT auth_sessions_expires_after_issued CHECK (expires_at > issued_at)
);
CREATE TABLE IF NOT EXISTS synced_resources (
server_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
resource_type text NOT NULL,
client_id text NOT NULL,
payload_json jsonb NOT NULL,
schema_version integer NOT NULL,
client_updated_at timestamptz NOT NULL,
server_updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
origin_device_id text,
CONSTRAINT synced_resources_type_check CHECK (
resource_type IN (
'exercise',
'program',
'workoutTemplate',
'workoutHistory',
'mediaAsset'
)
),
CONSTRAINT synced_resources_client_id_not_blank CHECK (length(trim(client_id)) > 0),
CONSTRAINT synced_resources_schema_version_positive CHECK (schema_version > 0),
CONSTRAINT synced_resources_owner_type_client_unique UNIQUE (
owner_user_id,
resource_type,
client_id
)
);
CREATE INDEX IF NOT EXISTS idx_synced_resources_owner_type_updated
ON synced_resources (owner_user_id, resource_type, server_updated_at);
CREATE INDEX IF NOT EXISTS idx_synced_resources_owner_updated
ON synced_resources (owner_user_id, server_updated_at);
CREATE OR REPLACE FUNCTION set_synced_resources_server_updated_at()
RETURNS trigger AS $$
BEGIN
NEW.server_updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_synced_resources_set_server_updated_at
ON synced_resources;
CREATE TRIGGER trg_synced_resources_set_server_updated_at
BEFORE UPDATE ON synced_resources
FOR EACH ROW
EXECUTE FUNCTION set_synced_resources_server_updated_at();
CREATE TABLE IF NOT EXISTS shares (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
sender_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
resource_type text NOT NULL,
payload_json jsonb NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
revoked_at timestamptz,
CONSTRAINT shares_resource_type_check CHECK (
resource_type IN ('program', 'workoutTemplate')
)
);
CREATE TABLE IF NOT EXISTS share_recipients (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
share_id uuid NOT NULL REFERENCES shares(id) ON DELETE CASCADE,
recipient_user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status text NOT NULL DEFAULT 'pending',
responded_at timestamptz,
CONSTRAINT share_recipients_status_check CHECK (
status IN ('pending', 'accepted', 'declined', 'revoked')
),
CONSTRAINT share_recipients_share_user_unique UNIQUE (
share_id,
recipient_user_id
)
);
CREATE INDEX IF NOT EXISTS idx_auth_sessions_user_id
ON auth_sessions (user_id);
CREATE INDEX IF NOT EXISTS idx_shares_sender_user_id
ON shares (sender_user_id);
CREATE INDEX IF NOT EXISTS idx_share_recipients_recipient_status
ON share_recipients (recipient_user_id, status);