feat: initial schema migration — 9 tables + seed roles & plans
This commit is contained in:
168
backend/src/migrations/1710374400000-InitialSchema.ts
Normal file
168
backend/src/migrations/1710374400000-InitialSchema.ts
Normal 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`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user