refactor: align backend with company-site-member schema and system-level RBAC groups

This commit is contained in:
Chris
2026-03-30 01:59:50 +08:00
parent 0f0b197b32
commit 602c5443ad
35 changed files with 1276 additions and 690 deletions

View File

@@ -2,54 +2,138 @@ BEGIN;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'record_status') THEN
CREATE TYPE record_status AS ENUM ('active','inactive');
END IF;
END
$$;
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
authentik_sub VARCHAR(255) NOT NULL UNIQUE,
authentik_sub TEXT NOT NULL UNIQUE,
authentik_user_id INTEGER,
email VARCHAR(320),
display_name VARCHAR(255),
email TEXT UNIQUE,
display_name TEXT,
status record_status NOT NULL DEFAULT 'active',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS permissions (
CREATE TABLE IF NOT EXISTS companies (
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 INDEX IF NOT EXISTS idx_users_authentik_sub ON users(authentik_sub);
CREATE INDEX IF NOT EXISTS idx_permissions_user_id ON permissions(user_id);
CREATE TABLE IF NOT EXISTS organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_code VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
tax_id VARCHAR(32),
status VARCHAR(16) NOT NULL DEFAULT 'active',
company_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_organizations_org_code ON organizations(org_code);
CREATE INDEX IF NOT EXISTS idx_organizations_status ON organizations(status);
CREATE TABLE IF NOT EXISTS member_organizations (
CREATE TABLE IF NOT EXISTS systems (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
member_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
system_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_member_organizations_member_org UNIQUE (member_id, organization_id)
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_member_organizations_member_id ON member_organizations(member_id);
CREATE INDEX IF NOT EXISTS idx_member_organizations_org_id ON member_organizations(organization_id);
CREATE TABLE IF NOT EXISTS modules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS 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,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'scope_type') THEN
CREATE TYPE scope_type AS ENUM ('company','site');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'permission_action') THEN
CREATE TYPE permission_action AS ENUM ('view','create','update','delete','manage');
END IF;
END
$$;
CREATE TABLE IF NOT EXISTS 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,
action permission_action NOT NULL,
scope_type scope_type NOT NULL,
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()
);
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 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 TABLE IF NOT EXISTS permission_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, authentik_sub)
);
CREATE TABLE IF NOT EXISTS 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,
module TEXT NOT NULL,
action TEXT NOT NULL,
scope_type TEXT NOT NULL,
scope_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_pgp_group_rule
ON permission_group_permissions(group_id, system, module, action, scope_type, scope_id);
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_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);
COMMIT;

View File

@@ -1,27 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
org_code VARCHAR(64) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
tax_id VARCHAR(32),
status VARCHAR(16) NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_organizations_org_code ON organizations(org_code);
CREATE INDEX IF NOT EXISTS idx_organizations_status ON organizations(status);
CREATE TABLE IF NOT EXISTS member_organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
member_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_member_organizations_member_org UNIQUE (member_id, organization_id)
);
CREATE INDEX IF NOT EXISTS idx_member_organizations_member_id ON member_organizations(member_id);
CREATE INDEX IF NOT EXISTS idx_member_organizations_org_id ON member_organizations(organization_id);
COMMIT;

View File

@@ -0,0 +1,73 @@
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'record_status') THEN
CREATE TYPE record_status AS ENUM ('active','inactive');
END IF;
END
$$;
CREATE TABLE IF NOT EXISTS systems (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
system_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
INSERT INTO systems (system_key, name, status)
VALUES ('member', 'Member Center', 'active')
ON CONFLICT (system_key) DO NOTHING;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'scope_type') THEN
CREATE TYPE scope_type AS ENUM ('company','site');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'permission_action') THEN
CREATE TYPE permission_action AS ENUM ('view','create','update','delete','manage');
END IF;
END
$$;
CREATE TABLE IF NOT EXISTS permission_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
group_key TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status record_status NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS 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,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, authentik_sub)
);
CREATE TABLE IF NOT EXISTS 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,
module TEXT NOT NULL,
action TEXT NOT NULL,
scope_type TEXT NOT NULL,
scope_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_systems_system_key ON systems(system_key);
CREATE INDEX IF NOT EXISTS idx_pgm_group_id ON permission_group_members(group_id);
CREATE INDEX IF NOT EXISTS idx_pgm_authentik_sub ON permission_group_members(authentik_sub);
CREATE INDEX IF NOT EXISTS idx_pgp_group_id ON permission_group_permissions(group_id);
CREATE UNIQUE INDEX IF NOT EXISTS uq_pgp_group_rule
ON permission_group_permissions(group_id, system, module, action, scope_type, scope_id);
COMMIT;