"""邮箱登录 + 平台设置(M1 账号功能) Revision ID: 0010 Revises: 0009 Create Date: 2026-11-02 两件事: 1. `app_user.email`:邮箱当账号名,配合密码登录。唯一性用 `lower(email)` 的部分 唯一索引——邮箱大小写不敏感,`Steven@x.com` 和 `steven@x.com` 是同一个人; 允许为空(老用户先不绑)。 2. `system_setting`:平台级键值表,第一项就是 SMTP。**这张表刻意不挂 workspace_id、 也不开 RLS**:发信账号是整台服务共用的,属于平台而不是某个租户,开 RLS 反而要 编一个"当前租户"出来。写权限在 API 层用平台管理员白名单把关(deps.platform_admin), 应用角色只有表的读写权,没有建表/改结构的权。 """ from __future__ import annotations from collections.abc import Sequence from alembic import op from app.roles import APP_ROLE revision: str = "0010" down_revision: str = "0009" branch_labels: Sequence[str] | None = None depends_on: Sequence[str] | None = None CREATE_SQL = """ CREATE TABLE IF NOT EXISTS system_setting ( key VARCHAR(64) PRIMARY KEY, value JSONB NOT NULL DEFAULT '{}'::jsonb, updated_by UUID REFERENCES app_user(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), version INTEGER NOT NULL DEFAULT 1 ) """ INDEX_STATEMENTS = ( "ALTER TABLE app_user ADD COLUMN IF NOT EXISTS email VARCHAR(255)", "CREATE UNIQUE INDEX uq_app_user_email ON app_user (lower(email)) WHERE email IS NOT NULL", "COMMENT ON TABLE system_setting IS '平台级设置(SMTP 等),不挂租户、无 RLS'", ) GRANT_SQL = ( f"GRANT SELECT, INSERT, UPDATE, DELETE ON system_setting TO {APP_ROLE}", f"GRANT SELECT, UPDATE ON app_user TO {APP_ROLE}", ) def upgrade() -> None: op.execute(CREATE_SQL) for statement in INDEX_STATEMENTS: op.execute(statement) for statement in GRANT_SQL: op.execute(statement) def downgrade() -> None: op.execute("DROP INDEX IF EXISTS uq_app_user_email") op.execute("ALTER TABLE app_user DROP COLUMN IF EXISTS email") op.execute("DROP TABLE IF EXISTS system_setting")