refactor: rebuild backend around role-site authorization model
This commit is contained in:
@@ -2,25 +2,29 @@ BEGIN;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- Drop all managed tables to ensure clean rebuild
|
||||
-- Drop legacy/managed tables for clean rebuild
|
||||
DROP TABLE IF EXISTS auth_sync_state CASCADE;
|
||||
DROP TABLE IF EXISTS user_sites CASCADE;
|
||||
DROP TABLE IF EXISTS site_roles CASCADE;
|
||||
DROP TABLE IF EXISTS roles CASCADE;
|
||||
DROP TABLE IF EXISTS api_clients CASCADE;
|
||||
DROP TABLE IF EXISTS sites CASCADE;
|
||||
DROP TABLE IF EXISTS companies CASCADE;
|
||||
DROP TABLE IF EXISTS systems CASCADE;
|
||||
DROP TABLE IF EXISTS users CASCADE;
|
||||
|
||||
-- legacy tables
|
||||
DROP TABLE IF EXISTS permissions CASCADE;
|
||||
DROP TABLE IF EXISTS modules 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(),
|
||||
user_sub TEXT NOT NULL UNIQUE,
|
||||
idp_user_id VARCHAR(128),
|
||||
idp_user_id VARCHAR(128) UNIQUE,
|
||||
username TEXT UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
display_name TEXT,
|
||||
@@ -30,18 +34,12 @@ CREATE TABLE users (
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE auth_sync_state (
|
||||
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
last_synced_at TIMESTAMPTZ,
|
||||
source_version TEXT,
|
||||
last_error TEXT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE companies (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
company_key TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
legal_name TEXT,
|
||||
idp_group_id TEXT,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
@@ -51,7 +49,9 @@ 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,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
domain TEXT,
|
||||
idp_group_id TEXT,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
@@ -61,67 +61,51 @@ CREATE TABLE systems (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
system_key TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
idp_client_id TEXT NOT NULL UNIQUE,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE modules (
|
||||
CREATE TABLE roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
system_key TEXT NOT NULL REFERENCES systems(system_key) ON DELETE CASCADE,
|
||||
module_key TEXT NOT NULL UNIQUE,
|
||||
role_key TEXT NOT NULL UNIQUE,
|
||||
system_id UUID NOT NULL REFERENCES systems(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
idp_role_name TEXT NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 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,
|
||||
action VARCHAR(32) NOT NULL,
|
||||
scope_type VARCHAR(16) 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(),
|
||||
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'))
|
||||
CONSTRAINT uq_roles_system_idp_role_name UNIQUE (system_id, idp_role_name)
|
||||
);
|
||||
|
||||
CREATE TABLE permission_groups (
|
||||
CREATE TABLE site_roles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
group_key TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
site_id UUID NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
CONSTRAINT uq_site_roles_site_role UNIQUE (site_id, role_id)
|
||||
);
|
||||
|
||||
CREATE TABLE permission_group_members (
|
||||
CREATE TABLE user_sites (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
group_id UUID NOT NULL REFERENCES permission_groups(id) ON DELETE CASCADE,
|
||||
user_sub TEXT NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
site_id UUID NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, user_sub)
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_user_sites_user_site UNIQUE (user_id, site_id)
|
||||
);
|
||||
|
||||
CREATE TABLE permission_group_permissions (
|
||||
CREATE TABLE auth_sync_state (
|
||||
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(),
|
||||
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)
|
||||
entity_type VARCHAR(32) NOT NULL,
|
||||
entity_id UUID NOT NULL,
|
||||
last_synced_at TIMESTAMPTZ,
|
||||
source_version TEXT,
|
||||
last_error TEXT,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_auth_sync_state_entity UNIQUE (entity_type, entity_id)
|
||||
);
|
||||
|
||||
CREATE TABLE api_clients (
|
||||
@@ -140,26 +124,18 @@ CREATE TABLE api_clients (
|
||||
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;
|
||||
|
||||
CREATE INDEX idx_users_user_sub ON users(user_sub);
|
||||
CREATE INDEX idx_users_username ON users(username);
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
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_user_sub ON permission_group_members(user_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_roles_system_id ON roles(system_id);
|
||||
CREATE INDEX idx_site_roles_site_id ON site_roles(site_id);
|
||||
CREATE INDEX idx_site_roles_role_id ON site_roles(role_id);
|
||||
CREATE INDEX idx_user_sites_user_id ON user_sites(user_id);
|
||||
CREATE INDEX idx_user_sites_site_id ON user_sites(site_id);
|
||||
CREATE INDEX idx_auth_sync_entity ON auth_sync_state(entity_type, entity_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_system_key ON modules(system_key);
|
||||
CREATE INDEX idx_modules_module_key ON modules(module_key);
|
||||
|
||||
COMMIT;
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS idp_user_id VARCHAR(128);
|
||||
@@ -1,16 +0,0 @@
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS username TEXT;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'uq_users_username'
|
||||
) THEN
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT uq_users_username UNIQUE (username);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
@@ -1,73 +0,0 @@
|
||||
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,
|
||||
user_sub TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT uq_permission_group_members_group_sub UNIQUE (group_id, user_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_user_sub ON permission_group_members(user_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;
|
||||
@@ -1,27 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- users / master tables
|
||||
ALTER TABLE users ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
ALTER TABLE companies ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
ALTER TABLE sites ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
ALTER TABLE systems ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
ALTER TABLE modules ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
ALTER TABLE permission_groups ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
|
||||
-- api_clients
|
||||
ALTER TABLE api_clients ALTER COLUMN status TYPE VARCHAR(16) USING status::text;
|
||||
|
||||
-- user scoped permissions
|
||||
ALTER TABLE user_scope_permissions ALTER COLUMN action TYPE VARCHAR(32) USING action::text;
|
||||
ALTER TABLE user_scope_permissions ALTER COLUMN scope_type TYPE VARCHAR(16) USING scope_type::text;
|
||||
|
||||
-- keep check constraint compatible with varchar
|
||||
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))
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
@@ -1,6 +0,0 @@
|
||||
ALTER TABLE users
|
||||
ALTER COLUMN idp_user_id TYPE VARCHAR(128)
|
||||
USING CASE
|
||||
WHEN idp_user_id IS NULL THEN NULL
|
||||
ELSE idp_user_id::text
|
||||
END;
|
||||
@@ -1,43 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'idp_sub'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'user_sub'
|
||||
) THEN
|
||||
ALTER TABLE users RENAME COLUMN idp_sub TO user_sub;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'idp_user_id'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'users' AND column_name = 'idp_user_id'
|
||||
) THEN
|
||||
ALTER TABLE users RENAME COLUMN idp_user_id TO idp_user_id;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'permission_group_members' AND column_name = 'idp_sub'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'permission_group_members' AND column_name = 'user_sub'
|
||||
) THEN
|
||||
ALTER TABLE permission_group_members RENAME COLUMN idp_sub TO user_sub;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
ALTER INDEX IF EXISTS idx_users_idp_sub RENAME TO idx_users_user_sub;
|
||||
ALTER INDEX IF EXISTS idx_pgm_idp_sub RENAME TO idx_pgm_user_sub;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_user_sub ON users(user_sub);
|
||||
CREATE INDEX IF NOT EXISTS idx_pgm_user_sub ON permission_group_members(user_sub);
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user