You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model MailboxAccount {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
/// 归属客户;空=管理端公共邮箱
ownerUserId BigInt? @map("owner_user_id") @db.UnsignedBigInt
name String @db.VarChar(128)
host String @default("imap.qq.com") @db.VarChar(255)
port Int @default(993) @db.UnsignedInt
username String @db.VarChar(320)
/// AES-GCM ciphertext (base64); empty when OAuth-only
passwordEnc String @default("") @map("password_enc") @db.VarChar(1024)
/// PASSWORD = 授权码;OAUTH = XOAUTH2
authType String @default("PASSWORD") @map("auth_type") @db.VarChar(16)
oauthProvider String? @map("oauth_provider") @db.VarChar(32)
oauthRefreshEnc String? @map("oauth_refresh_enc") @db.VarChar(2048)
oauthAccessEnc String? @map("oauth_access_enc") @db.VarChar(2048)
oauthExpiresAt DateTime? @map("oauth_expires_at") @db.DateTime(3)
idleEnabled Boolean @default(true) @map("idle_enabled")
folder String @default("INBOX") @db.VarChar(64)
enabled Boolean @default(true)
lastTestAt DateTime? @map("last_test_at") @db.DateTime(3)
lastTestOk Boolean? @map("last_test_ok")
lastTestError String? @map("last_test_error") @db.VarChar(500)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
owner AppUser? @relation(fields: [ownerUserId], references: [id], onDelete: SetNull)
mails MailMessage[]
@@unique([username], map: "uk_mailbox_username")
@@index([enabled], map: "idx_mailbox_enabled")
@@index([ownerUserId], map: "idx_mailbox_owner")
@@map("mailbox_account")
}
model MailMessage {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
// utf8mb4 unique key limit: keep message_id ≤ 191 chars for UNIQUE
messageId String? @unique @map("message_id") @db.VarChar(191)
mailboxAccountId BigInt? @map("mailbox_account_id") @db.UnsignedBigInt
imapUid BigInt? @map("imap_uid") @db.UnsignedBigInt
folder String @default("INBOX") @db.VarChar(64)
subject String @default("(无主题)") @db.VarChar(512)
fromAddr String @default("") @map("from_addr") @db.VarChar(320)
receivedAt DateTime? @map("received_at") @db.DateTime(3)
bodyText String? @map("body_text") @db.MediumText
/// OCR 抽取文本(权威解析辅助)
ocrText String? @map("ocr_text") @db.MediumText
/// 线程根 Message-ID(规范化)
threadId String? @map("thread_id") @db.VarChar(191)
inReplyTo String? @map("in_reply_to") @db.VarChar(191)
referencesHeader String? @map("references_header") @db.Text
isThreadRoot Boolean @default(true) @map("is_thread_root")
mailType String @default("UNKNOWN") @map("mail_type") @db.VarChar(32)
status String @default("FETCHED") @db.VarChar(32)
typeEvidence Json? @map("type_evidence")
snapshotPath String? @map("snapshot_path") @db.VarChar(512)
rawHash String? @unique @map("raw_hash") @db.Char(64)
lastError String? @map("last_error") @db.VarChar(1000)
/// 贯穿拉取/解析/暂存/提交日志
traceId String? @map("trace_id") @db.VarChar(36)
version Int @default(1) @db.UnsignedInt
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
mailboxAccount MailboxAccount? @relation(fields: [mailboxAccountId], references: [id], onDelete: SetNull)
attachments MailAttachment[]
parseResult ParseResult?
imports ContainerImport[]
@@unique([mailboxAccountId, folder, imapUid], map: "uk_mailbox_folder_uid")
@@index([status, receivedAt], map: "idx_status_received")
@@index([mailType], map: "idx_mail_type")
@@index([mailboxAccountId], map: "idx_mailbox")
@@index([threadId], map: "idx_thread")
@@index([traceId], map: "idx_trace")
@@map("mail_message")
}
model MailAttachment {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
mailId BigInt @map("mail_id") @db.UnsignedBigInt
filename String @db.VarChar(512)
contentType String? @map("content_type") @db.VarChar(128)
sha256 String @db.Char(64)
path String @db.VarChar(512)
size Int @db.UnsignedInt
rejected Boolean @default(false)
templateId String? @map("template_id") @db.VarChar(64)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
mail MailMessage @relation(fields: [mailId], references: [id])
@@index([mailId], map: "idx_mail")
@@map("mail_attachment")
}
model ParseResult {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
mailId BigInt @unique @map("mail_id") @db.UnsignedBigInt
containerHeader Json @map("container_header")
shipments Json
lineage Json?
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
mail MailMessage @relation(fields: [mailId], references: [id])
@@map("parse_result")
}
model ContainerImport {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
mailId BigInt @map("mail_id") @db.UnsignedBigInt
containerNo String @map("container_no") @db.VarChar(50)
externalId String? @map("external_id") @db.VarChar(64)
requestBody String? @map("request_body") @db.MediumText
responseBody String? @map("response_body") @db.MediumText
status String @db.VarChar(32)
lastError String? @map("last_error") @db.VarChar(1000)
shipmentsHash String @map("shipments_hash") @db.Char(64)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
mail MailMessage @relation(fields: [mailId], references: [id])
compensations ImportCompensation[]
@@unique([mailId, containerNo, shipmentsHash], map: "uk_mail_container_shiphash")
@@index([mailId], map: "idx_mail")
@@index([containerNo], map: "idx_container")
@@map("container_import")
}
model ContainerActiveLock {
containerNo String @id @map("container_no") @db.VarChar(50)
mailId BigInt @map("mail_id") @db.UnsignedBigInt
importId BigInt? @map("import_id") @db.UnsignedBigInt
lockedAt DateTime @map("locked_at") @db.DateTime(3)
@@map("container_active_lock")
}
model ImportCompensation {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
importId BigInt @map("import_id") @db.UnsignedBigInt
retryCount Int @default(0) @map("retry_count") @db.UnsignedInt
maxRetry Int @default(3) @map("max_retry") @db.UnsignedInt
nextRetryAt DateTime? @map("next_retry_at") @db.DateTime(3)
reason String @db.VarChar(64)
status String @default("OPEN") @db.VarChar(32)
retryToken String @unique @map("retry_token") @db.Char(36)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
import ContainerImport @relation(fields: [importId], references: [id])
@@index([status, nextRetryAt], map: "idx_open")
@@map("import_compensation")
}
model CcTokenCache {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
loginMark String @unique @map("login_mark") @db.Char(36)
token String @db.VarChar(128)
expireAt DateTime @map("expire_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
@@map("cc_token_cache")
}
/// CC 连接配置:userId 为空为管理端默认(原 id=1);客户各有一行
model CcSettings {
id Int @id @default(autoincrement())
userId BigInt? @unique @map("user_id") @db.UnsignedBigInt
mock Boolean @default(true)
apiBase String @map("api_base") @db.VarChar(512)
saasHeader String @map("saas_header") @db.VarChar(64)
loginMark String @map("login_mark") @db.Char(36)
username String @default("") @db.VarChar(128)
/// AES-GCM;空表示沿用 .env 密码或未配置
passwordEnc String? @map("password_enc") @db.VarChar(1024)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
user AppUser? @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("cc_settings")
}
/// CC 写能力门禁:live | mock | off
model CcWriteCapability {
id String @id @db.VarChar(32)
mode String @default("mock") @db.VarChar(16)
endpoint String? @db.VarChar(512)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
@@map("cc_write_capability")
}
/// Singleton OAuth 应用凭证(id=1);优先于 .env,secret AES 加密
model OauthSettings {
id Int @id @default(1)
publicBaseUrl String @default("http://localhost:3000") @map("public_base_url") @db.VarChar(512)
googleClientId String @default("") @map("google_client_id") @db.VarChar(256)
googleClientSecretEnc String? @map("google_client_secret_enc") @db.VarChar(1024)
msClientId String @default("") @map("ms_client_id") @db.VarChar(256)
msClientSecretEnc String? @map("ms_client_secret_enc") @db.VarChar(1024)
msTenant String @default("common") @map("ms_tenant") @db.VarChar(64)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
@@map("oauth_settings")
}
model SenderCustomerMap {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
matchType String @map("match_type") @db.VarChar(32)
matchValue String @map("match_value") @db.VarChar(320)
customerCode String @map("customer_code") @db.VarChar(64)
enabled Boolean @default(true)
@@unique([matchType, matchValue], map: "uk_match")
@@map("sender_customer_map")
}
model AppUser {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
username String @unique @db.VarChar(64)
passwordHash String @map("password_hash") @db.VarChar(128)
/// AES-GCM,管理端可回显明文密码
passwordEnc String @default("") @map("password_enc") @db.VarChar(1024)
role String @default("ops") @db.VarChar(16)
displayName String @default("") @map("display_name") @db.VarChar(128)
enabled Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
mailboxes MailboxAccount[]
ccSettings CcSettings?
@@index([role], map: "idx_app_user_role")
@@map("app_user")
}
/// Singleton IMAP 自动拉取配置(id=1);poll_interval_ms 优先于 .env POLL_INTERVAL_MS
model ImapSettings {
id Int @id @default(1)
/// 默认 30min;合法范围 3min~7d
pollIntervalMs Int @default(1800000) @map("poll_interval_ms")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3)
@@map("imap_settings")
}
/// 拉取过滤规则:关键词 / 发件人白名单 / 发件人黑名单 / 关键词黑名单
model ImapFilterRule {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
kind String @db.VarChar(32)
value String @db.VarChar(256)
enabled Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
@@unique([kind, value], map: "uk_imap_filter_kind_value")
@@index([kind, enabled], map: "idx_imap_filter_kind")
@@map("imap_filter_rule")
}
/// 拉取记录(每封一条;最多保留 1000,超则删最旧)
model ImapPullLog {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
pulledAt DateTime @default(now()) @map("pulled_at") @db.DateTime(3)
mailboxAccountId BigInt? @map("mailbox_account_id") @db.UnsignedBigInt
mailboxName String @default("") @map("mailbox_name") @db.VarChar(128)
mailboxUsername String @default("") @map("mailbox_username") @db.VarChar(320)
fromAddr String @default("") @map("from_addr") @db.VarChar(320)
subject String @default("") @db.VarChar(512)
mailType String? @map("mail_type") @db.VarChar(32)
mailId BigInt? @map("mail_id") @db.UnsignedBigInt
result String @db.VarChar(16)
reason String? @db.VarChar(64)
@@index([pulledAt], map: "idx_imap_pull_at")
@@map("imap_pull_log")
}