708 lines
26 KiB
Text
708 lines
26 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
|
|
}
|
|
|
|
enum GooglePlaySyncStatus {
|
|
NOT_ENABLED
|
|
PENDING_ADD
|
|
ACTIVE
|
|
PENDING_REMOVE
|
|
REMOVED
|
|
ERROR
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
/** App-gebruiker (Android/iOS/Tizen) — huishouden met optioneel IPTV + Stremio-addons. */
|
|
model ViewerUser {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
passwordHash String @map("password_hash")
|
|
name String?
|
|
enabled Boolean @default(true)
|
|
appAccess Boolean @default(true) @map("app_access")
|
|
/** Live TV in de apps (Xtream-lijn op deze gebruiker). */
|
|
iptvAccess Boolean @default(false) @map("iptv_access")
|
|
/** @deprecated Legacy: IPTV via addon-token; voorkeur = iptvLine op deze user. */
|
|
iptvAddonTokenId String? @map("iptv_addon_token_id")
|
|
/** Google-account voor Play Closed Testing; fallback = email */
|
|
googlePlayEmail String? @map("google_play_email")
|
|
/** Gewenste Play-distributietoegang (sync met Google Group) */
|
|
googlePlayAccess Boolean @default(false) @map("google_play_access")
|
|
googlePlayMembershipId String? @map("google_play_membership_id")
|
|
googlePlaySyncStatus GooglePlaySyncStatus @default(NOT_ENABLED) @map("google_play_sync_status")
|
|
googlePlayLastSyncedAt DateTime? @map("google_play_last_synced_at")
|
|
googlePlaySyncError String? @map("google_play_sync_error") @db.Text
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
devices ViewerDevice[]
|
|
profiles ViewerProfile[]
|
|
linkCodes DeviceLinkCode[]
|
|
googlePlayAuditLogs GooglePlayAccessAuditLog[]
|
|
/** Legacy koppeling — behouden voor migratie. */
|
|
iptvAddonToken AddonToken? @relation("ViewerLegacyIptvToken", fields: [iptvAddonTokenId], references: [id], onDelete: SetNull)
|
|
iptvLine IptvLine?
|
|
addonTokens AddonToken[] @relation("ViewerAddonTokens")
|
|
|
|
@@map("viewer_users")
|
|
}
|
|
|
|
/** Netflix-stijl kijkprofiel onder één ViewerUser (voortgang/prefs per profiel). */
|
|
model ViewerProfile {
|
|
id String @id @default(uuid())
|
|
viewerUserId String @map("viewer_user_id")
|
|
name String
|
|
avatarId String @default("fox") @map("avatar_id")
|
|
isOwner Boolean @default(false) @map("is_owner")
|
|
isKids Boolean @default(false) @map("is_kids")
|
|
pinHash String? @map("pin_hash")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
viewer ViewerUser @relation(fields: [viewerUserId], references: [id], onDelete: Cascade)
|
|
devices ViewerDevice[]
|
|
progress WatchProgress[]
|
|
shelfPrefs ViewerShelfPreference[]
|
|
favorites ViewerFavorite[]
|
|
|
|
@@unique([viewerUserId, name])
|
|
@@index([viewerUserId])
|
|
@@map("viewer_profiles")
|
|
}
|
|
|
|
model ViewerDevice {
|
|
id String @id @default(uuid())
|
|
viewerUserId String @map("viewer_user_id")
|
|
name String
|
|
platform String @default("android_tv")
|
|
refreshTokenHash String @unique @map("refresh_token_hash")
|
|
activeProfileId String? @map("active_profile_id")
|
|
lastSeenAt DateTime? @map("last_seen_at")
|
|
revoked Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
viewer ViewerUser @relation(fields: [viewerUserId], references: [id], onDelete: Cascade)
|
|
activeProfile ViewerProfile? @relation(fields: [activeProfileId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([viewerUserId])
|
|
@@index([activeProfileId])
|
|
@@map("viewer_devices")
|
|
}
|
|
|
|
/** Kortstondige koppelcode (TV toont code → gebruiker claimt op /link). */
|
|
model DeviceLinkCode {
|
|
id String @id @default(uuid())
|
|
codeHash String @unique @map("code_hash")
|
|
/** Display-code (alleen tot claim; daarna null) */
|
|
codeDisplay String? @map("code_display")
|
|
deviceName String @map("device_name")
|
|
platform String @default("android_tv")
|
|
expiresAt DateTime @map("expires_at")
|
|
consumedAt DateTime? @map("consumed_at")
|
|
viewerUserId String? @map("viewer_user_id")
|
|
/** Eenmalig token voor device poll na claim */
|
|
claimTokenHash String? @unique @map("claim_token_hash")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
viewer ViewerUser? @relation(fields: [viewerUserId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([expiresAt])
|
|
@@map("device_link_codes")
|
|
}
|
|
|
|
enum WatchMediaType {
|
|
MOVIE
|
|
EPISODE
|
|
}
|
|
|
|
model WatchProgress {
|
|
id String @id @default(uuid())
|
|
profileId String @map("profile_id")
|
|
mediaType WatchMediaType @map("media_type")
|
|
movieId String? @map("movie_id")
|
|
episodeId String? @map("episode_id")
|
|
seriesId String? @map("series_id")
|
|
positionMs Int @default(0) @map("position_ms")
|
|
durationMs Int @default(0) @map("duration_ms")
|
|
completed Boolean @default(false)
|
|
hiddenFromContinue Boolean @default(false) @map("hidden_from_continue")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
profile ViewerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([profileId, movieId])
|
|
@@unique([profileId, episodeId])
|
|
@@index([profileId, updatedAt])
|
|
@@index([profileId, hiddenFromContinue, completed])
|
|
@@map("watch_progress")
|
|
}
|
|
|
|
model ViewerShelfPreference {
|
|
id String @id @default(uuid())
|
|
profileId String @map("profile_id")
|
|
shelfId String @map("shelf_id")
|
|
visible Boolean @default(true)
|
|
sortOrder Int @default(0) @map("sort_order")
|
|
|
|
profile ViewerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([profileId, shelfId])
|
|
@@index([profileId, sortOrder])
|
|
@@map("viewer_shelf_preferences")
|
|
}
|
|
|
|
enum FavoriteMediaType {
|
|
MOVIE
|
|
SERIES
|
|
}
|
|
|
|
/** Favorieten/bookmarks per kijkprofiel — gesynchroniseerd over devices. */
|
|
model ViewerFavorite {
|
|
id String @id @default(uuid())
|
|
profileId String @map("profile_id")
|
|
mediaType FavoriteMediaType @map("media_type")
|
|
mediaId String @map("media_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
profile ViewerProfile @relation(fields: [profileId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([profileId, mediaType, mediaId])
|
|
@@index([profileId, createdAt])
|
|
@@map("viewer_favorites")
|
|
}
|
|
|
|
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")
|
|
/** Gebruiker (huishouden) waaraan deze Stremio-addon hangt. */
|
|
viewerUserId String? @map("viewer_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)
|
|
viewerUser ViewerUser? @relation("ViewerAddonTokens", fields: [viewerUserId], references: [id], onDelete: SetNull)
|
|
iptvLine IptvLine?
|
|
playbackSessions PlaybackSession[]
|
|
legacyIptvViewers ViewerUser[] @relation("ViewerLegacyIptvToken")
|
|
|
|
@@index([viewerUserId])
|
|
@@map("addon_tokens")
|
|
}
|
|
|
|
/** Xtream/XUI credentials — bij voorkeur per ViewerUser; legacy optioneel per AddonToken. */
|
|
model IptvLine {
|
|
id String @id @default(uuid())
|
|
/** Primair: IPTV-lijn van één app-gebruiker. */
|
|
viewerUserId String? @unique @map("viewer_user_id")
|
|
/** Legacy: IPTV direct op Stremio-addon (pre-merge). */
|
|
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")
|
|
|
|
viewerUser ViewerUser? @relation(fields: [viewerUserId], references: [id], onDelete: Cascade)
|
|
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
|
|
homepageName String? @map("homepage_name")
|
|
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")
|
|
logoUrl String? @map("logo_url")
|
|
genres String[] @default([])
|
|
/** TMDB adult-flag; kids-profielen filteren hierop. */
|
|
adult Boolean @default(false)
|
|
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[]
|
|
|
|
@@index([adult])
|
|
@@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([])
|
|
/** TMDB adult-flag; kids-profielen filteren hierop. */
|
|
adult Boolean @default(false)
|
|
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[]
|
|
|
|
@@index([adult])
|
|
@@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")
|
|
/** Absolute path on the media-node host (filled from node ≥1.3.7). */
|
|
path String? @map("path")
|
|
available Boolean @default(true)
|
|
lastSeen DateTime @default(now()) @map("last_seen")
|
|
/// Eerste keer dat dit bestand in de library kwam (wijzigt niet bij rematch/upsert).
|
|
discoveredAt DateTime @default(now()) @map("discovered_at")
|
|
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])
|
|
@@index([discoveredAt])
|
|
@@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 + library 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 1080p movies (e.g. …/Media/Films/Films) */
|
|
filmsHostPath String @default("") @map("films_host_path")
|
|
/** Absolute host path for 4K movies (e.g. …/Media/Films/4K) */
|
|
fourKHostPath String @map("four_k_host_path")
|
|
/** Absolute host path for series (e.g. …/Media/Series/Series) */
|
|
seriesHostPath String @default("") @map("series_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")
|
|
}
|
|
|
|
enum DownloadLibraryTarget {
|
|
FILMS
|
|
FOUR_K
|
|
SERIES
|
|
}
|
|
|
|
enum DownloadSeriesMode {
|
|
EPISODE
|
|
SEASON
|
|
}
|
|
|
|
model DownloadJob {
|
|
id String @id @default(uuid())
|
|
nodeId String @map("node_id")
|
|
status DownloadJobStatus @default(DOWNLOADING)
|
|
/** Where to import after DS finishes */
|
|
libraryTarget DownloadLibraryTarget @default(FOUR_K) @map("library_target")
|
|
/** Series only: single episode vs whole season pack */
|
|
seriesMode DownloadSeriesMode? @map("series_mode")
|
|
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")
|
|
seasonNumber Int? @map("season_number")
|
|
episodeNumber Int? @map("episode_number")
|
|
episodeTitle String? @map("episode_title")
|
|
/** Season pack tracking JSON */
|
|
episodesJson String? @map("episodes_json") @db.Text
|
|
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")
|
|
/** Cached subtitle body (downloaded at job create) */
|
|
subtitleContent String? @map("subtitle_content") @db.Text
|
|
subtitleError String? @map("subtitle_error")
|
|
subtitlePath String? @map("subtitle_path")
|
|
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")
|
|
/** Must match OpenSubtitles.com API Consumer / User-Agent exactly */
|
|
userAgent String? @map("user_agent")
|
|
/** Non-secret JSON config (e.g. Google Play group/project fields) */
|
|
configJson String? @map("config_json") @db.Text
|
|
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")
|
|
}
|
|
|
|
/** Audit trail voor Google Play group-lidmaatschap (geen secrets). */
|
|
model GooglePlayAccessAuditLog {
|
|
id String @id @default(uuid())
|
|
adminUserId String? @map("admin_user_id")
|
|
adminEmail String? @map("admin_email")
|
|
viewerUserId String @map("viewer_user_id")
|
|
targetEmail String @map("target_email")
|
|
action String
|
|
result String
|
|
membershipId String? @map("membership_id")
|
|
errorMessage String? @map("error_message") @db.Text
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
viewer ViewerUser @relation(fields: [viewerUserId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([viewerUserId, createdAt])
|
|
@@map("google_play_access_audit_logs")
|
|
}
|