Files
ai_site/platform/internal/meta/migrate.go
whm b04b180d30 feat: harden loose-offline sync for user JWT, schema, and console ops
Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 09:47:35 +08:00

225 lines
8.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package meta
import (
"context"
"database/sql"
"fmt"
)
const metaSchemaSQL = `
CREATE SCHEMA IF NOT EXISTS platform_meta;
CREATE TABLE IF NOT EXISTS platform_meta.tenants (
tenant_id BIGSERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL,
slug VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS platform_meta.users (
user_id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT REFERENCES platform_meta.tenants(tenant_id),
username VARCHAR(64) NOT NULL,
password_hash TEXT NOT NULL,
display_name VARCHAR(128) NOT NULL DEFAULT '',
role VARCHAR(32) NOT NULL DEFAULT 'pending',
status VARCHAR(32) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_users_username UNIQUE (username)
);
CREATE INDEX IF NOT EXISTS idx_users_tenant ON platform_meta.users (tenant_id);
-- 兼容旧库允许无租户pending
ALTER TABLE platform_meta.users ALTER COLUMN tenant_id DROP NOT NULL;
ALTER TABLE platform_meta.users ADD COLUMN IF NOT EXISTS status VARCHAR(32) NOT NULL DEFAULT 'active';
CREATE TABLE IF NOT EXISTS platform_meta.tenant_apps (
app_id UUID PRIMARY KEY,
tenant_id BIGINT NOT NULL,
slug VARCHAR(64) NOT NULL,
name VARCHAR(128) NOT NULL,
schema_name VARCHAR(64) NOT NULL,
engine VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL,
blueprint_json JSONB NOT NULL,
ddl_json JSONB NOT NULL DEFAULT '[]'::jsonb,
endpoints_json JSONB NOT NULL DEFAULT '[]'::jsonb,
error_msg TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_tenant_apps_tenant_slug UNIQUE (tenant_id, slug)
);
CREATE INDEX IF NOT EXISTS idx_tenant_apps_tenant_status
ON platform_meta.tenant_apps (tenant_id, status);
ALTER TABLE platform_meta.tenant_apps
ADD COLUMN IF NOT EXISTS database_name VARCHAR(64) NOT NULL DEFAULT '';
CREATE TABLE IF NOT EXISTS platform_meta.audit_logs (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
action VARCHAR(64) NOT NULL,
detail TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 智能体服务账号(机器身份,供宿主自动登录)
CREATE TABLE IF NOT EXISTS platform_meta.agent_accounts (
agent_id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id),
name VARCHAR(128) NOT NULL,
client_id VARCHAR(64) NOT NULL,
client_secret_hash TEXT NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'active',
created_by BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_token_at TIMESTAMPTZ,
CONSTRAINT uq_agent_client_id UNIQUE (client_id)
);
CREATE INDEX IF NOT EXISTS idx_agent_accounts_tenant
ON platform_meta.agent_accounts (tenant_id);
ALTER TABLE platform_meta.agent_accounts
ADD COLUMN IF NOT EXISTS host_key VARCHAR(128) NOT NULL DEFAULT '';
CREATE UNIQUE INDEX IF NOT EXISTS uq_agent_tenant_host_key
ON platform_meta.agent_accounts (tenant_id, host_key)
WHERE host_key <> '';
CREATE TABLE IF NOT EXISTS platform_meta.agent_permissions (
agent_id BIGINT NOT NULL REFERENCES platform_meta.agent_accounts(agent_id) ON DELETE CASCADE,
perm VARCHAR(64) NOT NULL,
PRIMARY KEY (agent_id, perm)
);
CREATE TABLE IF NOT EXISTS platform_meta.agent_app_grants (
agent_id BIGINT NOT NULL REFERENCES platform_meta.agent_accounts(agent_id) ON DELETE CASCADE,
slug VARCHAR(64) NOT NULL,
PRIMARY KEY (agent_id, slug)
);
-- 可编辑角色(租户级)
CREATE TABLE IF NOT EXISTS platform_meta.roles (
role_id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id),
code VARCHAR(64) NOT NULL,
name VARCHAR(128) NOT NULL,
description TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_roles_tenant_code UNIQUE (tenant_id, code)
);
CREATE INDEX IF NOT EXISTS idx_roles_tenant ON platform_meta.roles (tenant_id);
CREATE TABLE IF NOT EXISTS platform_meta.role_permissions (
role_id BIGINT NOT NULL REFERENCES platform_meta.roles(role_id) ON DELETE CASCADE,
perm VARCHAR(64) NOT NULL,
PRIMARY KEY (role_id, perm)
);
ALTER TABLE platform_meta.agent_accounts
ADD COLUMN IF NOT EXISTS role_id BIGINT;
-- Z8b智能体绑定同步通道 / 线上库 / 模块落库名
ALTER TABLE platform_meta.agent_accounts
ADD COLUMN IF NOT EXISTS channel_id VARCHAR(128) NOT NULL DEFAULT '';
ALTER TABLE platform_meta.agent_accounts
ADD COLUMN IF NOT EXISTS online_db_id VARCHAR(128) NOT NULL DEFAULT '';
ALTER TABLE platform_meta.agent_accounts
ADD COLUMN IF NOT EXISTS database_name VARCHAR(128) NOT NULL DEFAULT '';
-- 租户邀请码pending 用户凭码加入已有公司
CREATE TABLE IF NOT EXISTS platform_meta.tenant_invites (
invite_id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE,
code VARCHAR(64) NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'editor',
created_by BIGINT NOT NULL DEFAULT 0,
max_uses INT NOT NULL DEFAULT 1,
used_count INT NOT NULL DEFAULT 0,
status VARCHAR(32) NOT NULL DEFAULT 'active',
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_tenant_invites_code UNIQUE (code)
);
CREATE INDEX IF NOT EXISTS idx_tenant_invites_tenant
ON platform_meta.tenant_invites (tenant_id);
-- 租户内多级组织(默认最多 5 级)
CREATE TABLE IF NOT EXISTS platform_meta.org_units (
org_unit_id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE,
parent_id BIGINT REFERENCES platform_meta.org_units(org_unit_id) ON DELETE CASCADE,
name VARCHAR(128) NOT NULL,
code VARCHAR(64) NOT NULL DEFAULT '',
depth INT NOT NULL DEFAULT 1,
path VARCHAR(512) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_org_units_tenant_parent
ON platform_meta.org_units (tenant_id, parent_id);
CREATE INDEX IF NOT EXISTS idx_org_units_tenant_path
ON platform_meta.org_units (tenant_id, path);
CREATE UNIQUE INDEX IF NOT EXISTS uq_org_units_tenant_code
ON platform_meta.org_units (tenant_id, code)
WHERE code <> '';
ALTER TABLE platform_meta.users
ADD COLUMN IF NOT EXISTS org_unit_id BIGINT;
ALTER TABLE platform_meta.tenant_invites
ADD COLUMN IF NOT EXISTS org_unit_id BIGINT NOT NULL DEFAULT 0;
ALTER TABLE platform_meta.users
ALTER COLUMN role TYPE VARCHAR(64);
CREATE TABLE IF NOT EXISTS platform_meta.tenant_permissions (
tenant_id BIGINT NOT NULL REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE,
perm VARCHAR(64) NOT NULL,
PRIMARY KEY (tenant_id, perm)
);
CREATE TABLE IF NOT EXISTS platform_meta.tenant_entitlement_state (
tenant_id BIGINT PRIMARY KEY REFERENCES platform_meta.tenants(tenant_id) ON DELETE CASCADE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 公司路径 slugwww.example.com/{slug}/);旧库补列
ALTER TABLE platform_meta.tenants ADD COLUMN IF NOT EXISTS slug VARCHAR(64) NOT NULL DEFAULT '';
CREATE UNIQUE INDEX IF NOT EXISTS uq_tenants_slug ON platform_meta.tenants (slug) WHERE slug <> '';
-- 手机号(可绑定;非空时全局唯一,可用于登录)
ALTER TABLE platform_meta.users ADD COLUMN IF NOT EXISTS phone VARCHAR(20) NOT NULL DEFAULT '';
CREATE UNIQUE INDEX IF NOT EXISTS uq_users_phone ON platform_meta.users (phone) WHERE phone <> '';
-- 已绑手机时可禁用用户名登录(仅手机号+密码/短信)
ALTER TABLE platform_meta.users ADD COLUMN IF NOT EXISTS username_login_disabled BOOLEAN NOT NULL DEFAULT false;
-- 授权租约已消费 id删 leases 目录后仍能拦截旧延期包)
CREATE TABLE IF NOT EXISTS platform_meta.license_consumed (
lease_id VARCHAR(64) PRIMARY KEY,
active BOOLEAN NOT NULL DEFAULT false,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_license_consumed_active ON platform_meta.license_consumed (active) WHERE active;
`
// EnsureSchema 创建平台元数据表(幂等)。
func EnsureSchema(ctx context.Context, db *sql.DB) error {
if db == nil {
return fmt.Errorf("db is nil")
}
if _, err := db.ExecContext(ctx, metaSchemaSQL); err != nil {
return fmt.Errorf("meta migrate: %w", err)
}
return nil
}