|
|
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")
|
|
|
}
|