chore(db): rebuild init schema with drop-recreate and group-centric constraints

This commit is contained in:
Chris
2026-03-30 19:42:05 +08:00
parent 61cab48fca
commit 357ebad821

View File

@@ -2,7 +2,22 @@ BEGIN;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS users (
-- Drop all managed tables to ensure clean rebuild
DROP TABLE IF EXISTS auth_sync_state CASCADE;
DROP TABLE IF EXISTS user_scope_permissions CASCADE;
DROP TABLE IF EXISTS permission_group_permissions CASCADE;
DROP TABLE IF EXISTS permission_group_members CASCADE;
DROP TABLE IF EXISTS permission_groups CASCADE;
DROP TABLE IF EXISTS modules CASCADE;
DROP TABLE IF EXISTS systems CASCADE;
DROP TABLE IF EXISTS sites CASCADE;
DROP TABLE IF EXISTS companies CASCADE;
DROP TABLE IF EXISTS users CASCADE;
DROP TABLE IF EXISTS api_clients CASCADE;
-- remove legacy table if present
DROP TABLE IF EXISTS permissions CASCADE;
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
authentik_sub TEXT NOT NULL UNIQUE,
authentik_user_id INTEGER,
@@ -14,7 +29,7 @@ CREATE TABLE IF NOT EXISTS users (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS auth_sync_state (
CREATE TABLE auth_sync_state (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
last_synced_at TIMESTAMPTZ,
source_version TEXT,
@@ -22,7 +37,7 @@ CREATE TABLE IF NOT EXISTS auth_sync_state (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS companies (
CREATE TABLE companies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
@@ -31,7 +46,7 @@ CREATE TABLE IF NOT EXISTS companies (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS sites (
CREATE TABLE sites (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
site_key TEXT NOT NULL UNIQUE,
company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
@@ -41,7 +56,7 @@ CREATE TABLE IF NOT EXISTS sites (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS systems (
CREATE TABLE systems (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
system_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
@@ -50,7 +65,7 @@ CREATE TABLE IF NOT EXISTS systems (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS modules (
CREATE TABLE modules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
@@ -59,20 +74,8 @@ CREATE TABLE IF NOT EXISTS modules (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- legacy table: 保留相容舊流程
CREATE TABLE IF NOT EXISTS permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
scope_type VARCHAR(32) NOT NULL,
scope_id VARCHAR(128) NOT NULL,
module VARCHAR(128) NOT NULL,
action VARCHAR(32) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_permissions_user_scope_module_action
UNIQUE (user_id, scope_type, scope_id, module, action)
);
CREATE TABLE IF NOT EXISTS user_scope_permissions (
-- direct permission table retained only for compatibility
CREATE TABLE user_scope_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
module_id UUID NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
@@ -81,18 +84,14 @@ CREATE TABLE IF NOT EXISTS user_scope_permissions (
company_id UUID REFERENCES companies(id) ON DELETE CASCADE,
site_id UUID REFERENCES sites(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT user_scope_permissions_scope_check
CHECK (scope_type = 'site' AND site_id IS NOT NULL AND company_id IS NULL),
CONSTRAINT user_scope_permissions_action_check
CHECK (action IN ('view', 'edit'))
);
ALTER TABLE user_scope_permissions DROP CONSTRAINT IF EXISTS user_scope_permissions_check;
ALTER TABLE user_scope_permissions
ADD CONSTRAINT user_scope_permissions_check
CHECK (
((scope_type = 'company' AND company_id IS NOT NULL AND site_id IS NULL)
OR (scope_type = 'site' AND site_id IS NOT NULL AND company_id IS NULL))
);
CREATE TABLE IF NOT EXISTS permission_groups (
CREATE TABLE permission_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
@@ -101,7 +100,7 @@ CREATE TABLE IF NOT EXISTS permission_groups (
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS permission_group_members (
CREATE TABLE permission_group_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE,
authentik_sub TEXT NOT NULL,
@@ -109,7 +108,7 @@ CREATE TABLE IF NOT EXISTS permission_group_members (
CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, authentik_sub)
);
CREATE TABLE IF NOT EXISTS permission_group_permissions (
CREATE TABLE permission_group_permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE,
system TEXT NOT NULL,
@@ -118,10 +117,12 @@ CREATE TABLE IF NOT EXISTS permission_group_permissions (
scope_type TEXT NOT NULL,
scope_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT permission_group_permissions_scope_check CHECK (scope_type = 'site'),
CONSTRAINT permission_group_permissions_action_check CHECK (action IN ('view', 'edit')),
CONSTRAINT uq_pgp_group_rule UNIQUE (group_id, system, module, action, scope_type, scope_id)
);
CREATE TABLE IF NOT EXISTS api_clients (
CREATE TABLE api_clients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
@@ -141,22 +142,20 @@ INSERT INTO systems (system_key, name, status)
VALUES ('member', 'Member Center', 'active')
ON CONFLICT (system_key) DO NOTHING;
CREATE INDEX IF NOT EXISTS idx_users_authentik_sub ON users(authentik_sub);
CREATE INDEX IF NOT EXISTS idx_sites_company_id ON sites(company_id);
CREATE INDEX IF NOT EXISTS idx_permissions_user_id ON permissions(user_id);
CREATE INDEX IF NOT EXISTS idx_usp_user_id ON user_scope_permissions(user_id);
CREATE INDEX IF NOT EXISTS idx_usp_module_id ON user_scope_permissions(module_id);
CREATE INDEX IF NOT EXISTS idx_usp_company_id ON user_scope_permissions(company_id);
CREATE INDEX IF NOT EXISTS idx_usp_site_id ON user_scope_permissions(site_id);
CREATE UNIQUE INDEX IF NOT EXISTS uq_usp_company
ON user_scope_permissions(user_id, module_id, action, scope_type, company_id)
WHERE scope_type = 'company';
CREATE UNIQUE INDEX IF NOT EXISTS uq_usp_site
ON user_scope_permissions(user_id, module_id, action, scope_type, site_id)
WHERE scope_type = 'site';
CREATE INDEX IF NOT EXISTS idx_api_clients_status ON api_clients(status);
CREATE INDEX IF NOT EXISTS idx_api_clients_expires_at ON api_clients(expires_at);
CREATE INDEX IF NOT EXISTS idx_systems_system_key ON systems(system_key);
CREATE INDEX IF NOT EXISTS idx_modules_module_key ON modules(module_key);
CREATE INDEX idx_users_authentik_sub ON users(authentik_sub);
CREATE INDEX idx_sites_company_id ON sites(company_id);
CREATE INDEX idx_usp_user_id ON user_scope_permissions(user_id);
CREATE INDEX idx_usp_module_id ON user_scope_permissions(module_id);
CREATE INDEX idx_usp_site_id ON user_scope_permissions(site_id);
CREATE UNIQUE INDEX uq_usp_site
ON user_scope_permissions(user_id, module_id, action, scope_type, site_id);
CREATE INDEX idx_pgm_group_id ON permission_group_members(group_id);
CREATE INDEX idx_pgm_authentik_sub ON permission_group_members(authentik_sub);
CREATE INDEX idx_pgp_group_id ON permission_group_permissions(group_id);
CREATE INDEX idx_pgp_scope_site ON permission_group_permissions(scope_id);
CREATE INDEX idx_api_clients_status ON api_clients(status);
CREATE INDEX idx_api_clients_expires_at ON api_clients(expires_at);
CREATE INDEX idx_systems_system_key ON systems(system_key);
CREATE INDEX idx_modules_module_key ON modules(module_key);
COMMIT;