2026-09-17 19:51:56 +08:00
|
|
|
|
// 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])
|
|
|
|
|
|
}
|
2026-09-17 20:05:09 +08:00
|
|
|
|
|
|
|
|
|
|
// —— 相册域(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])
|
|
|
|
|
|
}
|
2026-09-17 20:11:26 +08:00
|
|
|
|
|
|
|
|
|
|
// —— 照片与上传域 + outbox(Task 10)——
|
|
|
|
|
|
// 同相册域约定:扁平字符串外键、无 Prisma relation/FK 约束。
|
|
|
|
|
|
// photos.objectKey 是 originals 正式键(Task 18 上传确认时写入);
|
|
|
|
|
|
// upload_sessions.objectKey 是 staging 键;outbox_events 承载领域事件
|
|
|
|
|
|
// (契约见 packages/contracts),由 worker 轮询投递。
|
|
|
|
|
|
model photos {
|
|
|
|
|
|
id String @id @default(cuid())
|
|
|
|
|
|
albumId String @db.VarChar(64)
|
|
|
|
|
|
uploaderId String @db.VarChar(64)
|
|
|
|
|
|
categoryId String? @db.VarChar(64)
|
|
|
|
|
|
objectKey String @unique @db.VarChar(255) // originals 正式键
|
|
|
|
|
|
bytes Int @default(0)
|
|
|
|
|
|
width Int?
|
|
|
|
|
|
height Int?
|
|
|
|
|
|
shotAt DateTime? @db.DateTime(6)
|
|
|
|
|
|
uploadedAt DateTime? @db.DateTime(6)
|
|
|
|
|
|
publishedAt DateTime? @db.DateTime(6)
|
|
|
|
|
|
processingState String @default("pending_upload") // pending_upload|uploaded|processing|ready|processing_failed
|
|
|
|
|
|
errorCode String? @db.VarChar(64)
|
|
|
|
|
|
visibility String @default("unpublished") // unpublished|published|hidden|deleted
|
|
|
|
|
|
likeCount Int @default(0) // P2 起为统计副本
|
|
|
|
|
|
entityVersion Int @default(1)
|
|
|
|
|
|
@@index([albumId, visibility, publishedAt, id])
|
|
|
|
|
|
@@index([albumId, uploaderId, uploadedAt])
|
|
|
|
|
|
}
|
|
|
|
|
|
model photo_variants {
|
|
|
|
|
|
photoId String @db.VarChar(64)
|
|
|
|
|
|
variant String @db.VarChar(32) // thumb|browse
|
|
|
|
|
|
objectKey String @unique @db.VarChar(255)
|
|
|
|
|
|
bytes Int
|
|
|
|
|
|
version Int @default(1)
|
|
|
|
|
|
width Int?
|
|
|
|
|
|
height Int?
|
|
|
|
|
|
@@unique([photoId, variant])
|
|
|
|
|
|
}
|
|
|
|
|
|
model upload_sessions {
|
|
|
|
|
|
id String @id @default(cuid())
|
|
|
|
|
|
userId String @db.VarChar(64)
|
|
|
|
|
|
albumId String @db.VarChar(64)
|
|
|
|
|
|
clientTaskId String @db.VarChar(128)
|
|
|
|
|
|
photoId String @unique @db.VarChar(64)
|
|
|
|
|
|
objectKey String @unique @db.VarChar(255) // staging 键
|
|
|
|
|
|
expectedBytes Int
|
|
|
|
|
|
contentType String @db.VarChar(128)
|
|
|
|
|
|
multipartId String? @db.VarChar(255)
|
|
|
|
|
|
state String @default("pending") // pending|confirmed|cancelled|expired(对齐契约 UploadSessionState;核验失败是请求级错误不改会话态,expired 由 expiresAt 判定或 sweep 置位)
|
|
|
|
|
|
expiresAt DateTime @db.DateTime(6)
|
|
|
|
|
|
createdAt DateTime @default(now()) @db.DateTime(6)
|
|
|
|
|
|
@@unique([userId, clientTaskId]) // 幂等锚点(A03)
|
|
|
|
|
|
}
|
|
|
|
|
|
model photo_jobs {
|
|
|
|
|
|
id String @id @default(cuid())
|
|
|
|
|
|
photoId String @db.VarChar(64)
|
|
|
|
|
|
type String @default("process") @db.VarChar(32)
|
|
|
|
|
|
state String @default("pending") // pending|active|done|failed
|
|
|
|
|
|
attempt Int @default(0)
|
|
|
|
|
|
errorCode String? @db.VarChar(64)
|
|
|
|
|
|
nextRetryAt DateTime? @db.DateTime(6)
|
|
|
|
|
|
updatedAt DateTime @updatedAt @db.DateTime(6)
|
|
|
|
|
|
@@unique([photoId, type])
|
|
|
|
|
|
@@index([state, nextRetryAt])
|
|
|
|
|
|
}
|
|
|
|
|
|
model outbox_events {
|
|
|
|
|
|
id String @id @default(cuid())
|
|
|
|
|
|
type String @db.VarChar(64)
|
|
|
|
|
|
albumId String @db.VarChar(64)
|
|
|
|
|
|
photoId String? @db.VarChar(64)
|
|
|
|
|
|
entityVersion Int
|
|
|
|
|
|
payload Json
|
|
|
|
|
|
occurredAt DateTime @default(now()) @db.DateTime(6)
|
|
|
|
|
|
deliveredAt DateTime? @db.DateTime(6)
|
|
|
|
|
|
@@index([deliveredAt, occurredAt])
|
|
|
|
|
|
}
|