429 lines
17 KiB
Plaintext
429 lines
17 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum AccountStatus {
|
|
ACTIVE
|
|
SUSPENDED
|
|
DELETED
|
|
}
|
|
|
|
enum AuthProvider {
|
|
PHONE
|
|
APPLE
|
|
WECHAT
|
|
}
|
|
|
|
enum ReviewStatus {
|
|
DRAFT
|
|
REVIEWING
|
|
REJECTED
|
|
MANUAL_REVIEW
|
|
APPROVED
|
|
}
|
|
|
|
enum BottlePoolStatus {
|
|
IN_POOL
|
|
LEASED
|
|
CONSUMED
|
|
REMOVED
|
|
CLOSED
|
|
}
|
|
|
|
enum BottlePickLeaseStatus {
|
|
ACTIVE
|
|
RETURNED
|
|
EXPIRED
|
|
CONSUMED
|
|
}
|
|
|
|
enum ConversationStatus {
|
|
ACTIVE
|
|
CLOSED
|
|
}
|
|
|
|
enum MessageStatus {
|
|
SENT
|
|
DELETED
|
|
MODERATED
|
|
}
|
|
|
|
enum ReportStatus {
|
|
PENDING
|
|
REVIEWING
|
|
RESOLVED
|
|
DISMISSED
|
|
}
|
|
|
|
enum ModerationTaskStatus {
|
|
PENDING
|
|
PROCESSING
|
|
COMPLETED
|
|
}
|
|
|
|
enum SanctionType {
|
|
WARNING
|
|
MUTE
|
|
SUSPENSION
|
|
BAN
|
|
}
|
|
|
|
enum NotificationStatus {
|
|
PENDING
|
|
SENT
|
|
READ
|
|
FAILED
|
|
}
|
|
|
|
enum OutboxStatus {
|
|
PENDING
|
|
PROCESSING
|
|
PUBLISHED
|
|
FAILED
|
|
}
|
|
|
|
model Account {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
phoneCiphertext Bytes @map("phone_ciphertext")
|
|
phoneHmac String @unique @map("phone_hmac") @db.VarChar(128)
|
|
status AccountStatus @default(ACTIVE)
|
|
tokenVersion Int @default(0) @map("token_version")
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
anonymousProfile AnonymousProfile?
|
|
authIdentities AuthIdentity[]
|
|
sessions Session[]
|
|
bottles Bottle[]
|
|
pickLeases BottlePickLease[] @relation("PickerLeases")
|
|
pickHistory BottlePickHistory[] @relation("PickerHistory")
|
|
conversationMembers ConversationMember[]
|
|
messages Message[]
|
|
blocksInitiated Block[] @relation("Blocker")
|
|
blocksReceived Block[] @relation("Blocked")
|
|
reportsFiled Report[] @relation("Reporter")
|
|
reportsAgainst Report[] @relation("Reported")
|
|
moderationTasks ModerationTask[] @relation("Moderator")
|
|
sanctionsReceived Sanction[] @relation("SanctionedAccount")
|
|
sanctionsIssued Sanction[] @relation("SanctionIssuer")
|
|
notifications Notification[]
|
|
auditLogs AuditLog[] @relation("AuditActor")
|
|
dailyUsage DailyUsage[]
|
|
|
|
@@map("accounts")
|
|
}
|
|
|
|
model AnonymousProfile {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
accountId String @unique @map("account_id") @db.Uuid
|
|
publicId String @unique @default(uuid()) @map("public_id") @db.Uuid
|
|
nickname String @db.VarChar(64)
|
|
avatarKey String? @map("avatar_key") @db.VarChar(255)
|
|
avatarColor String @map("avatar_color") @db.VarChar(16)
|
|
bio String? @db.VarChar(500)
|
|
reviewStatus ReviewStatus @default(REVIEWING) @map("review_status")
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("anonymous_profiles")
|
|
}
|
|
|
|
model AuthIdentity {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
accountId String @map("account_id") @db.Uuid
|
|
provider AuthProvider
|
|
providerSubject String @map("provider_subject") @db.VarChar(255)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([provider, providerSubject])
|
|
@@index([accountId])
|
|
@@map("auth_identities")
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
accountId String @map("account_id") @db.Uuid
|
|
refreshTokenHash String @unique @map("refresh_token_hash") @db.VarChar(255)
|
|
deviceId String? @map("device_id") @db.VarChar(255)
|
|
expiresAt DateTime @map("expires_at") @db.Timestamptz(3)
|
|
revokedAt DateTime? @map("revoked_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([accountId, expiresAt])
|
|
@@map("sessions")
|
|
}
|
|
|
|
model Bottle {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
authorId String @map("author_id") @db.Uuid
|
|
contentText String @map("content_text") @db.Text
|
|
reviewStatus ReviewStatus @default(DRAFT) @map("review_status")
|
|
poolStatus BottlePoolStatus @default(CLOSED) @map("pool_status")
|
|
version Int @default(1)
|
|
approvedAt DateTime? @map("approved_at") @db.Timestamptz(3)
|
|
consumedAt DateTime? @map("consumed_at") @db.Timestamptz(3)
|
|
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
author Account @relation(fields: [authorId], references: [id], onDelete: Restrict)
|
|
leases BottlePickLease[]
|
|
pickHistory BottlePickHistory[]
|
|
conversation Conversation?
|
|
reports Report[]
|
|
|
|
@@index([poolStatus, createdAt])
|
|
@@index([reviewStatus, createdAt])
|
|
@@index([authorId, createdAt])
|
|
@@map("bottles")
|
|
}
|
|
|
|
model BottlePickLease {
|
|
/// Partial unique index bottle_pick_leases_one_active_per_bottle is managed in 0001_init SQL.
|
|
id String @id @default(uuid()) @db.Uuid
|
|
bottleId String @map("bottle_id") @db.Uuid
|
|
pickerId String @map("picker_id") @db.Uuid
|
|
leaseTokenHash String @unique @map("lease_token_hash") @db.VarChar(255)
|
|
expiresAt DateTime @map("expires_at") @db.Timestamptz(3)
|
|
status BottlePickLeaseStatus @default(ACTIVE)
|
|
endedAt DateTime? @map("ended_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
bottle Bottle @relation(fields: [bottleId], references: [id], onDelete: Cascade)
|
|
picker Account @relation("PickerLeases", fields: [pickerId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([pickerId, expiresAt])
|
|
@@index([bottleId, expiresAt])
|
|
@@map("bottle_pick_leases")
|
|
}
|
|
|
|
model BottlePickHistory {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
bottleId String @map("bottle_id") @db.Uuid
|
|
pickerId String @map("picker_id") @db.Uuid
|
|
pickedAt DateTime @default(now()) @map("picked_at") @db.Timestamptz(3)
|
|
bottle Bottle @relation(fields: [bottleId], references: [id], onDelete: Cascade)
|
|
picker Account @relation("PickerHistory", fields: [pickerId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([bottleId, pickerId])
|
|
@@index([pickerId, pickedAt])
|
|
@@map("bottle_pick_history")
|
|
}
|
|
|
|
model DailyUsage {
|
|
/// Nonnegative counters are enforced by CHECK constraints in 0001_init SQL.
|
|
id String @id @default(uuid()) @db.Uuid
|
|
accountId String @map("account_id") @db.Uuid
|
|
usageDate DateTime @map("usage_date") @db.Date
|
|
bottlesCreated Int @default(0) @map("bottles_created")
|
|
bottlesPicked Int @default(0) @map("bottles_picked")
|
|
messagesSent Int @default(0) @map("messages_sent")
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([accountId, usageDate])
|
|
@@index([usageDate])
|
|
@@map("daily_usage")
|
|
}
|
|
|
|
model Conversation {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
sourceBottleId String @unique @map("source_bottle_id") @db.Uuid
|
|
status ConversationStatus @default(ACTIVE)
|
|
nextSeq BigInt @default(1) @map("next_seq")
|
|
lastMessageAt DateTime @default(now()) @map("last_message_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
sourceBottle Bottle @relation(fields: [sourceBottleId], references: [id], onDelete: Restrict)
|
|
members ConversationMember[]
|
|
messages Message[]
|
|
reports Report[]
|
|
|
|
@@index([status, updatedAt])
|
|
@@map("conversations")
|
|
}
|
|
|
|
model ConversationMember {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
conversationId String @map("conversation_id") @db.Uuid
|
|
accountId String @map("account_id") @db.Uuid
|
|
joinedAt DateTime @default(now()) @map("joined_at") @db.Timestamptz(3)
|
|
leftAt DateTime? @map("left_at") @db.Timestamptz(3)
|
|
lastReadSeq BigInt @default(0) @map("last_read_seq")
|
|
peerAliasSnapshot String @map("peer_alias_snapshot") @db.VarChar(64)
|
|
blockedAt DateTime? @map("blocked_at") @db.Timestamptz(3)
|
|
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([conversationId, accountId])
|
|
@@index([accountId, joinedAt])
|
|
@@map("conversation_members")
|
|
}
|
|
|
|
model Message {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
conversationId String @map("conversation_id") @db.Uuid
|
|
senderId String @map("sender_id") @db.Uuid
|
|
clientMsgId String @map("client_msg_id") @db.VarChar(128)
|
|
seq BigInt
|
|
contentText String @map("content_text") @db.Text
|
|
status MessageStatus @default(SENT)
|
|
reviewStatus ReviewStatus @default(REVIEWING) @map("review_status")
|
|
sentAt DateTime @default(now()) @map("sent_at") @db.Timestamptz(3)
|
|
recalledAt DateTime? @map("recalled_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
sender Account @relation(fields: [senderId], references: [id], onDelete: Restrict)
|
|
reports Report[]
|
|
|
|
@@unique([conversationId, clientMsgId])
|
|
@@unique([conversationId, seq])
|
|
@@index([senderId, createdAt])
|
|
@@map("messages")
|
|
}
|
|
|
|
model Block {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
blockerId String @map("blocker_id") @db.Uuid
|
|
blockedId String @map("blocked_id") @db.Uuid
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
blocker Account @relation("Blocker", fields: [blockerId], references: [id], onDelete: Cascade)
|
|
blocked Account @relation("Blocked", fields: [blockedId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([blockerId, blockedId])
|
|
@@index([blockedId])
|
|
@@map("blocks")
|
|
}
|
|
|
|
model Report {
|
|
/// Exactly one nullable target foreign key must be set; enforced in 0001_init SQL.
|
|
id String @id @default(uuid()) @db.Uuid
|
|
reporterId String @map("reporter_id") @db.Uuid
|
|
reportedAccountId String? @map("reported_account_id") @db.Uuid
|
|
bottleId String? @map("bottle_id") @db.Uuid
|
|
conversationId String? @map("conversation_id") @db.Uuid
|
|
messageId String? @map("message_id") @db.Uuid
|
|
reason String @db.VarChar(100)
|
|
details String? @db.Text
|
|
targetSnapshot Json @map("target_snapshot")
|
|
resolution String? @db.Text
|
|
status ReportStatus @default(PENDING)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
reporter Account @relation("Reporter", fields: [reporterId], references: [id], onDelete: Restrict)
|
|
reportedAccount Account? @relation("Reported", fields: [reportedAccountId], references: [id], onDelete: SetNull)
|
|
bottle Bottle? @relation(fields: [bottleId], references: [id], onDelete: SetNull)
|
|
conversation Conversation? @relation(fields: [conversationId], references: [id], onDelete: SetNull)
|
|
message Message? @relation(fields: [messageId], references: [id], onDelete: SetNull)
|
|
moderationTasks ModerationTask[]
|
|
|
|
@@index([status, createdAt])
|
|
@@index([reportedAccountId, createdAt])
|
|
@@map("reports")
|
|
}
|
|
|
|
model ModerationTask {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
targetType String @map("target_type") @db.VarChar(50)
|
|
targetId String @map("target_id") @db.Uuid
|
|
provider String @db.VarChar(100)
|
|
result Json?
|
|
riskLabels String[] @map("risk_labels")
|
|
payloadHash String @map("payload_hash") @db.VarChar(128)
|
|
reportId String? @map("report_id") @db.Uuid
|
|
assignedToId String? @map("assigned_to_id") @db.Uuid
|
|
status ModerationTaskStatus @default(PENDING)
|
|
decision String? @db.Text
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
reviewedAt DateTime? @map("reviewed_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
report Report? @relation(fields: [reportId], references: [id], onDelete: SetNull)
|
|
assignedTo Account? @relation("Moderator", fields: [assignedToId], references: [id], onDelete: SetNull)
|
|
sanctions Sanction[]
|
|
|
|
@@index([status, createdAt])
|
|
@@index([targetType, targetId])
|
|
@@index([assignedToId, status])
|
|
@@map("moderation_tasks")
|
|
}
|
|
|
|
model Sanction {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
accountId String @map("account_id") @db.Uuid
|
|
issuedById String? @map("issued_by_id") @db.Uuid
|
|
moderationTaskId String? @map("moderation_task_id") @db.Uuid
|
|
type SanctionType
|
|
reason String @db.Text
|
|
startsAt DateTime @default(now()) @map("starts_at") @db.Timestamptz(3)
|
|
expiresAt DateTime? @map("expires_at") @db.Timestamptz(3)
|
|
revokedAt DateTime? @map("revoked_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
account Account @relation("SanctionedAccount", fields: [accountId], references: [id], onDelete: Cascade)
|
|
issuedBy Account? @relation("SanctionIssuer", fields: [issuedById], references: [id], onDelete: SetNull)
|
|
moderationTask ModerationTask? @relation(fields: [moderationTaskId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([accountId, expiresAt])
|
|
@@map("sanctions")
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
accountId String @map("account_id") @db.Uuid
|
|
type String @db.VarChar(100)
|
|
payload Json
|
|
status NotificationStatus @default(PENDING)
|
|
sentAt DateTime? @map("sent_at") @db.Timestamptz(3)
|
|
readAt DateTime? @map("read_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([accountId, status, createdAt])
|
|
@@map("notifications")
|
|
}
|
|
|
|
model OutboxEvent {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
aggregateType String @map("aggregate_type") @db.VarChar(100)
|
|
aggregateId String @map("aggregate_id") @db.Uuid
|
|
eventType String @map("event_type") @db.VarChar(150)
|
|
payload Json
|
|
status OutboxStatus @default(PENDING)
|
|
attempts Int @default(0)
|
|
nextRetryAt DateTime @default(now()) @map("next_retry_at") @db.Timestamptz(3)
|
|
publishedAt DateTime? @map("published_at") @db.Timestamptz(3)
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
|
|
|
|
@@index([status, nextRetryAt])
|
|
@@index([aggregateType, aggregateId])
|
|
@@map("outbox_events")
|
|
}
|
|
|
|
model AuditLog {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
actorId String? @map("actor_id") @db.Uuid
|
|
action String @db.VarChar(150)
|
|
entityType String @map("entity_type") @db.VarChar(100)
|
|
entityId String? @map("entity_id") @db.Uuid
|
|
requestId String? @map("request_id") @db.VarChar(128)
|
|
ipHash String? @map("ip_hash") @db.VarChar(128)
|
|
metadata Json?
|
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
|
|
actor Account? @relation("AuditActor", fields: [actorId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([actorId, createdAt])
|
|
@@index([entityType, entityId, createdAt])
|
|
@@index([requestId])
|
|
@@map("audit_logs")
|
|
}
|