// packages/db-cn/prisma/schema.prisma —— CN 侧唯一 schema(Task 8 起步:身份与审计域)。 // 约定:MySQL 8.4、库级 collation utf8mb4_0900_bin(大小写敏感,账号/token 语义依赖它); // 时间一律 @db.DateTime(6);ID cuid;连接池上限由 DATABASE_URL_CN 的 ?connection_limit=10 // 承担,不在应用侧另行放大。 generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL_CN") } model users { id String @id @default(cuid()) account String @unique @db.VarChar(64) passwordHash String @db.VarChar(255) displayName String @db.VarChar(128) role String @default("staff") // staff|admin(公司角色;相册角色另表) sourceStatus String @default("local") // local|active|disabled(P4a 同步用) localLocked Boolean @default(false) credentialStatus String @default("active") // active|pending locale String? @db.VarChar(16) createdAt DateTime @default(now()) @db.DateTime(6) sessions sessions[] auditLogs audit_logs[] } model sessions { id String @id @default(cuid()) userId String tokenHash String @unique @db.VarChar(64) // 长效会话期限(默认 30 天,AUTH_SESSION_TTL_SECONDS 可调;不做短时效 // access/refresh 双层——用户决策 2026-09-17) expiresAt DateTime @db.DateTime(6) revokedAt DateTime? @db.DateTime(6) // 注销/停用撤销 user users @relation(fields: [userId], references: [id]) } model audit_logs { id String @id @default(cuid()) actorId String? action String @db.VarChar(64) resourceId String? @db.VarChar(64) metadata Json? createdAt DateTime @default(now()) @db.DateTime(6) actor users? @relation(fields: [actorId], references: [id]) } // —— 相册域(Task 9)—— // 约定:与身份域不同,相册域一律使用扁平字符串外键(albumId/ownerId/userId/ // categoryId 为纯字符串,不建 Prisma relation/FK 约束)——Task 10 的照片域同此。 // 原因:跨域删除语义(用户注销→相册归属、相册下线→照片保留)由应用层显式 // 决策,不做数据库级联;也避免 TRUNCATE 隔离工具为绕 FK 检查承担更多顺序负担。 model albums { id String @id @default(cuid()) publicToken String @unique @db.VarChar(32) ownerId String @db.VarChar(64) startAt DateTime @db.DateTime(6) endAt DateTime? @db.DateTime(6) timezone String @default("Asia/Shanghai") @db.VarChar(64) status String @default("active") // active|offline defaultLocale String @db.VarChar(16) uploadEnabled Boolean @default(true) publicEnabled Boolean @default(true) downloadEnabled Boolean @default(true) originalDownloadEnabled Boolean @default(false) watermarkVersion Int @default(0) createdAt DateTime @default(now()) @db.DateTime(6) updatedAt DateTime @updatedAt @db.DateTime(6) @@index([ownerId]) @@index([status, publicEnabled]) } model album_translations { id String @id @default(cuid()) albumId String @db.VarChar(64) locale String @db.VarChar(16) title String @db.VarChar(255) description String? @db.Text venue String? @db.VarChar(255) address String? @db.VarChar(255) @@unique([albumId, locale]) @@index([albumId]) } model album_members { id String @id @default(cuid()) albumId String @db.VarChar(64) userId String @db.VarChar(64) role String @default("collaborator") // owner|collaborator status String @default("active") addedAt DateTime @default(now()) @db.DateTime(6) @@unique([albumId, userId]) @@index([userId]) } model categories { id String @id @default(cuid()) albumId String @db.VarChar(64) sortOrder Int @default(0) createdAt DateTime @default(now()) @db.DateTime(6) @@index([albumId, sortOrder]) } model category_translations { id String @id @default(cuid()) categoryId String @db.VarChar(64) locale String @db.VarChar(16) name String @db.VarChar(128) @@unique([categoryId, locale]) } model album_assets { id String @id @default(cuid()) albumId String @db.VarChar(64) kind String @db.VarChar(16) // banner|cover|logo locale String? @db.VarChar(16) objectKey String @db.VarChar(255) createdAt DateTime @default(now()) @db.DateTime(6) @@index([albumId, kind]) }