// Prisma 数据模型定义(TodoList) generator client { provider = "prisma-client" output = "../generated/prisma" } datasource db { provider = "postgresql" } enum UserStatus { ACTIVE DISABLED BANNED } enum AuthProvider { EMAIL GITHUB QQ WECHAT } enum TaskPriority { LOW MEDIUM HIGH URGENT } enum TaskStatus { TODO IN_PROGRESS DONE ARCHIVED } enum AttachmentType { IMAGE VIDEO FILE LINK } enum AiChannel { USER_KEY ASTRBOT PUBLIC_POOL } enum NotificationChannel { EMAIL WEB_PUSH } enum NotificationStatus { PENDING SENT FAILED CANCELED } model User { id String @id @default(cuid()) email String emailHash String? @unique nickname String? avatarUrl String? status UserStatus @default(ACTIVE) defaultStorageQuotaMb Int @default(100) usedStorageBytes BigInt @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt identities AuthIdentity[] refreshTokens RefreshToken[] security UserSecurity? tasks Task[] tags Tag[] attachments Attachment[] taskActivityLogs TaskActivityLog[] syncOperations SyncOperation[] syncCursors SyncCursor[] taskTombstones TaskTombstone[] aiProviderBindings AiProviderBinding[] aiUsageLogs AiUsageLog[] notificationRules NotificationRule[] notificationJobs NotificationJob[] createdAdminTokens AdminToken[] auditLogs AuditLog[] @@map("users") } model AuthIdentity { id String @id @default(cuid()) userId String provider AuthProvider providerUserId String email String? emailHash String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerUserId]) @@index([emailHash]) @@index([userId]) @@map("auth_identities") } model UserSecurity { id String @id @default(cuid()) userId String @unique twoFactorEnabled Boolean @default(false) twoFactorSecret String? recoveryCodes String[] @default([]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("user_security") } model RefreshToken { id String @id @default(cuid()) userId String tokenHash String @unique deviceId String? expiresAt DateTime revokedAt DateTime? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, expiresAt]) @@map("refresh_tokens") } model Task { id String @id @default(cuid()) userId String title String contentJson Json? contentText String? priority TaskPriority @default(MEDIUM) status TaskStatus @default(TODO) ddl DateTime? completedAt DateTime? version Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) taskTags TaskTag[] attachments Attachment[] activityLogs TaskActivityLog[] notificationJobs NotificationJob[] notificationRules NotificationRule[] @@index([userId, status]) @@index([userId, ddl]) @@map("tasks") } model Tag { id String @id @default(cuid()) userId String name String color String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) taskTags TaskTag[] @@unique([userId, name]) @@index([userId]) @@map("tags") } model TaskTag { taskId String tagId String createdAt DateTime @default(now()) task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([taskId, tagId]) @@index([tagId]) @@map("task_tags") } model Attachment { id String @id @default(cuid()) userId String taskId String? type AttachmentType url String mimeType String? fileName String? fileSize Int width Int? height Int? durationMs Int? checksum String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull) @@index([userId]) @@index([taskId]) @@map("attachments") } model TaskActivityLog { id String @id @default(cuid()) userId String taskId String action String payload Json? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) @@index([taskId, createdAt]) @@index([userId, createdAt]) @@map("task_activity_logs") } model SyncOperation { id String @id @default(cuid()) opId String @unique userId String deviceId String entityType String entityId String action String payload Json? clientTs DateTime serverTs DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, deviceId, serverTs]) @@index([userId, entityType, entityId]) @@map("sync_operations") } model SyncCursor { id String @id @default(cuid()) userId String deviceId String lastPulledAt DateTime? lastOperationServerTs DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, deviceId]) @@map("sync_cursors") } model TaskTombstone { id String @id @default(cuid()) taskId String @unique userId String deletedAt DateTime @default(now()) deleteOpId String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, deletedAt]) @@map("task_tombstones") } model AiProviderBinding { id String @id @default(cuid()) userId String channel AiChannel providerName String model String? configId String? configName String? encryptedApiKey String? endpoint String? isDefault Boolean @default(false) isEnabled Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, isEnabled]) @@map("ai_provider_bindings") } model AiPublicPoolConfig { id String @id @default(cuid()) enabled Boolean @default(false) providerName String? model String? encryptedApiKey String? endpoint String? rpmLimit Int @default(60) dailyTokenLimit Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("ai_public_pool_config") } model AiUsageLog { id String @id @default(cuid()) userId String? channel AiChannel providerName String? model String? promptTokens Int @default(0) completionTokens Int @default(0) totalTokens Int @default(0) latencyMs Int? success Boolean @default(true) errorCode String? createdAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId, createdAt]) @@index([channel, createdAt]) @@map("ai_usage_logs") } model NotificationRule { id String @id @default(cuid()) userId String taskId String? channel NotificationChannel @default(EMAIL) advanceMinutes Int @default(60) enabled Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull) jobs NotificationJob[] @@index([userId, enabled]) @@index([taskId]) @@map("notification_rules") } model NotificationJob { id String @id @default(cuid()) userId String taskId String? ruleId String? channel NotificationChannel scheduledAt DateTime sentAt DateTime? status NotificationStatus @default(PENDING) retryCount Int @default(0) errorMessage String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull) rule NotificationRule? @relation(fields: [ruleId], references: [id], onDelete: SetNull) @@index([status, scheduledAt]) @@index([userId, createdAt]) @@map("notification_jobs") } model SystemSetting { id String @id @default(cuid()) key String @unique value Json createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@map("system_settings") } model AdminToken { id String @id @default(cuid()) tokenHash String @unique name String expiresAt DateTime lastUsedAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) createdByUserId String? createdByUser User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull) @@index([expiresAt]) @@map("admin_tokens") } model AuditLog { id String @id @default(cuid()) actorUserId String? action String targetType String targetId String? meta Json? ip String? userAgent String? createdAt DateTime @default(now()) actorUser User? @relation(fields: [actorUserId], references: [id], onDelete: SetNull) @@index([action, createdAt]) @@index([actorUserId, createdAt]) @@map("audit_logs") }