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]) }