493 lines
15 KiB
Plaintext
493 lines
15 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ─── USERS ────────────────────────────────────────────────
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
name String?
|
|
googleId String @unique
|
|
isAppAdmin Boolean @default(false)
|
|
preferences Json?
|
|
teams TeamMember[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
// ─── TEAMS ────────────────────────────────────────────────
|
|
|
|
model Team {
|
|
id String @id @default(cuid())
|
|
name String
|
|
slug String @unique
|
|
dateFormat String?
|
|
timezone String @default("UTC")
|
|
publishingSchedule Json?
|
|
showCanvaLink Boolean @default(false)
|
|
disabledLintRules String[] @default([])
|
|
showDeletedVideos Boolean @default(false)
|
|
conflictDetectionEnabled Boolean @default(false)
|
|
conflictDetectionBatchSize Int @default(50)
|
|
conflictDetectionMinAgeDays Int @default(7)
|
|
members TeamMember[]
|
|
channels Channel[]
|
|
blocks DescriptionBlock[]
|
|
templates Template[]
|
|
collaborators Collaborator[]
|
|
bulkJobs BulkJob[]
|
|
savedViews SavedView[]
|
|
importJobs ImportJob[]
|
|
exportJobs ExportJob[]
|
|
campaigns Campaign[]
|
|
variables TeamVariable[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model TeamVariable {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
name String
|
|
value String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([teamId, name])
|
|
@@index([teamId])
|
|
}
|
|
|
|
model TeamMember {
|
|
userId String
|
|
teamId String
|
|
role TeamRole @default(EDITOR)
|
|
user User @relation(fields: [userId], references: [id])
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
createdAt DateTime @default(now())
|
|
|
|
@@id([userId, teamId])
|
|
@@index([teamId])
|
|
}
|
|
|
|
enum TeamRole {
|
|
OWNER
|
|
ADMIN
|
|
EDITOR
|
|
REVIEWER
|
|
READONLY
|
|
}
|
|
|
|
// ─── CHANNELS ─────────────────────────────────────────────
|
|
|
|
model Channel {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
youtubeChannelId String @unique
|
|
name String
|
|
uploadsPlaylistId String?
|
|
supplementalVideoIds String[]
|
|
youtubeAccessToken String?
|
|
youtubeRefreshToken String?
|
|
youtubeTokenExpiry DateTime?
|
|
connectedBy String
|
|
videos Video[]
|
|
playlists Playlist[]
|
|
quotaLogs QuotaLog[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
// ─── VIDEOS ───────────────────────────────────────────────
|
|
|
|
model Video {
|
|
id String @id @default(cuid())
|
|
youtubeVideoId String @unique
|
|
channelId String
|
|
channel Channel @relation(fields: [channelId], references: [id])
|
|
title String
|
|
youtubeDescription String?
|
|
renderedDescription String?
|
|
thumbnailUrl String?
|
|
tags String[]
|
|
categoryId String?
|
|
privacyStatus PrivacyStatus @default(PRIVATE)
|
|
publishedAt DateTime?
|
|
scheduledAt DateTime?
|
|
templateId String?
|
|
// Extended metadata
|
|
madeForKids Boolean @default(false)
|
|
selfDeclaredMadeForKids Boolean @default(false)
|
|
containsPaidPromotion Boolean @default(false)
|
|
ageRestricted Boolean @default(false)
|
|
embeddable Boolean @default(true)
|
|
license String @default("youtube")
|
|
defaultLanguage String?
|
|
defaultAudioLanguage String?
|
|
recordingDate DateTime?
|
|
gameTitle String?
|
|
collaboratorIds Json @default("[]")
|
|
youtubeSnapshot Json?
|
|
lintStatus LintStatus @default(OK)
|
|
lastSyncedAt DateTime?
|
|
lastSyncedHash String?
|
|
remoteConflict Boolean @default(false)
|
|
pendingRemoteSnapshot Json?
|
|
pendingRemoteDescription String?
|
|
youtubeDeletedAt DateTime?
|
|
template Template? @relation(fields: [templateId], references: [id])
|
|
config VideoConfig?
|
|
playlists VideoPlaylist[]
|
|
lintResults LintResult[]
|
|
bulkJobItems BulkJobItem[]
|
|
quotaLogs QuotaLog[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([channelId])
|
|
}
|
|
|
|
enum PrivacyStatus {
|
|
PUBLIC
|
|
PRIVATE
|
|
UNLISTED
|
|
}
|
|
|
|
enum LintStatus {
|
|
OK
|
|
WARNING
|
|
ERROR
|
|
}
|
|
|
|
// ─── VIDEO CONFIG ─────────────────────────────────────────
|
|
|
|
model VideoConfig {
|
|
id String @id @default(cuid())
|
|
videoId String @unique
|
|
video Video @relation(fields: [videoId], references: [id])
|
|
templateId String?
|
|
blockOrder Json
|
|
blockOverrides Json
|
|
variableValues Json
|
|
version Int @default(1)
|
|
renderHash String?
|
|
renderedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
// ─── DESCRIPTION BLOCKS ───────────────────────────────────
|
|
|
|
model DescriptionBlock {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
name String
|
|
type BlockType
|
|
content String
|
|
language String @default("de")
|
|
campaignId String?
|
|
campaign Campaign? @relation(fields: [campaignId], references: [id])
|
|
version Int @default(1)
|
|
active Boolean @default(true)
|
|
compact Boolean @default(false)
|
|
tags String[]
|
|
variableDefinitions Json @default("[]")
|
|
condition Json?
|
|
versions BlockVersion[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
enum BlockType {
|
|
STATIC
|
|
VARIABLE
|
|
CONDITIONAL
|
|
REPEATABLE
|
|
GLOBAL
|
|
CAMPAIGN
|
|
COLLABORATOR
|
|
}
|
|
|
|
model BlockVersion {
|
|
id String @id @default(cuid())
|
|
blockId String
|
|
block DescriptionBlock @relation(fields: [blockId], references: [id])
|
|
version Int
|
|
contentSnapshot Json
|
|
createdAt DateTime @default(now())
|
|
createdBy String?
|
|
}
|
|
|
|
// ─── TEMPLATES ────────────────────────────────────────────
|
|
|
|
model Template {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
name String
|
|
description String?
|
|
defaultBlocks Json
|
|
defaultOverrides Json @default("{}")
|
|
rules Json
|
|
variables Json
|
|
videoFields Json?
|
|
version Int @default(1)
|
|
active Boolean @default(true)
|
|
videos Video[]
|
|
versions TemplateVersion[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
model TemplateVersion {
|
|
id String @id @default(cuid())
|
|
templateId String
|
|
template Template @relation(fields: [templateId], references: [id])
|
|
version Int
|
|
snapshot Json
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
// ─── COLLABORATORS ────────────────────────────────────────
|
|
|
|
model Collaborator {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
name String
|
|
youtubeLink String?
|
|
twitchLink String?
|
|
instagramLink String?
|
|
tiktokLink String?
|
|
twitterLink String?
|
|
blueskyLink String?
|
|
discordHandle String?
|
|
aliases String[]
|
|
active Boolean @default(true)
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
// ─── SAVED VIEWS ──────────────────────────────────────────
|
|
|
|
model SavedView {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
name String
|
|
description String?
|
|
isGlobal Boolean @default(false)
|
|
ownerId String?
|
|
queryJson Json
|
|
columnsJson Json
|
|
sortJson Json?
|
|
pinnedAsTab Boolean @default(false)
|
|
tabOrder Int?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
// ─── LINT RESULTS ─────────────────────────────────────────
|
|
|
|
model LintResult {
|
|
id String @id @default(cuid())
|
|
videoId String
|
|
video Video @relation(fields: [videoId], references: [id])
|
|
ruleCode String
|
|
severity LintSeverity
|
|
targetField String?
|
|
message String
|
|
fixSuggestion String?
|
|
resolvedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([videoId, severity])
|
|
@@index([ruleCode])
|
|
}
|
|
|
|
enum LintSeverity {
|
|
INFO
|
|
WARNING
|
|
ERROR
|
|
}
|
|
|
|
// ─── BULK JOBS ────────────────────────────────────────────
|
|
|
|
model BulkJob {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
type String
|
|
initiatedBy String
|
|
filterSnapshot Json
|
|
targetIds String[]
|
|
status BulkJobStatus @default(PENDING)
|
|
dryRunResult Json?
|
|
rollbackData Json?
|
|
totalCount Int
|
|
successCount Int @default(0)
|
|
errorCount Int @default(0)
|
|
items BulkJobItem[]
|
|
createdAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
enum BulkJobStatus {
|
|
PENDING
|
|
DRY_RUN
|
|
CONFIRMED
|
|
RUNNING
|
|
DONE
|
|
FAILED
|
|
ROLLED_BACK
|
|
}
|
|
|
|
model BulkJobItem {
|
|
id String @id @default(cuid())
|
|
bulkJobId String
|
|
bulkJob BulkJob @relation(fields: [bulkJobId], references: [id])
|
|
videoId String
|
|
video Video @relation(fields: [videoId], references: [id])
|
|
beforeSnapshot Json?
|
|
afterSnapshot Json?
|
|
status String @default("pending")
|
|
errorMessage String?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
// ─── CAMPAIGNS ────────────────────────────────────────────
|
|
|
|
model Campaign {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
name String
|
|
startAt DateTime
|
|
endAt DateTime?
|
|
status String @default("active")
|
|
notes String?
|
|
blocks DescriptionBlock[]
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
// ─── PLAYLISTS ────────────────────────────────────────────
|
|
|
|
model Playlist {
|
|
id String @id @default(cuid())
|
|
channelId String
|
|
channel Channel @relation(fields: [channelId], references: [id])
|
|
youtubePlaylistId String @unique
|
|
title String
|
|
description String?
|
|
itemCount Int @default(0)
|
|
privacyStatus String @default("public")
|
|
videos VideoPlaylist[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([channelId])
|
|
}
|
|
|
|
model VideoPlaylist {
|
|
videoId String
|
|
playlistId String
|
|
position Int?
|
|
video Video @relation(fields: [videoId], references: [id])
|
|
playlist Playlist @relation(fields: [playlistId], references: [id])
|
|
|
|
@@id([videoId, playlistId])
|
|
}
|
|
|
|
// ─── IMPORT / EXPORT JOBS ─────────────────────────────────
|
|
|
|
model ImportJob {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
type String
|
|
sourceName String
|
|
mappingJson Json?
|
|
validationReport Json?
|
|
commitStatus String @default("pending")
|
|
createdBy String
|
|
createdAt DateTime @default(now())
|
|
committedAt DateTime?
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
model ExportJob {
|
|
id String @id @default(cuid())
|
|
teamId String
|
|
team Team @relation(fields: [teamId], references: [id])
|
|
type String
|
|
scopeJson Json
|
|
fileReference String?
|
|
createdBy String
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([teamId])
|
|
}
|
|
|
|
// ─── YOUTUBE QUOTA LOG ────────────────────────────────────
|
|
|
|
model QuotaLog {
|
|
id String @id @default(cuid())
|
|
datePt DateTime
|
|
units Int
|
|
operation String
|
|
entityId String?
|
|
channelId String?
|
|
channel Channel? @relation(fields: [channelId], references: [id])
|
|
videoId String?
|
|
video Video? @relation(fields: [videoId], references: [id])
|
|
bulkJobId String?
|
|
actionId String?
|
|
actionType String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([datePt])
|
|
@@index([channelId])
|
|
@@index([actionId])
|
|
}
|
|
|
|
// ─── AUDIT LOG ────────────────────────────────────────────
|
|
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
actorId String
|
|
entityType String
|
|
entityId String
|
|
action String
|
|
beforeJson Json?
|
|
afterJson Json?
|
|
requestId String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([entityType, entityId])
|
|
@@index([createdAt])
|
|
}
|