465 lines
16 KiB
Text
465 lines
16 KiB
Text
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum NodeStatus {
|
|
ONLINE
|
|
OFFLINE
|
|
DEGRADED
|
|
REVOKED
|
|
UPDATING
|
|
SCANNING
|
|
ERROR
|
|
}
|
|
|
|
enum PlaybackSessionStatus {
|
|
ACTIVE
|
|
IDLE
|
|
EXPIRED
|
|
REVOKED
|
|
COMPLETED
|
|
}
|
|
|
|
enum LocationType {
|
|
LOCAL
|
|
REMOTE
|
|
}
|
|
|
|
enum LibraryShelfKind {
|
|
MOVIE
|
|
SERIES
|
|
}
|
|
|
|
enum DownloadJobStatus {
|
|
DOWNLOADING
|
|
FINISHED
|
|
IMPORTING
|
|
IMPORTED
|
|
FAILED
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
passwordHash String @map("password_hash")
|
|
name String?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
addonTokens AddonToken[]
|
|
sessions AdminSession[]
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model AdminSession {
|
|
id String @id @default(uuid())
|
|
userId String @map("user_id")
|
|
tokenHash String @unique @map("token_hash")
|
|
expiresAt DateTime @map("expires_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("admin_sessions")
|
|
}
|
|
|
|
model AddonToken {
|
|
id String @id @default(uuid())
|
|
userId String @map("user_id")
|
|
tokenHash String @unique @map("token_hash")
|
|
label String?
|
|
revoked Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
iptvLine IptvLine?
|
|
playbackSessions PlaybackSession[]
|
|
|
|
@@map("addon_tokens")
|
|
}
|
|
|
|
/** Xtream/XUI credentials bound to one Stremio addon install (token). */
|
|
model IptvLine {
|
|
id String @id @default(uuid())
|
|
addonTokenId String @unique @map("addon_token_id")
|
|
label String?
|
|
baseUrl String @map("base_url")
|
|
username String
|
|
/** AES-GCM ciphertext (base64) */
|
|
passwordEnc String @map("password_enc")
|
|
enableLive Boolean @default(true) @map("enable_live")
|
|
enableVod Boolean @default(true) @map("enable_vod")
|
|
enableSeries Boolean @default(true) @map("enable_series")
|
|
lastOkAt DateTime? @map("last_ok_at")
|
|
lastError String? @map("last_error")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
addonToken AddonToken @relation(fields: [addonTokenId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("iptv_lines")
|
|
}
|
|
|
|
model Location {
|
|
id String @id @default(uuid())
|
|
name String @unique
|
|
description String?
|
|
priority Int @default(0)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
nodes Node[]
|
|
|
|
@@map("locations")
|
|
}
|
|
|
|
model Node {
|
|
id String @id @default(uuid())
|
|
name String
|
|
hostname String?
|
|
locationId String? @map("location_id")
|
|
locationType LocationType @default(LOCAL) @map("location_type")
|
|
status NodeStatus @default(OFFLINE)
|
|
version String?
|
|
architecture String?
|
|
publicStreamUrl String @map("public_stream_url")
|
|
lastSeen DateTime? @map("last_seen")
|
|
totalStorage BigInt @default(0) @map("total_storage")
|
|
freeStorage BigInt @default(0) @map("free_storage")
|
|
activeStreams Int @default(0) @map("active_streams")
|
|
currentBandwidth BigInt @default(0) @map("current_bandwidth")
|
|
libraryFileCount Int @default(0) @map("library_file_count")
|
|
lastRevision Int @default(0) @map("last_revision")
|
|
/** Node scanner: "off" or duration like "24h" / "6h" */
|
|
fullScanInterval String @default("off") @map("full_scan_interval")
|
|
/** Nightly catch-up time HH:MM in node local TZ */
|
|
fullScanAt String @default("03:30") @map("full_scan_at")
|
|
/** Host/container scan roots for movies (e.g. /mnt/user/NL Films) — legacy; prefer scanRoots */
|
|
moviesPaths String[] @default([]) @map("movies_paths")
|
|
/** Host/container scan roots for series — legacy; prefer scanRoots */
|
|
seriesPaths String[] @default([]) @map("series_paths")
|
|
revoked Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
location Location? @relation(fields: [locationId], references: [id])
|
|
credentials NodeCredential?
|
|
mediaFiles MediaFile[]
|
|
playbackSessions PlaybackSession[]
|
|
scanRoots NodeScanRoot[]
|
|
synologyDsConfig SynologyDsConfig?
|
|
downloadJobs DownloadJob[]
|
|
|
|
@@map("nodes")
|
|
}
|
|
|
|
/** Named Stremio catalog / shelf shared across all nodes (e.g. Kids Films). */
|
|
model LibraryShelf {
|
|
id String @id @default(uuid())
|
|
name String
|
|
slug String @unique
|
|
kind LibraryShelfKind
|
|
sortOrder Int @default(0) @map("sort_order")
|
|
enabled Boolean @default(true)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
scanRoots NodeScanRoot[]
|
|
mediaFiles MediaFile[]
|
|
|
|
@@map("library_shelves")
|
|
}
|
|
|
|
/** One filesystem root on a node bound to a library shelf. */
|
|
model NodeScanRoot {
|
|
id String @id @default(uuid())
|
|
nodeId String @map("node_id")
|
|
path String
|
|
shelfId String @map("shelf_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
node Node @relation(fields: [nodeId], references: [id], onDelete: Cascade)
|
|
shelf LibraryShelf @relation(fields: [shelfId], references: [id], onDelete: Restrict)
|
|
|
|
@@unique([nodeId, path])
|
|
@@index([shelfId])
|
|
@@map("node_scan_roots")
|
|
}
|
|
|
|
model NodeCredential {
|
|
id String @id @default(uuid())
|
|
nodeId String @unique @map("node_id")
|
|
apiKeyHash String @map("api_key_hash")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
rotatedAt DateTime? @map("rotated_at")
|
|
|
|
node Node @relation(fields: [nodeId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("node_credentials")
|
|
}
|
|
|
|
model NodeEnrollmentToken {
|
|
id String @id @default(uuid())
|
|
tokenHash String @unique @map("token_hash")
|
|
name String?
|
|
locationId String? @map("location_id")
|
|
expiresAt DateTime @map("expires_at")
|
|
usedAt DateTime? @map("used_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
@@map("node_enrollment_tokens")
|
|
}
|
|
|
|
model Movie {
|
|
id String @id @default(uuid())
|
|
title String
|
|
originalTitle String? @map("original_title")
|
|
year Int?
|
|
overview String?
|
|
posterUrl String? @map("poster_url")
|
|
backdropUrl String? @map("backdrop_url")
|
|
genres String[] @default([])
|
|
runtime Int?
|
|
imdbId String? @unique @map("imdb_id")
|
|
tmdbId Int? @unique @map("tmdb_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
mediaFiles MediaFile[]
|
|
matches LibraryMatch[]
|
|
subtitles LibrarySubtitle[]
|
|
|
|
@@map("movies")
|
|
}
|
|
|
|
model Series {
|
|
id String @id @default(uuid())
|
|
title String
|
|
year Int?
|
|
overview String?
|
|
posterUrl String? @map("poster_url")
|
|
backdropUrl String? @map("backdrop_url")
|
|
logoUrl String? @map("logo_url")
|
|
genres String[] @default([])
|
|
imdbId String? @unique @map("imdb_id")
|
|
tmdbId Int? @unique @map("tmdb_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
seasons Season[]
|
|
episodes Episode[]
|
|
matches LibraryMatch[]
|
|
|
|
@@map("series")
|
|
}
|
|
|
|
model Season {
|
|
id String @id @default(uuid())
|
|
seriesId String @map("series_id")
|
|
seasonNumber Int @map("season_number")
|
|
|
|
series Series @relation(fields: [seriesId], references: [id], onDelete: Cascade)
|
|
episodes Episode[]
|
|
|
|
@@unique([seriesId, seasonNumber])
|
|
@@map("seasons")
|
|
}
|
|
|
|
model Episode {
|
|
id String @id @default(uuid())
|
|
seriesId String @map("series_id")
|
|
seasonId String @map("season_id")
|
|
seasonNumber Int @map("season_number")
|
|
episodeNumber Int @map("episode_number")
|
|
title String?
|
|
overview String?
|
|
stillUrl String? @map("still_url")
|
|
runtime Int?
|
|
airDate DateTime? @map("air_date")
|
|
imdbId String? @map("imdb_id")
|
|
tmdbId Int? @map("tmdb_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
series Series @relation(fields: [seriesId], references: [id], onDelete: Cascade)
|
|
season Season @relation(fields: [seasonId], references: [id], onDelete: Cascade)
|
|
mediaFiles MediaFile[]
|
|
matches LibraryMatch[]
|
|
|
|
@@unique([seriesId, seasonNumber, episodeNumber])
|
|
@@map("episodes")
|
|
}
|
|
|
|
model MediaFile {
|
|
id String @id @default(uuid())
|
|
nodeId String @map("node_id")
|
|
movieId String? @map("movie_id")
|
|
episodeId String? @map("episode_id")
|
|
shelfId String? @map("shelf_id")
|
|
localFileId String @map("local_file_id")
|
|
sizeBytes BigInt @map("size_bytes")
|
|
container String?
|
|
resolution String?
|
|
videoCodec String? @map("video_codec")
|
|
videoBitrate Int? @map("video_bitrate")
|
|
hdrType String? @map("hdr_type")
|
|
audioCodec String? @map("audio_codec")
|
|
audioChannels String? @map("audio_channels")
|
|
audioLanguage String? @map("audio_language")
|
|
releaseName String @map("release_name")
|
|
available Boolean @default(true)
|
|
lastSeen DateTime @default(now()) @map("last_seen")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
node Node @relation(fields: [nodeId], references: [id], onDelete: Cascade)
|
|
movie Movie? @relation(fields: [movieId], references: [id])
|
|
episode Episode? @relation(fields: [episodeId], references: [id])
|
|
shelf LibraryShelf? @relation(fields: [shelfId], references: [id], onDelete: SetNull)
|
|
playbackSessions PlaybackSession[]
|
|
|
|
@@unique([nodeId, localFileId])
|
|
@@index([shelfId])
|
|
@@map("media_files")
|
|
}
|
|
|
|
model LibraryMatch {
|
|
id String @id @default(uuid())
|
|
movieId String? @map("movie_id")
|
|
seriesId String? @map("series_id")
|
|
episodeId String? @map("episode_id")
|
|
releaseName String @map("release_name")
|
|
ignored Boolean @default(false)
|
|
manual Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
movie Movie? @relation(fields: [movieId], references: [id])
|
|
series Series? @relation(fields: [seriesId], references: [id])
|
|
episode Episode? @relation(fields: [episodeId], references: [id])
|
|
|
|
@@map("library_matches")
|
|
}
|
|
|
|
model PlaybackSession {
|
|
id String @id @default(uuid())
|
|
tokenHash String @unique @map("token_hash")
|
|
userId String? @map("user_id")
|
|
addonTokenId String? @map("addon_token_id")
|
|
nodeId String @map("node_id")
|
|
mediaFileId String @map("media_file_id")
|
|
status PlaybackSessionStatus @default(ACTIVE)
|
|
idleTimeoutSeconds Int @map("idle_timeout_seconds")
|
|
absoluteExpiresAt DateTime @map("absolute_expires_at")
|
|
lastActivity DateTime @default(now()) @map("last_activity")
|
|
bytesSent BigInt @default(0) @map("bytes_sent")
|
|
lastByteOffset BigInt @default(0) @map("last_byte_offset")
|
|
fileSizeBytes BigInt @default(0) @map("file_size_bytes")
|
|
revoked Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
node Node @relation(fields: [nodeId], references: [id])
|
|
mediaFile MediaFile @relation(fields: [mediaFileId], references: [id])
|
|
addonToken AddonToken? @relation(fields: [addonTokenId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([status, lastActivity])
|
|
@@index([addonTokenId])
|
|
@@map("playback_sessions")
|
|
}
|
|
|
|
/** Synology Download Station credentials + fixed 4K import paths (per node). */
|
|
model SynologyDsConfig {
|
|
id String @id @default(uuid())
|
|
nodeId String @unique @map("node_id")
|
|
/** DSM base URL, e.g. https://nas.local:5001 */
|
|
baseUrl String @map("base_url")
|
|
username String
|
|
passwordEnc String @map("password_enc")
|
|
/** DS task destination (share-relative), e.g. Downloads/complete */
|
|
downloadDestination String @map("download_destination")
|
|
/** Absolute host path of that destination on the media-node */
|
|
downloadHostPath String @map("download_host_path")
|
|
/** Absolute host path for 4K movies library folder */
|
|
fourKHostPath String @map("four_k_host_path")
|
|
enabled Boolean @default(true)
|
|
lastOkAt DateTime? @map("last_ok_at")
|
|
lastError String? @map("last_error")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
node Node @relation(fields: [nodeId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("synology_ds_configs")
|
|
}
|
|
|
|
model DownloadJob {
|
|
id String @id @default(uuid())
|
|
nodeId String @map("node_id")
|
|
status DownloadJobStatus @default(DOWNLOADING)
|
|
sourceUri String @map("source_uri")
|
|
sourceTitle String @map("source_title")
|
|
dsTaskId String? @map("ds_task_id")
|
|
tmdbId Int? @map("tmdb_id")
|
|
imdbId String? @map("imdb_id")
|
|
movieTitle String @map("movie_title")
|
|
movieYear Int? @map("movie_year")
|
|
posterUrl String? @map("poster_url")
|
|
destFolderName String? @map("dest_folder_name")
|
|
sourcePath String? @map("source_path")
|
|
importedPath String? @map("imported_path")
|
|
releaseTags String? @map("release_tags")
|
|
subtitleFileId Int? @map("subtitle_file_id")
|
|
subtitleLang String? @map("subtitle_lang")
|
|
subtitleSource String? @map("subtitle_source")
|
|
error String?
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
node Node @relation(fields: [nodeId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([status, updatedAt])
|
|
@@index([nodeId, status])
|
|
@@map("download_jobs")
|
|
}
|
|
|
|
/** Singleton-style integration credentials (OpenSubtitles, etc.). */
|
|
model IntegrationSetting {
|
|
id String @id
|
|
apiKeyEnc String? @map("api_key_enc")
|
|
username String?
|
|
passwordEnc String? @map("password_enc")
|
|
enabled Boolean @default(true)
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
@@map("integration_settings")
|
|
}
|
|
|
|
/** Sidecar / OpenSubtitles text stored for Stremio stream.subtitles. */
|
|
model LibrarySubtitle {
|
|
id String @id @default(uuid())
|
|
movieId String? @map("movie_id")
|
|
tmdbId Int? @map("tmdb_id")
|
|
imdbId String? @map("imdb_id")
|
|
language String @default("nl")
|
|
format String @default("srt")
|
|
content String
|
|
fileName String? @map("file_name")
|
|
downloadJobId String? @map("download_job_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
movie Movie? @relation(fields: [movieId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([movieId])
|
|
@@index([tmdbId])
|
|
@@index([imdbId])
|
|
@@map("library_subtitles")
|
|
}
|