Files
wmailhub/migrations/0001_init.sql
T
BigCode d4c1334143 feat: WmailHub v1.0.0 —— Cloudflare Email Workers 邮件接收与转发平台(本地可用版本)
- Console:React 19 + antd 6.6.5 前端(登录 admin/admin)+ Hono 管理 API
  (Nodes/Domains/Mailboxes/Emails/Webhooks/Keys/OAuth/Admin)
- Node:Bun 转发节点(HMAC 签名、有限重试、心跳、结构化日志、优雅退出)
- Email Worker:Cloudflare email handler + 本地联调入口
- shared:HMAC 协议(docs/04)、wmh_ Key(32 位)、AES-GCM 加密、SSRF 防护
- 邮件管线:postal-mime 解析、附件、catch-all、限额、malformed 降级、TTL 1 天
- Webhook:签名投递、指数退避重试、SSRF 校验、投递日志
- API v1 + MCP(JSON-RPC,六工具,凭据 kind 隔离)
- Cloudflare OAuth:真实 + 本地 sim 模式,自动安装管线(幂等,无需手工命令)
- 测试:bun test 41 例(协议安全/管线/TTL/Webhook/SSRF/MCP/OAuth/鉴权)+ Playwright UI 冒烟
- LOGO/favicon 由 codex CLI 生成
2026-09-30 03:34:37 +08:00

174 lines
5.0 KiB
SQL

-- WmailHub D1 初始schema
-- Console 运行时也会在本地模式下自动执行等价初始化(幂等)
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
-- 节点(转发平面)
CREATE TABLE IF NOT EXISTS nodes (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
cloudflare_account_id TEXT,
credential_id TEXT,
status TEXT NOT NULL DEFAULT 'pending_oauth', -- pending_oauth | provisioning | offline | online | revoked
version TEXT,
last_seen_at TEXT,
ingress_url TEXT,
ingress_secret_enc TEXT,
enrollment_token TEXT,
enrollment_expires_at TEXT,
provision_steps TEXT, -- JSON: 各安装步骤状态
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- 节点凭据:secret_hash 为摘要,secret_enc 为 AES-GCM 密文(HMAC 验签需要原文,加密存储防拖库泄露)
CREATE TABLE IF NOT EXISTS node_credentials (
id TEXT PRIMARY KEY,
node_id TEXT NOT NULL,
secret_hash TEXT NOT NULL,
secret_enc TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active', -- active | revoked
created_at TEXT NOT NULL,
revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_node_credentials_node ON node_credentials(node_id);
CREATE TABLE IF NOT EXISTS domains (
id TEXT PRIMARY KEY,
cloudflare_account_id TEXT NOT NULL,
node_id TEXT,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending', -- pending | active | error
worker_id TEXT,
zone_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(cloudflare_account_id, name)
);
CREATE TABLE IF NOT EXISTS mailboxes (
id TEXT PRIMARY KEY,
domain_id TEXT NOT NULL,
address TEXT NOT NULL UNIQUE,
status TEXT NOT NULL DEFAULT 'active', -- active | disabled
retention_seconds INTEGER, -- NULL = 使用全局默认 86400
is_catch_all INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_mailboxes_domain ON mailboxes(domain_id);
CREATE TABLE IF NOT EXISTS emails (
id TEXT PRIMARY KEY,
node_id TEXT,
mailbox_id TEXT,
message_id TEXT,
from_address TEXT,
to_addresses TEXT, -- JSON array
cc_addresses TEXT, -- JSON array
subject TEXT,
text_body TEXT,
html_body TEXT,
raw_storage_key TEXT,
size INTEGER NOT NULL DEFAULT 0,
received_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_emails_expires ON emails(expires_at);
CREATE INDEX IF NOT EXISTS idx_emails_mailbox ON emails(mailbox_id);
CREATE INDEX IF NOT EXISTS idx_emails_received ON emails(received_at DESC);
CREATE INDEX IF NOT EXISTS idx_emails_message_id ON emails(message_id);
CREATE TABLE IF NOT EXISTS email_attachments (
id TEXT PRIMARY KEY,
email_id TEXT NOT NULL,
filename TEXT,
content_type TEXT,
size INTEGER NOT NULL DEFAULT 0,
storage_key TEXT,
content_id TEXT
);
CREATE INDEX IF NOT EXISTS idx_attachments_email ON email_attachments(email_id);
CREATE TABLE IF NOT EXISTS webhooks (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
endpoint TEXT NOT NULL,
secret TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS webhook_deliveries (
id TEXT PRIMARY KEY,
webhook_id TEXT NOT NULL,
email_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending', -- pending | success | failed | exhausted
attempt_count INTEGER NOT NULL DEFAULT 0,
next_retry_at TEXT,
last_error TEXT,
delivered_at TEXT,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_deliveries_retry ON webhook_deliveries(status, next_retry_at);
CREATE INDEX IF NOT EXISTS idx_deliveries_webhook ON webhook_deliveries(webhook_id);
CREATE TABLE IF NOT EXISTS oauth_connections (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
account_id TEXT,
encrypted_access_token TEXT NOT NULL,
refresh_token TEXT,
expires_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- OAuth CSRF state(node_id 记录本次授权为哪个节点安装)
CREATE TABLE IF NOT EXISTS oauth_states (
state TEXT PRIMARY KEY,
node_id TEXT,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL
);
-- Node 请求 nonce 防重放(唯一约束即拒绝重复)
CREATE TABLE IF NOT EXISTS nonces (
nonce TEXT PRIMARY KEY,
node_id TEXT NOT NULL,
expires_at TEXT NOT NULL
);
-- Console API / MCP 凭据(wmh_ 开头,总长 32;库中只存 SHA-256)
CREATE TABLE IF NOT EXISTS api_credentials (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL, -- api | mcp
name TEXT,
key_hash TEXT NOT NULL UNIQUE,
key_last4 TEXT NOT NULL,
created_at TEXT NOT NULL,
revoked_at TEXT,
last_used_at TEXT
);
-- 本地 sim 模式:模拟 Cloudflare API 已创建的资源,供 UI 展示安装结果
CREATE TABLE IF NOT EXISTS cf_sim_resources (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL, -- email_worker | routing_rule | zone | account
ref_id TEXT,
payload TEXT,
created_at TEXT NOT NULL
);