feat: TypeORM entities — User, Role, SubscriptionPlan, Video, Playlist + relations
This commit is contained in:
47
backend/src/entities/Playlist.ts
Normal file
47
backend/src/entities/Playlist.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
Entity, PrimaryGeneratedColumn, Column,
|
||||
ManyToOne, OneToMany, JoinColumn,
|
||||
CreateDateColumn, UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
import { User } from "./User";
|
||||
import { PlaylistVideo } from "./PlaylistVideo";
|
||||
import { PlaylistShare } from "./PlaylistShare";
|
||||
|
||||
export type PlaylistVisibility = "private" | "shared" | "public";
|
||||
|
||||
@Entity("playlists")
|
||||
export class Playlist {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
ownerId!: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
title!: string;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
description!: string | null;
|
||||
|
||||
// private → visible uniquement par le propriétaire
|
||||
// shared → visible par les utilisateurs invités via PlaylistShare
|
||||
// public → visible par tous
|
||||
@Column({ type: "enum", enum: ["private", "shared", "public"], default: "private" })
|
||||
visibility!: PlaylistVisibility;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.playlists, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "ownerId" })
|
||||
owner!: User;
|
||||
|
||||
@OneToMany(() => PlaylistVideo, (pv) => pv.playlist)
|
||||
playlistVideos!: PlaylistVideo[];
|
||||
|
||||
@OneToMany(() => PlaylistShare, (share) => share.playlist)
|
||||
shares!: PlaylistShare[];
|
||||
}
|
||||
46
backend/src/entities/PlaylistShare.ts
Normal file
46
backend/src/entities/PlaylistShare.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
Entity, PrimaryGeneratedColumn, Column,
|
||||
ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
import { Playlist } from "./Playlist";
|
||||
import { User } from "./User";
|
||||
|
||||
export type SharePermission = "view" | "edit";
|
||||
export type ShareStatus = "pending" | "active" | "revoked";
|
||||
|
||||
@Entity("playlist_shares")
|
||||
export class PlaylistShare {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
playlistId!: string;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
userId!: string;
|
||||
|
||||
// Permissions gérées par le propriétaire de la playlist
|
||||
@Column({ type: "enum", enum: ["view", "edit"], default: "view" })
|
||||
permission!: SharePermission;
|
||||
|
||||
// Statut géré par le propriétaire :
|
||||
// pending → invitation envoyée, pas encore acceptée
|
||||
// active → invité a accepté
|
||||
// revoked → propriétaire a révoqué l'accès
|
||||
@Column({ type: "enum", enum: ["pending", "active", "revoked"], default: "pending" })
|
||||
status!: ShareStatus;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
@ManyToOne(() => Playlist, (playlist) => playlist.shares, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "playlistId" })
|
||||
playlist!: Playlist;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.playlistShares, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "userId" })
|
||||
user!: User;
|
||||
}
|
||||
24
backend/src/entities/PlaylistVideo.ts
Normal file
24
backend/src/entities/PlaylistVideo.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Entity, ManyToOne, JoinColumn, PrimaryColumn, Column } from "typeorm";
|
||||
import { Playlist } from "./Playlist";
|
||||
import { Video } from "./Video";
|
||||
|
||||
@Entity("playlist_videos")
|
||||
export class PlaylistVideo {
|
||||
@PrimaryColumn({ type: "uuid" })
|
||||
playlistId!: string;
|
||||
|
||||
@PrimaryColumn({ type: "uuid" })
|
||||
videoId!: string;
|
||||
|
||||
// Ordre d'affichage dans la playlist — géré par le propriétaire ou les éditeurs
|
||||
@Column({ type: "int", unsigned: true, default: 0 })
|
||||
position!: number;
|
||||
|
||||
@ManyToOne(() => Playlist, (playlist) => playlist.playlistVideos, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "playlistId" })
|
||||
playlist!: Playlist;
|
||||
|
||||
@ManyToOne(() => Video, (video) => video.playlistVideos, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "videoId" })
|
||||
video!: Video;
|
||||
}
|
||||
19
backend/src/entities/Role.ts
Normal file
19
backend/src/entities/Role.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
|
||||
import { UserRole } from "./UserRole";
|
||||
|
||||
export type RoleSlug = "user" | "moderator" | "admin" | "super_admin";
|
||||
|
||||
@Entity("roles")
|
||||
export class Role {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
@Column({ type: "varchar", length: 50, unique: true })
|
||||
slug!: RoleSlug;
|
||||
|
||||
@Column({ type: "varchar", length: 100 })
|
||||
name!: string;
|
||||
|
||||
@OneToMany(() => UserRole, (userRole) => userRole.role)
|
||||
userRoles!: UserRole[];
|
||||
}
|
||||
35
backend/src/entities/SubscriptionPlan.ts
Normal file
35
backend/src/entities/SubscriptionPlan.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
|
||||
import { UserSubscription } from "./UserSubscription";
|
||||
|
||||
export type PlanSlug = "free" | "basic" | "pro" | "enterprise";
|
||||
|
||||
@Entity("subscription_plans")
|
||||
export class SubscriptionPlan {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
@Column({ type: "varchar", length: 50, unique: true })
|
||||
slug!: PlanSlug;
|
||||
|
||||
@Column({ type: "varchar", length: 100 })
|
||||
name!: string;
|
||||
|
||||
// Niveau d'accès — comparaison entière : userPlanLevel >= video.requiredLevel
|
||||
// free=0, basic=1, pro=2, enterprise=3
|
||||
@Column({ type: "tinyint", unsigned: true, default: 0 })
|
||||
level!: number;
|
||||
|
||||
// Prix en centimes (0 = gratuit). Évite les flottants en DB.
|
||||
@Column({ type: "int", unsigned: true, default: 0 })
|
||||
priceInCents!: number;
|
||||
|
||||
// Features libres — permet la personnalisation marque blanche sans migration
|
||||
@Column({ type: "json", nullable: true })
|
||||
features!: Record<string, unknown> | null;
|
||||
|
||||
@Column({ type: "boolean", default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@OneToMany(() => UserSubscription, (sub) => sub.plan)
|
||||
subscriptions!: UserSubscription[];
|
||||
}
|
||||
45
backend/src/entities/User.ts
Normal file
45
backend/src/entities/User.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
Entity, PrimaryGeneratedColumn, Column,
|
||||
OneToMany, CreateDateColumn, UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
import { UserRole } from "./UserRole";
|
||||
import { UserSubscription } from "./UserSubscription";
|
||||
import { Playlist } from "./Playlist";
|
||||
import { PlaylistShare } from "./PlaylistShare";
|
||||
|
||||
@Entity("users")
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
// Identifiant SuperOAuth — clé de réconciliation avec le service auth
|
||||
@Column({ type: "varchar", length: 255, unique: true })
|
||||
superOAuthId!: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
email!: string | null;
|
||||
|
||||
@Column({ type: "varchar", length: 100 })
|
||||
nickname!: string;
|
||||
|
||||
@Column({ type: "boolean", default: true })
|
||||
isActive!: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
@OneToMany(() => UserRole, (userRole) => userRole.user)
|
||||
userRoles!: UserRole[];
|
||||
|
||||
@OneToMany(() => UserSubscription, (sub) => sub.user)
|
||||
subscriptions!: UserSubscription[];
|
||||
|
||||
@OneToMany(() => Playlist, (playlist) => playlist.owner)
|
||||
playlists!: Playlist[];
|
||||
|
||||
@OneToMany(() => PlaylistShare, (share) => share.user)
|
||||
playlistShares!: PlaylistShare[];
|
||||
}
|
||||
20
backend/src/entities/UserRole.ts
Normal file
20
backend/src/entities/UserRole.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Entity, ManyToOne, JoinColumn, PrimaryColumn } from "typeorm";
|
||||
import { User } from "./User";
|
||||
import { Role } from "./Role";
|
||||
|
||||
@Entity("user_roles")
|
||||
export class UserRole {
|
||||
@PrimaryColumn({ type: "uuid" })
|
||||
userId!: string;
|
||||
|
||||
@PrimaryColumn({ type: "uuid" })
|
||||
roleId!: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.userRoles, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "userId" })
|
||||
user!: User;
|
||||
|
||||
@ManyToOne(() => Role, (role) => role.userRoles, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "roleId" })
|
||||
role!: Role;
|
||||
}
|
||||
41
backend/src/entities/UserSubscription.ts
Normal file
41
backend/src/entities/UserSubscription.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Entity, PrimaryGeneratedColumn, Column,
|
||||
ManyToOne, JoinColumn, CreateDateColumn,
|
||||
} from "typeorm";
|
||||
import { User } from "./User";
|
||||
import { SubscriptionPlan } from "./SubscriptionPlan";
|
||||
|
||||
export type SubscriptionStatus = "active" | "expired" | "cancelled" | "trial";
|
||||
|
||||
@Entity("user_subscriptions")
|
||||
export class UserSubscription {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
userId!: string;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
planId!: string;
|
||||
|
||||
@Column({ type: "enum", enum: ["active", "expired", "cancelled", "trial"], default: "active" })
|
||||
status!: SubscriptionStatus;
|
||||
|
||||
@Column({ type: "datetime" })
|
||||
startsAt!: Date;
|
||||
|
||||
// null = pas d'expiration (plan gratuit permanent)
|
||||
@Column({ type: "datetime", nullable: true })
|
||||
endsAt!: Date | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.subscriptions, { onDelete: "CASCADE" })
|
||||
@JoinColumn({ name: "userId" })
|
||||
user!: User;
|
||||
|
||||
@ManyToOne(() => SubscriptionPlan, (plan) => plan.subscriptions)
|
||||
@JoinColumn({ name: "planId" })
|
||||
plan!: SubscriptionPlan;
|
||||
}
|
||||
58
backend/src/entities/Video.ts
Normal file
58
backend/src/entities/Video.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
Entity, PrimaryGeneratedColumn, Column,
|
||||
OneToMany, CreateDateColumn, UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
import { PlaylistVideo } from "./PlaylistVideo";
|
||||
|
||||
export type StorageType = "youtube" | "s3" | "local" | "external";
|
||||
|
||||
@Entity("videos")
|
||||
export class Video {
|
||||
@PrimaryGeneratedColumn("uuid")
|
||||
id!: string;
|
||||
|
||||
@Column({ type: "varchar", length: 255 })
|
||||
title!: string;
|
||||
|
||||
@Column({ type: "text", nullable: true })
|
||||
description!: string | null;
|
||||
|
||||
@Column({ type: "varchar", length: 500, nullable: true })
|
||||
thumbnailUrl!: string | null;
|
||||
|
||||
// Durée en secondes
|
||||
@Column({ type: "int", unsigned: true, nullable: true })
|
||||
duration!: number | null;
|
||||
|
||||
@Column({ type: "enum", enum: ["youtube", "s3", "local", "external"] })
|
||||
storageType!: StorageType;
|
||||
|
||||
// Référence flexible selon storageType :
|
||||
// youtube → videoId YouTube (ex: "dQw4w9WgXcQ")
|
||||
// s3 → clé S3 (ex: "videos/2026/my-video.mp4")
|
||||
// local → chemin relatif (ex: "uploads/my-video.mp4")
|
||||
// external → URL complète
|
||||
@Column({ type: "varchar", length: 500 })
|
||||
storageKey!: string;
|
||||
|
||||
// Niveau de plan minimum requis pour accéder à cette vidéo
|
||||
// 0 = libre, 1 = basic, 2 = pro, 3 = enterprise
|
||||
// Comparaison : userSubscription.plan.level >= video.requiredLevel
|
||||
@Column({ type: "tinyint", unsigned: true, default: 0 })
|
||||
requiredLevel!: number;
|
||||
|
||||
@Column({ type: "boolean", default: false })
|
||||
isPublished!: boolean;
|
||||
|
||||
@Column({ type: "datetime", nullable: true })
|
||||
publishedAt!: Date | null;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
@OneToMany(() => PlaylistVideo, (pv) => pv.video)
|
||||
playlistVideos!: PlaylistVideo[];
|
||||
}
|
||||
Reference in New Issue
Block a user