generator client { provider = "prisma-client-js" binaryTargets = ["native", "linux-musl-openssl-3.0.x"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum Role { USER ADMIN } model User { id String @id @default(uuid()) email String @unique username String @unique password String role Role @default(USER) isActive Boolean @default(false) canDownload Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt department String @default("") dingtalkUserId String? @unique loginType String @default("normal") realName String? phone String? @unique avatar String? managedDepartment String? isSuperAdmin Boolean @default(false) parentId String? likes MaterialLike[] materials Material[] downloadLogs MaterialDownloadLog[] materialLogs MaterialLog[] parent User? @relation("UserHierarchy", fields: [parentId], references: [id]) children User[] @relation("UserHierarchy") favorites MaterialFavorite[] browseHistory BrowseHistory[] dingtalkLoginTokens DingtalkLoginToken[] materialShareTokens MaterialShareToken[] @@map("users") } model DingtalkLoginToken { id String @id @default(uuid()) tokenHash String @unique userId String purpose String @default("login") expiresAt DateTime usedAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([purpose]) @@index([expiresAt]) @@map("dingtalk_login_tokens") } model MaterialShareToken { id String @id @default(uuid()) tokenHash String @unique materialId String createdByUserId String expiresAt DateTime revokedAt DateTime? createdAt DateTime @default(now()) material Material @relation(fields: [materialId], references: [id], onDelete: Cascade) createdBy User @relation(fields: [createdByUserId], references: [id], onDelete: Cascade) @@index([materialId]) @@index([createdByUserId]) @@index([expiresAt]) @@index([revokedAt]) @@map("material_share_tokens") } model UploadTemplate { id String @id @default(uuid()) name String description String? status String @default("draft") materialType String @default("其他") /// 模板素材类型大类:IMAGE / VIDEO / OTHER(与具体 materialType 文案解耦) materialKind String @default("IMAGE") version Int @default(1) fieldGroups String? displayConfig String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt fields UploadField[] materials Material[] @@index([status]) @@index([materialType]) @@index([materialKind]) @@index([createdAt]) @@index([updatedAt]) } model UploadField { id String @id @default(uuid()) templateId String key String label String type String required Boolean @default(false) orderIndex Int @default(0) layoutWidth String? validations String? optionsSource String? optionsStatic String? allowedExtensions String? visibleOnHomeFilter Boolean @default(false) isSearchable Boolean @default(false) isReadOnly Boolean @default(false) addToCategory Boolean @default(false) conditions String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt template UploadTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade) @@index([templateId]) @@index([key]) @@index([type]) @@index([visibleOnHomeFilter]) @@index([isSearchable]) @@index([addToCategory]) @@index([orderIndex]) @@index([templateId, orderIndex]) } model FieldDefinition { id String @id @default(uuid()) key String @unique label String type String @default("select") description String? source String @default("custom") // standard | custom sourceTemplateIds String? // JSON array of template ids referencing this field isStandard Boolean @default(false) isFilterEnabled Boolean @default(false) filterDisplayName String? filterOrder Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt options FieldOption[] @@index([source]) @@index([isStandard]) @@index([isFilterEnabled]) @@index([filterOrder]) @@index([isFilterEnabled, filterOrder]) } model FieldOption { id String @id @default(uuid()) fieldId String value String label String orderIndex Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt field FieldDefinition @relation(fields: [fieldId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@unique([fieldId, value]) @@index([fieldId]) @@index([fieldId, orderIndex]) } model FilterItem { id String @id @default(uuid()) fieldKey String label String group String? orderIndex Int @default(0) multiSelect Boolean @default(false) showOnHome Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Material { id String @id @default(uuid()) previewUrl String productModel String brandName String? seriesName String? skuCode String? materialTexture String? categoryName String sceneName String materialType String @default("3DMAX") materialKind String @default("IMAGE") // 素材大类:IMAGE / VIDEO / OTHER renderer String @default("CR") renderTime String? imageResolution String? colorMode String? audioDuration String? // 音频时长,如:00:30、1:25 audioCategory String? // 音频分类,如:背景音乐、音效、配音 audioScene String? // 音频使用场景,如:广告、产品视频、直播 audioUrl String? // 音频文件URL,用于在线播放 authorName String /// 素材归属类型:USER(个人负责)/ TEAM(团队空间) ownerType String @default("TEAM") /// 素材归属主体ID:当 ownerType=USER 时为用户ID;ownerType=TEAM 时为团队/空间ID ownerId String @default("default-team") /// 最初上传者用户ID(离职后仍然保留,便于追溯) createdByUserId String? /// 最近一次所有权移交时间 lastTransferredAt DateTime? zipFileName String @default("") zipFilePath String @default("") zipFileSize BigInt @default(0) // 视频类素材相关字段(为后续视频预览 / 转码预留) videoSourceUrl String? // 源视频文件 URL videoPreview540Url String? // 540P 预览视频 URL videoPreviewClipUrl String? // 首页卡片用 10 秒超小预览视频 URL videoCoverUrl String? // 从视频帧中截取的封面图 URL videoDuration Int? // 视频时长(秒) videoProcessingStatus String? // 视频处理状态:pending / processing / done / failed downloadCount Int @default(0) likeCount Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String displayImages String @default("") platformName String @default("") previewUrls String? // 多预览图URLs(JSON数组格式) metadata String? templateId String? isPremium Boolean @default(false) deletedAt DateTime? // 软删除时间戳 likes MaterialLike[] favorites MaterialFavorite[] downloadLogs MaterialDownloadLog[] browseHistory BrowseHistory[] shareTokens MaterialShareToken[] videoTranscodeJob VideoTranscodeJob? template UploadTemplate? @relation(fields: [templateId], references: [id]) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([ownerType, ownerId]) @@index([createdByUserId]) @@index([createdAt]) @@index([categoryName]) @@index([sceneName]) @@index([platformName]) @@index([materialType]) @@index([downloadCount]) @@index([likeCount]) @@index([deletedAt]) @@map("materials") } model VideoTranscodeJob { id String @id @default(uuid()) materialId String @unique videoSourceUrl String status String @default("queued") // queued / processing / done / failed attempts Int @default(0) maxAttempts Int @default(3) needsCoverImage Boolean @default(true) errorMessage String? lockedAt DateTime? startedAt DateTime? finishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt material Material @relation(fields: [materialId], references: [id], onDelete: Cascade) @@index([status, updatedAt]) @@index([lockedAt]) @@map("video_transcode_jobs") } model MaterialLike { id String @id @default(uuid()) materialId String userId String createdAt DateTime @default(now()) material Material @relation(fields: [materialId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([materialId, userId]) @@index([materialId]) @@index([userId]) @@map("material_likes") } model MaterialFavorite { id String @id @default(uuid()) materialId String userId String createdAt DateTime @default(now()) material Material @relation(fields: [materialId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([materialId, userId]) @@index([materialId]) @@index([userId]) @@map("material_favorites") } model MaterialDownloadLog { id String @id @default(uuid()) materialId String userId String downloadedAt DateTime @default(now()) material Material @relation(fields: [materialId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([materialId]) @@index([userId]) @@index([downloadedAt]) @@index([materialId, userId, downloadedAt]) @@map("material_download_logs") } model MaterialLog { id String @id @default(uuid()) materialId String userId String action String // 'UPLOAD', 'UPDATE', 'DELETE', 'TOGGLE_PREMIUM' changes Json? // 详细变更记录,格式:{ "fieldName": { "oldValue": "...", "newValue": "..." } } materialSnapshot Json? // 素材硬删除后仍用于审计展示的最小快照 timestamp DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([materialId]) @@index([userId]) @@index([timestamp]) @@map("material_logs") } model Category { id String @id @default(uuid()) name String @unique options String? // JSON array of options for dropdown createdAt DateTime @default(now()) @@map("categories") } model Scene { id String @id @default(uuid()) name String @unique options String? // JSON array of options for dropdown createdAt DateTime @default(now()) @@map("scenes") } model Platform { id String @id @default(uuid()) name String @unique options String? // JSON array of options for dropdown createdAt DateTime @default(now()) @@map("platforms") } model SystemSettings { id String @id @default(uuid()) headerBgColor String @default("#1f2937") siteName String @default("FLNXI素材库") premiumRatio Float @default(10.0) updatedAt DateTime @updatedAt createdAt DateTime @default(now()) headerLogoUrl String @default("") loginLogoUrl String @default("") s3AccessKey String @default("") s3Bucket String @default("") s3Endpoint String @default("") s3PublicUrl String @default("") s3Region String @default("us-east-1") s3SecretKey String @default("") enableRegistration Boolean @default(true) enableUserActivation Boolean @default(false) enableAiImageEdit Boolean @default(false) enableTunnelService Boolean @default(false) enablePublicShare Boolean @default(true) homeDeleteButtonMode String? @default("ADMIN_ONLY") enabledBuiltinTemplates String? // JSON array of enabled builtin template keys (empty array or null = all enabled) categoryManagedFields String? // JSON array,记录分类管理已启用的字段 key @@map("system_settings") } model Department { id String @id @default(uuid()) name String @unique createdAt DateTime @default(now()) @@map("departments") } model Danmaku { id String @id @default(uuid()) content String username String userId String createdAt DateTime @default(now()) @@index([createdAt]) @@map("danmakus") } model Invitation { id String @id @default(uuid()) code String @unique createdBy String usedBy String? usedAt DateTime? expiresAt DateTime? maxUses Int @default(1) currentUses Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([code]) @@index([createdBy]) @@map("invitations") } model BrowseHistory { id String @id @default(uuid()) materialId String userId String viewedAt DateTime @default(now()) material Material @relation(fields: [materialId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([materialId, userId]) @@index([userId]) @@index([viewedAt]) @@index([userId, viewedAt]) @@map("browse_history") } // 系统配置表(用于存储许可证等配置) model SystemConfig { id String @id @default(uuid()) key String @unique value String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("system_configs") }