# -*- coding: utf-8 -*- """ Database schema v2 — Credential & Browser Profile Manager. Time stored as INTEGER Unix seconds (UTC). DDL comments are preserved in sqlite_master, visible in DBeaver / Navicat. Schema version management: PRAGMA user_version = 2 (set in connection.py after migration). """ import textwrap # --------------------------------------------------------------------------- # platforms — Registry of all platforms (logistics, LLM, content, etc.) # --------------------------------------------------------------------------- PLATFORMS_TABLE_SQL = textwrap.dedent("""\ CREATE TABLE IF NOT EXISTS platforms ( platform_key TEXT PRIMARY KEY, -- 平台唯一键,例如 maersk / deepseek / sohu display_name TEXT NOT NULL, -- 展示名称,例如 Maersk / DeepSeek / 搜狐号 domain TEXT NOT NULL, -- 业务域:logistics / llm / content / ecommerce / generic provider_code TEXT, -- 供应商代码,物流 profile 可复用;例如 maersk、cma_cgm default_url TEXT, -- 默认入口 URL aliases_json TEXT NOT NULL DEFAULT '[]',-- 别名 JSON 数组,中英文别名 capabilities_json TEXT NOT NULL DEFAULT '{}', -- 能力声明,例如 {"web":true,"api_key":true,"rpa":true} enabled INTEGER NOT NULL DEFAULT 1,-- 是否启用 (1=yes 0=no) created_at INTEGER NOT NULL, -- 记录创建时间,Unix 秒 UTC updated_at INTEGER NOT NULL -- 记录最后更新时间,Unix 秒 UTC ); """) PLATFORMS_INDEXES_SQL = textwrap.dedent("""\ CREATE INDEX IF NOT EXISTS idx_platforms_domain ON platforms(domain); CREATE INDEX IF NOT EXISTS idx_platforms_provider_code ON platforms(provider_code); """) # --------------------------------------------------------------------------- # accounts — An identity that can be used (web account, RPA profile, API account) # --------------------------------------------------------------------------- ACCOUNTS_TABLE_SQL = textwrap.dedent("""\ CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 账号主键(自增) platform_key TEXT NOT NULL, -- 对应 platforms.platform_key account_label TEXT NOT NULL, -- 人可读名称,例如 Maersk simulator booking login_id TEXT, -- 登录标识:邮箱、用户名、手机号、客户代码等 login_id_type TEXT NOT NULL DEFAULT 'unknown', -- email / username / phone / customer_code / api_account / unknown tenant_id TEXT, -- 租户/客户/项目标识,可空 environment TEXT NOT NULL DEFAULT 'production',-- simulator / staging / production / test role TEXT NOT NULL DEFAULT 'default', -- booking / tracking / admin / sales / api / default provider_code TEXT, -- 冗余供应商码,便于业务 skill 查询 profile_dir TEXT, -- Playwright 持久化浏览器用户数据目录;web/rpa 账号使用 url TEXT, -- 账号默认入口 URL;为空时用 platforms.default_url status TEXT NOT NULL DEFAULT 'active', -- active / disabled / needs_review session_status TEXT NOT NULL DEFAULT 'unknown', -- unknown / likely_valid / needs_login / expired last_used_at INTEGER, -- 最近被业务 skill pick/使用时间 last_login_check_at INTEGER, -- 最近一次登录状态检查时间,可空 last_error_code TEXT, -- 最近一次错误代码 last_error_message TEXT, -- 最近一次错误消息 extra_json TEXT NOT NULL DEFAULT '{}', -- 扩展字段 JSON created_at INTEGER NOT NULL, -- 记录创建时间,Unix 秒 UTC updated_at INTEGER NOT NULL -- 记录最后更新时间,Unix 秒 UTC ); """) ACCOUNTS_INDEXES_SQL = textwrap.dedent("""\ CREATE INDEX IF NOT EXISTS idx_accounts_platform ON accounts(platform_key); CREATE INDEX IF NOT EXISTS idx_accounts_pick_web ON accounts(platform_key, environment, tenant_id, role, status, updated_at); CREATE INDEX IF NOT EXISTS idx_accounts_provider ON accounts(provider_code); CREATE INDEX IF NOT EXISTS idx_accounts_status ON accounts(status); """) # Note: Unique constraint (platform_key, login_id, tenant_id, environment, role) # is intentionally NOT added here because SQLite allows multiple NULL login_id. # Application-level checks are used when login_id is provided. # --------------------------------------------------------------------------- # credentials — Secret-type credentials (API Key, Token, Password, etc.) # Raw secret is NEVER stored in SQLite. Only metadata, secret_ref, secret_mask. # --------------------------------------------------------------------------- CREDENTIALS_TABLE_SQL = textwrap.dedent("""\ CREATE TABLE IF NOT EXISTS credentials ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 凭据主键(自增) account_id INTEGER, -- 可空;关联 accounts.id;有些 API Key 可直接挂平台,也可挂具体账号 platform_key TEXT NOT NULL, -- 平台唯一键 credential_label TEXT NOT NULL, -- 人可读名称,例如 DeepSeek main API key credential_type TEXT NOT NULL, -- api_key / password / bearer_token / oauth_client / refresh_token / browser_profile / note secret_storage TEXT NOT NULL, -- none / env / windows_credential secret_ref TEXT, -- env 时为环境变量名;windows_credential 时为 target name;none 时为空 secret_mask TEXT, -- 打码展示,例如 sk-****abcd;不能反推出原文 expires_at INTEGER, -- 过期时间,Unix 秒 UTC;可空 status TEXT NOT NULL DEFAULT 'active', -- active / disabled / expired / needs_rotation last_used_at INTEGER, -- 最近使用时间 extra_json TEXT NOT NULL DEFAULT '{}', -- 扩展字段 JSON created_at INTEGER NOT NULL, -- 记录创建时间,Unix 秒 UTC updated_at INTEGER NOT NULL -- 记录最后更新时间,Unix 秒 UTC ); """) CREDENTIALS_INDEXES_SQL = textwrap.dedent("""\ CREATE INDEX IF NOT EXISTS idx_credentials_platform_type ON credentials(platform_key, credential_type); CREATE INDEX IF NOT EXISTS idx_credentials_account ON credentials(account_id); CREATE INDEX IF NOT EXISTS idx_credentials_status ON credentials(status); """) # --------------------------------------------------------------------------- # account_leases — RPA / profile concurrency locks # Prevents multiple tasks from opening the same profile_dir simultaneously. # --------------------------------------------------------------------------- ACCOUNT_LEASES_TABLE_SQL = textwrap.dedent("""\ CREATE TABLE IF NOT EXISTS account_leases ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 租约主键(自增) account_id INTEGER NOT NULL, -- 关联 accounts.id lease_token TEXT NOT NULL UNIQUE, -- 随机 token,用于 release / heartbeat holder TEXT NOT NULL, -- 持有者,例如 monitor-competitor-rates 或 run id purpose TEXT, -- 用途,例如 maersk_sim_rpa / booking_collect expires_at INTEGER NOT NULL, -- 租约过期时间,Unix 秒 UTC heartbeat_at INTEGER, -- 最近一次心跳时间 released_at INTEGER, -- 主动释放时间 status TEXT NOT NULL DEFAULT 'active', -- active / released / expired created_at INTEGER NOT NULL, -- 记录创建时间,Unix 秒 UTC updated_at INTEGER NOT NULL -- 记录最后更新时间,Unix 秒 UTC ); """) ACCOUNT_LEASES_INDEXES_SQL = textwrap.dedent("""\ CREATE INDEX IF NOT EXISTS idx_leases_account_status ON account_leases(account_id, status); CREATE INDEX IF NOT EXISTS idx_leases_token ON account_leases(lease_token); CREATE INDEX IF NOT EXISTS idx_leases_expires ON account_leases(expires_at); """) # --------------------------------------------------------------------------- # Full DDL assembly # --------------------------------------------------------------------------- ALL_DDL = ( PLATFORMS_TABLE_SQL + PLATFORMS_INDEXES_SQL + ACCOUNTS_TABLE_SQL + ACCOUNTS_INDEXES_SQL + CREDENTIALS_TABLE_SQL + CREDENTIALS_INDEXES_SQL + ACCOUNT_LEASES_TABLE_SQL + ACCOUNT_LEASES_INDEXES_SQL ) SCHEMA_VERSION = 2