From 71d90eb1339a7fafb9c7568a8836c593898af948 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Sat, 14 Mar 2026 07:02:20 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20initial=20schema=20migration=20?= =?UTF-8?q?=E2=80=94=209=20tables=20+=20seed=20roles=20&=20plans?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/1710374400000-InitialSchema.ts | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 backend/src/migrations/1710374400000-InitialSchema.ts diff --git a/backend/src/migrations/1710374400000-InitialSchema.ts b/backend/src/migrations/1710374400000-InitialSchema.ts new file mode 100644 index 0000000..d3e013a --- /dev/null +++ b/backend/src/migrations/1710374400000-InitialSchema.ts @@ -0,0 +1,168 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class InitialSchema1710374400000 implements MigrationInterface { + name = "InitialSchema1710374400000"; + + public async up(queryRunner: QueryRunner): Promise { + // roles + await queryRunner.query(` + CREATE TABLE roles ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + slug VARCHAR(50) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // subscription_plans + await queryRunner.query(` + CREATE TABLE subscription_plans ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + slug VARCHAR(50) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + level TINYINT UNSIGNED NOT NULL DEFAULT 0, + priceInCents INT UNSIGNED NOT NULL DEFAULT 0, + features JSON NULL, + isActive BOOLEAN NOT NULL DEFAULT TRUE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // users + await queryRunner.query(` + CREATE TABLE users ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + superOAuthId VARCHAR(255) NOT NULL UNIQUE, + email VARCHAR(255) NULL, + nickname VARCHAR(100) NOT NULL, + isActive BOOLEAN NOT NULL DEFAULT TRUE, + createdAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updatedAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + INDEX idx_users_superOAuthId (superOAuthId), + INDEX idx_users_email (email) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // user_roles (pivot) + await queryRunner.query(` + CREATE TABLE user_roles ( + userId VARCHAR(36) NOT NULL, + roleId VARCHAR(36) NOT NULL, + PRIMARY KEY (userId, roleId), + CONSTRAINT fk_user_roles_user FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT fk_user_roles_role FOREIGN KEY (roleId) REFERENCES roles(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // user_subscriptions + await queryRunner.query(` + CREATE TABLE user_subscriptions ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + userId VARCHAR(36) NOT NULL, + planId VARCHAR(36) NOT NULL, + status ENUM('active','expired','cancelled','trial') NOT NULL DEFAULT 'active', + startsAt DATETIME NOT NULL, + endsAt DATETIME NULL, + createdAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + INDEX idx_user_subscriptions_userId (userId), + INDEX idx_user_subscriptions_status (status), + CONSTRAINT fk_user_subscriptions_user FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE, + CONSTRAINT fk_user_subscriptions_plan FOREIGN KEY (planId) REFERENCES subscription_plans(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // videos + await queryRunner.query(` + CREATE TABLE videos ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description TEXT NULL, + thumbnailUrl VARCHAR(500) NULL, + duration INT UNSIGNED NULL, + storageType ENUM('youtube','s3','local','external') NOT NULL, + storageKey VARCHAR(500) NOT NULL, + requiredLevel TINYINT UNSIGNED NOT NULL DEFAULT 0, + isPublished BOOLEAN NOT NULL DEFAULT FALSE, + publishedAt DATETIME NULL, + createdAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updatedAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + INDEX idx_videos_requiredLevel (requiredLevel), + INDEX idx_videos_isPublished (isPublished) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // playlists + await queryRunner.query(` + CREATE TABLE playlists ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + ownerId VARCHAR(36) NOT NULL, + title VARCHAR(255) NOT NULL, + description TEXT NULL, + visibility ENUM('private','shared','public') NOT NULL DEFAULT 'private', + createdAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updatedAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + INDEX idx_playlists_ownerId (ownerId), + INDEX idx_playlists_visibility (visibility), + CONSTRAINT fk_playlists_owner FOREIGN KEY (ownerId) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // playlist_videos (pivot ordonné) + await queryRunner.query(` + CREATE TABLE playlist_videos ( + playlistId VARCHAR(36) NOT NULL, + videoId VARCHAR(36) NOT NULL, + position INT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (playlistId, videoId), + INDEX idx_playlist_videos_position (playlistId, position), + CONSTRAINT fk_playlist_videos_playlist FOREIGN KEY (playlistId) REFERENCES playlists(id) ON DELETE CASCADE, + CONSTRAINT fk_playlist_videos_video FOREIGN KEY (videoId) REFERENCES videos(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // playlist_shares + await queryRunner.query(` + CREATE TABLE playlist_shares ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + playlistId VARCHAR(36) NOT NULL, + userId VARCHAR(36) NOT NULL, + permission ENUM('view','edit') NOT NULL DEFAULT 'view', + status ENUM('pending','active','revoked') NOT NULL DEFAULT 'pending', + createdAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updatedAt DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + UNIQUE KEY uq_playlist_shares (playlistId, userId), + INDEX idx_playlist_shares_userId (userId), + INDEX idx_playlist_shares_status (status), + CONSTRAINT fk_playlist_shares_playlist FOREIGN KEY (playlistId) REFERENCES playlists(id) ON DELETE CASCADE, + CONSTRAINT fk_playlist_shares_user FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `); + + // Données initiales — plans et rôles + await queryRunner.query(` + INSERT INTO roles (id, slug, name) VALUES + (UUID(), 'user', 'Utilisateur'), + (UUID(), 'moderator', 'Modérateur'), + (UUID(), 'admin', 'Administrateur'), + (UUID(), 'super_admin', 'Super Administrateur') + `); + + await queryRunner.query(` + INSERT INTO subscription_plans (id, slug, name, level, priceInCents, features) VALUES + (UUID(), 'free', 'Gratuit', 0, 0, JSON_OBJECT('maxPlaylists', 3)), + (UUID(), 'basic', 'Basic', 1, 499, JSON_OBJECT('maxPlaylists', 20)), + (UUID(), 'pro', 'Pro', 2, 999, JSON_OBJECT('maxPlaylists', 999)), + (UUID(), 'enterprise', 'Enterprise', 3, 4999, JSON_OBJECT('maxPlaylists', 999, 'whiteLabel', true)) + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE IF EXISTS playlist_shares`); + await queryRunner.query(`DROP TABLE IF EXISTS playlist_videos`); + await queryRunner.query(`DROP TABLE IF EXISTS playlists`); + await queryRunner.query(`DROP TABLE IF EXISTS videos`); + await queryRunner.query(`DROP TABLE IF EXISTS user_subscriptions`); + await queryRunner.query(`DROP TABLE IF EXISTS user_roles`); + await queryRunner.query(`DROP TABLE IF EXISTS users`); + await queryRunner.query(`DROP TABLE IF EXISTS subscription_plans`); + await queryRunner.query(`DROP TABLE IF EXISTS roles`); + } +}