feat: initial schema migration — 9 tables + seed roles & plans

This commit is contained in:
2026-03-14 07:02:20 +01:00
parent 2f47be1305
commit 71d90eb133

View File

@@ -0,0 +1,168 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class InitialSchema1710374400000 implements MigrationInterface {
name = "InitialSchema1710374400000";
public async up(queryRunner: QueryRunner): Promise<void> {
// 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<void> {
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`);
}
}