streamflix-api / prisma /schema.prisma
Akshar2325
feat(short-url): Added Trim Kutt URL shortener integration with CRUD operations
2ed6bf1
Raw
History Blame
27.5 kB
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
// ================ ENUMS =================
enum USER_STATUS {
PENDING_EMAIL_VERIFICATION
PENDING_PHONE_VERIFICATION
PENDING_PROFILE_COMPLETION
ENABLED
DISABLED
SUSPENDED
INVITED
}
enum SESSION_STATUS {
CURRENT
EXPIRED
REVOKED
}
enum OTP_TYPE {
EMAIL
SMS
}
enum OTP_PURPOSE {
EMAIL_VERIFICATION
PHONE_VERIFICATION
PASSWORD_RESET
LOGIN_2FA
}
enum UPLOAD_STATUS {
UPLOADING
PROCESSING
COMPLETED
FAILED
}
enum NOTIFICATION_TYPE {
NEW_MOVIE
NEW_SERIES
NEW_EPISODE
COMMENT_REPLY
COMMENT_LIKE
CONTENT_LIKE
ACCOUNT_UPDATE
SUBSCRIPTION_REMINDER
RECOMMENDATION
}
enum CONTENT_TYPE {
MOVIE
SERIES
EPISODE
}
enum TWO_FACTOR_METHOD_TYPE {
EMAIL
AUTHENTICATOR // TOTP
PASSKEY // WebAuthn / FIDO2
PUSH_APPROVAL // Mobile yes/no
}
enum PUSH_APPROVAL_STATUS {
PENDING
APPROVED
DENIED
EXPIRED
}
enum PLATFORM_TYPE {
ANDROID
IOS
WEB
}
enum URL_SHORTENER_PROVIDER {
SHLINK
TRIM_KUTT
}
// ============ USER MANAGEMENT ============
model User {
id String @id @default(uuid())
uniqueId String @unique
email String? @unique
phone String? @unique
code String?
firstName String?
lastName String?
firstNameLastName String?
lastNameFirstName String?
profileImage String?
bio String? @db.Text
dateOfBirth DateTime? @db.Timestamp(3)
country String?
city String?
status USER_STATUS @default(PENDING_EMAIL_VERIFICATION)
emailVerified Boolean @default(false)
phoneVerified Boolean @default(false)
lastLogin DateTime? @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
sessions UserSession[]
credentials UserCredential[]
otps UserOtp[]
interests UserInterest[]
watchHistories WatchHistory[]
notifications Notification[]
playlists Playlist[]
likes Like[]
ratings Rating[]
comments Comment[]
commentLikes CommentLike[]
@@map("users")
}
model UserSession {
id String @id @default(uuid())
userId String
accessToken String @db.Text
refreshToken String @db.Text
ipAddress String?
userAgent String? @db.Text
geoIpCountry String?
city String?
state String?
latitude Float?
longitude Float?
status SESSION_STATUS @default(CURRENT)
loginAt DateTime? @db.Timestamp(3)
logoutAt DateTime? @db.Timestamp(3)
expiredAt DateTime? @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_sessions")
}
model UserCredential {
id String @id @default(uuid())
userId String @unique
password String
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_credentials")
}
model UserOtp {
id String @id @default(uuid())
userId String
otp String
type OTP_TYPE
purpose OTP_PURPOSE
expiresAt DateTime @db.Timestamp(3)
verified Boolean @default(false)
verifiedAt DateTime? @db.Timestamp(3)
attempts Int @default(0)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, type, verified])
@@map("user_otps")
}
model UserInterest {
id String @id @default(uuid())
userId String
genreId String
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
genre Genre @relation(fields: [genreId], references: [id], onDelete: Cascade)
@@unique([userId, genreId])
@@index([userId])
@@index([genreId])
@@map("user_interests")
}
// ============ SUPER ADMIN ============
model SuperAdmin {
id String @id @default(uuid())
email String @unique
name String
profileImage String?
isTwoFactorEnabled Boolean @default(false)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
sessions SuperAdminSession[]
credentials SuperAdminCredential[]
twoFactorMethods SuperAdminTwoFactorMethod[]
recoveryCodes SuperAdminRecoveryCode[]
trustedDevices SuperAdminTrustedDevice[]
webAuthnCredentials SuperAdminWebAuthnCredential[]
pushDevices SuperAdminPushDevice[]
pushApprovalChallenges SuperAdminPushApprovalChallenge[]
@@map("super_admins")
}
model SuperAdminSession {
id String @id @default(uuid())
superAdminId String
accessToken String @db.Text
refreshToken String @db.Text
ipAddress String?
userAgent String? @db.Text
geoIpCountry String?
city String?
state String?
latitude Float?
longitude Float?
isFromAdmin Boolean @default(false)
status SESSION_STATUS @default(CURRENT)
loginAt DateTime? @db.Timestamp(3)
expiredAt DateTime? @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@map("super_admin_sessions")
}
model SuperAdminCredential {
id String @id @default(uuid())
superAdminId String
password String
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@map("super_admin_credentials")
}
// ============ SUPER ADMIN 2FA ============
model SuperAdminTwoFactorMethod {
id String @id @default(uuid())
superAdminId String
type TWO_FACTOR_METHOD_TYPE
isEnabled Boolean @default(false)
isPrimary Boolean @default(false)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
emailTwoFactor SuperAdminEmailTwoFactor?
totpTwoFactor SuperAdminTotpTwoFactor?
passkeyTwoFactor SuperAdminPasskeyTwoFactor?
pushTwoFactor SuperAdminPushTwoFactor?
@@unique([superAdminId, type])
@@index([superAdminId])
@@map("super_admin_two_factor_methods")
}
model SuperAdminEmailTwoFactor {
id String @id @default(uuid())
methodId String @unique
lastCodeHash String?
lastCodeExpiresAt DateTime? @db.Timestamp(3)
lastSentAt DateTime? @db.Timestamp(3)
failedAttempts Int @default(0)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
method SuperAdminTwoFactorMethod @relation(fields: [methodId], references: [id], onDelete: Cascade)
@@map("super_admin_email_two_factor")
}
model SuperAdminTotpTwoFactor {
id String @id @default(uuid())
methodId String @unique
secretEncrypted String
confirmedAt DateTime? @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
method SuperAdminTwoFactorMethod @relation(fields: [methodId], references: [id], onDelete: Cascade)
@@map("super_admin_totp_two_factor")
}
model SuperAdminPasskeyTwoFactor {
id String @id @default(uuid())
methodId String @unique
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
method SuperAdminTwoFactorMethod @relation(fields: [methodId], references: [id], onDelete: Cascade)
@@map("super_admin_passkey_two_factor")
}
model SuperAdminPushTwoFactor {
id String @id @default(uuid())
methodId String @unique
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
method SuperAdminTwoFactorMethod @relation(fields: [methodId], references: [id], onDelete: Cascade)
@@map("super_admin_push_two_factor")
}
model SuperAdminRecoveryCode {
id String @id @default(uuid())
superAdminId String
codeHash String
usedAt DateTime? @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@index([superAdminId])
@@map("super_admin_recovery_codes")
}
model SuperAdminWebAuthnCredential {
id String @id @default(uuid())
superAdminId String
credentialId String @unique
publicKey String @db.Text
counter Int @default(0)
deviceName String?
createdAt DateTime @default(now()) @db.Timestamp(3)
lastUsedAt DateTime? @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@index([superAdminId])
@@map("super_admin_webauthn_credentials")
}
model SuperAdminTrustedDevice {
id String @id @default(uuid())
superAdminId String
deviceId String @unique
deviceName String?
ipAddress String?
userAgent String? @db.Text
lastUsedAt DateTime @default(now()) @db.Timestamp(3)
expiresAt DateTime @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@index([superAdminId])
@@index([deviceId])
@@map("super_admin_trusted_devices")
}
model SuperAdminPushDevice {
id String @id @default(uuid())
superAdminId String
pushToken String
platform PLATFORM_TYPE
deviceName String?
lastSeenAt DateTime @default(now()) @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@index([superAdminId])
@@map("super_admin_push_devices")
}
model SuperAdminPushApprovalChallenge {
id String @id @default(uuid())
superAdminId String
status PUSH_APPROVAL_STATUS @default(PENDING)
ipAddress String?
userAgent String? @db.Text
location String?
deviceInfo String?
expiresAt DateTime @db.Timestamp(3)
approvedAt DateTime? @db.Timestamp(3)
deniedAt DateTime? @db.Timestamp(3)
createdAt DateTime @default(now()) @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
superAdmin SuperAdmin @relation(fields: [superAdminId], references: [id], onDelete: Cascade)
@@index([superAdminId])
@@index([status])
@@map("super_admin_push_approval_challenges")
}
// ============ MOVIES ============
model Movie {
id String @id @default(uuid())
title String
slug String @unique
description String? @db.Text
thumbnailUrl String
bannerUrl String?
trailerUrl String?
rating String?
releaseYear Int?
language String?
duration Int?
imdbId String?
tmdbId String?
isFeatured Boolean @default(false)
uploadStatus UPLOAD_STATUS @default(COMPLETED)
views Int @default(0)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
genres MovieGenre[]
tags MovieTag[]
video MovieVideo?
comments Comment[]
likes Like[]
ratings Rating[]
playlistItems PlaylistItem[]
watchHistory WatchHistory[]
@@index([isDeleted])
@@index([isFeatured])
@@index([slug])
@@map("movies")
}
model MovieVideo {
id String @id @default(uuid())
movieId String @unique
videoUrl String
videoKey String?
fileSize BigInt?
quality String?
uploadStatus UPLOAD_STATUS @default(COMPLETED)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
movie Movie @relation(fields: [movieId], references: [id], onDelete: Cascade)
@@index([isDeleted])
@@map("movie_videos")
}
model MovieGenre {
id String @id @default(uuid())
movieId String
genreId String
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
movie Movie @relation(fields: [movieId], references: [id], onDelete: Cascade)
genre Genre @relation(fields: [genreId], references: [id], onDelete: Cascade)
@@unique([movieId, genreId])
@@index([movieId])
@@index([genreId])
@@map("movie_genres")
}
// ============ SERIES ============
model Series {
id String @id @default(uuid())
title String
slug String @unique
description String? @db.Text
thumbnailUrl String
bannerUrl String?
trailerUrl String?
rating String?
releaseYear Int?
language String?
imdbId String?
tmdbId String?
isFeatured Boolean @default(false)
uploadStatus UPLOAD_STATUS @default(COMPLETED)
views Int @default(0)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
genres SeriesGenre[]
tags SeriesTag[]
seasons Season[]
comments Comment[]
likes Like[]
ratings Rating[]
playlistItems PlaylistItem[]
watchHistory WatchHistory[]
@@index([isDeleted])
@@index([isFeatured])
@@index([slug])
@@map("series")
}
model Season {
id String @id @default(uuid())
seriesId String
seasonNumber Int
title String
description String? @db.Text
thumbnailUrl String?
releaseYear Int?
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
series Series @relation(fields: [seriesId], references: [id], onDelete: Cascade)
episodes Episode[]
@@unique([seriesId, seasonNumber])
@@index([seriesId])
@@map("seasons")
}
model Episode {
id String @id @default(uuid())
seasonId String
episodeNumber Int
title String
description String? @db.Text
thumbnailUrl String?
duration Int
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
season Season @relation(fields: [seasonId], references: [id], onDelete: Cascade)
video EpisodeVideo?
comments Comment[]
likes Like[]
ratings Rating[]
playlistItems PlaylistItem[]
watchHistory WatchHistory[]
@@unique([seasonId, episodeNumber])
@@index([seasonId])
@@map("episodes")
}
model EpisodeVideo {
id String @id @default(uuid())
episodeId String @unique
videoUrl String
videoKey String?
fileSize BigInt?
quality String?
uploadStatus UPLOAD_STATUS @default(COMPLETED)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
episode Episode @relation(fields: [episodeId], references: [id], onDelete: Cascade)
@@index([isDeleted])
@@map("episode_videos")
}
model SeriesGenre {
id String @id @default(uuid())
seriesId String
genreId String
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
series Series @relation(fields: [seriesId], references: [id], onDelete: Cascade)
genre Genre @relation(fields: [genreId], references: [id], onDelete: Cascade)
@@unique([seriesId, genreId])
@@index([seriesId])
@@index([genreId])
@@map("series_genres")
}
// ============ GENRES ============
model Genre {
id String @id @default(uuid())
name String @unique
slug String @unique
description String? @db.Text
icon String?
sortOrder Int @default(0)
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
movieGenres MovieGenre[]
seriesGenres SeriesGenre[]
userInterests UserInterest[]
@@index([slug])
@@map("genres")
}
// ============ UNIFIED COMMENT ============
model Comment {
id String @id @default(uuid())
content String @db.Text
userId String
contentType CONTENT_TYPE
movieId String?
seriesId String?
episodeId String?
parentId String?
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
movie Movie? @relation(fields: [movieId], references: [id], onDelete: Cascade)
series Series? @relation(fields: [seriesId], references: [id], onDelete: Cascade)
episode Episode? @relation(fields: [episodeId], references: [id], onDelete: Cascade)
parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: Cascade)
replies Comment[] @relation("CommentReplies")
likes CommentLike[]
@@index([movieId, isDeleted])
@@index([seriesId, isDeleted])
@@index([episodeId, isDeleted])
@@index([userId, isDeleted])
@@index([contentType, isDeleted])
@@map("comments")
}
// ============ UNIFIED LIKE ============
model Like {
id String @id @default(uuid())
userId String
contentType CONTENT_TYPE
movieId String?
seriesId String?
episodeId String?
isLike Boolean
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
movie Movie? @relation(fields: [movieId], references: [id], onDelete: Cascade)
series Series? @relation(fields: [seriesId], references: [id], onDelete: Cascade)
episode Episode? @relation(fields: [episodeId], references: [id], onDelete: Cascade)
// Only one of movieId/seriesId/episodeId should be non-null per row (enforce in code)
@@unique([userId, movieId])
@@unique([userId, seriesId])
@@unique([userId, episodeId])
@@index([movieId, contentType])
@@index([seriesId, contentType])
@@index([episodeId, contentType])
@@map("likes")
}
// ============ COMMENT LIKE ============
model CommentLike {
id String @id @default(uuid())
userId String
commentId String
isLike Boolean
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade)
@@unique([userId, commentId])
@@index([commentId])
@@map("comment_likes")
}
// ============ PLAYLISTS ============
model Playlist {
id String @id @default(uuid())
name String
description String? @db.Text
userId String
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
items PlaylistItem[]
@@index([userId, isDeleted])
@@map("playlists")
}
model PlaylistItem {
id String @id @default(uuid())
playlistId String
contentType CONTENT_TYPE
movieId String?
seriesId String?
episodeId String?
position Int
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
playlist Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
movie Movie? @relation(fields: [movieId], references: [id], onDelete: Cascade)
series Series? @relation(fields: [seriesId], references: [id], onDelete: Cascade)
episode Episode? @relation(fields: [episodeId], references: [id], onDelete: Cascade)
// Only one of movieId/seriesId/episodeId should be non-null per row (enforce in code)
@@unique([playlistId, movieId])
@@unique([playlistId, seriesId])
@@unique([playlistId, episodeId])
@@index([playlistId])
@@index([movieId, contentType])
@@index([seriesId, contentType])
@@index([episodeId, contentType])
@@map("playlist_items")
}
// ============ WATCH HISTORY ============
model WatchHistory {
id String @id @default(uuid())
userId String
contentType CONTENT_TYPE
movieId String?
seriesId String?
episodeId String?
watchedAt DateTime @default(now()) @db.Timestamp(3)
watchTime Int
duration Int
completed Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
movie Movie? @relation(fields: [movieId], references: [id], onDelete: Cascade)
series Series? @relation(fields: [seriesId], references: [id], onDelete: Cascade)
episode Episode? @relation(fields: [episodeId], references: [id], onDelete: Cascade)
@@index([userId, contentType])
@@index([movieId])
@@index([seriesId])
@@index([episodeId])
@@map("watch_history")
}
// ============ NOTIFICATIONS ============
model Notification {
id String @id @default(uuid())
userId String
type NOTIFICATION_TYPE
title String
message String @db.Text
read Boolean @default(false)
data Json?
createdAt DateTime @default(now()) @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, isDeleted])
@@index([userId, read, isDeleted])
@@map("notifications")
}
// ============ RATINGS (Star Ratings 1-5) ============
model Rating {
id String @id @default(uuid())
userId String
contentType CONTENT_TYPE
movieId String?
seriesId String?
episodeId String?
value Int
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
movie Movie? @relation(fields: [movieId], references: [id], onDelete: Cascade)
series Series? @relation(fields: [seriesId], references: [id], onDelete: Cascade)
episode Episode? @relation(fields: [episodeId], references: [id], onDelete: Cascade)
// Only one of movieId/seriesId/episodeId should be non-null per row (enforce in code)
@@unique([userId, movieId])
@@unique([userId, seriesId])
@@unique([userId, episodeId])
@@index([movieId, contentType])
@@index([seriesId, contentType])
@@index([episodeId, contentType])
@@map("ratings")
}
// ============ UPLOADS ============
model Upload {
id String @id @default(uuid())
fileName String
fileType String // 'image', 'video', 'document'
originalSize Int?
// For videos (HLS)
hlsUrl String? // URL to playlist.m3u8
hlsFolderId String? // folder containing HLS files
hlsSegments Json? // Array of segment URLs
videoDuration Float? // Duration in seconds
status String @default("pending") // pending, processing, completed, failed
errorMessage String?
uploadedAt DateTime @default(now()) @db.Timestamp(3)
processedAt DateTime?
metadata Json?
@@index([status])
@@index([fileType])
@@index([uploadedAt])
@@map("uploads")
}
// ============ TAGS (Flexible Content Tags) ============
model Tag {
id String @id @default(uuid())
name String @unique
slug String @unique
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
// Relations
movieTags MovieTag[]
seriesTags SeriesTag[]
@@index([slug])
@@map("tags")
}
model MovieTag {
id String @id @default(uuid())
movieId String
tagId String
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
movie Movie @relation(fields: [movieId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@unique([movieId, tagId])
@@index([movieId])
@@index([tagId])
@@map("movie_tags")
}
model SeriesTag {
id String @id @default(uuid())
seriesId String
tagId String
createdAt DateTime @default(now()) @db.Timestamp(3)
// Relations
series Series @relation(fields: [seriesId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@unique([seriesId, tagId])
@@index([seriesId])
@@index([tagId])
@@map("series_tags")
}
// ============ SHORT URL ============
model ShortUrl {
id String @id @default(uuid())
longUrl String @db.Text
shortUrl String
shortCode String?
externalId String? // Provider's internal ID (e.g., Kutt's ID)
provider URL_SHORTENER_PROVIDER
createdAt DateTime @default(now()) @db.Timestamp(3)
updatedAt DateTime @updatedAt @db.Timestamp(3)
isDeleted Boolean @default(false)
@@index([provider])
@@map("short_urls")
}