create extension if not exists "pgcrypto"; create table if not exists app_users ( id uuid primary key default gen_random_uuid(), username varchar(100) unique not null, password_hash text not null, is_active boolean not null default true, created_at timestamptz not null default now() ); create table if not exists roles ( id uuid primary key default gen_random_uuid(), code varchar(100) unique not null, name varchar(120) not null, description text, created_at timestamptz not null default now() ); create table if not exists permissions ( id uuid primary key default gen_random_uuid(), resource varchar(100) not null, action varchar(50) not null, unique(resource, action) ); create table if not exists user_roles ( user_id uuid not null references app_users(id) on delete cascade, role_id uuid not null references roles(id) on delete cascade, primary key(user_id, role_id) ); create table if not exists table_groups ( id uuid primary key default gen_random_uuid(), code varchar(100) unique not null, name varchar(120) not null, description text ); create table if not exists table_group_tables ( id uuid primary key default gen_random_uuid(), table_group_id uuid not null references table_groups(id) on delete cascade, table_name varchar(120) unique not null ); create table if not exists role_permissions ( id uuid primary key default gen_random_uuid(), role_id uuid not null references roles(id) on delete cascade, permission_id uuid not null references permissions(id) on delete cascade, table_group_id uuid references table_groups(id) on delete cascade, unique nulls not distinct (role_id, permission_id, table_group_id) ); create table if not exists audit_logs ( id bigserial primary key, user_id uuid references app_users(id) on delete set null, action varchar(100) not null, resource_type varchar(100) not null, resource_name varchar(150), sql_text text, details jsonb not null default '{}'::jsonb, success boolean not null default true, created_at timestamptz not null default now() ); create table if not exists finance_entries ( id bigserial primary key, title varchar(120) not null, amount numeric(12, 2) not null, currency varchar(10) not null default 'USD', created_at timestamptz not null default now() ); create table if not exists user_profiles ( id bigserial primary key, email varchar(190) not null unique, full_name varchar(190) not null, status varchar(50) not null default 'active', created_at timestamptz not null default now() ); create table if not exists app_logs ( id bigserial primary key, level varchar(20) not null, message text not null, created_at timestamptz not null default now() ); insert into table_groups (code, name, description) values ('finance', 'Finance', 'Financial tables'), ('users', 'Users', 'User domain tables'), ('logs', 'Logs', 'Log and audit related tables') on conflict (code) do nothing; insert into table_group_tables (table_group_id, table_name) select tg.id, mapping.table_name from table_groups tg join ( values ('finance', 'finance_entries'), ('users', 'user_profiles'), ('logs', 'app_logs') ) as mapping(group_code, table_name) on mapping.group_code = tg.code on conflict (table_name) do nothing; insert into permissions (resource, action) values ('global', 'read'), ('global', 'write'), ('global', 'delete'), ('global', 'schema'), ('global', 'execute') on conflict (resource, action) do nothing; insert into roles (code, name, description) values ('root', 'Root', 'Full access'), ('group_admin', 'Group Admin', 'Admin of assigned table groups'), ('editor', 'Editor', 'Read and write access'), ('viewer', 'Viewer', 'Read-only access') on conflict (code) do nothing; insert into app_users (username, password_hash) values ('root', crypt('ChangeMe123!', gen_salt('bf'))) on conflict (username) do nothing; insert into user_roles (user_id, role_id) select u.id, r.id from app_users u join roles r on r.code = 'root' where u.username = 'root' on conflict do nothing; insert into role_permissions (role_id, permission_id, table_group_id) select r.id, p.id, null from roles r join permissions p on p.resource = 'global' where r.code = 'root' on conflict do nothing; insert into finance_entries (title, amount, currency) values ('Subscription revenue', 1500.00, 'USD'), ('Cloud hosting', -420.00, 'USD'); insert into user_profiles (email, full_name, status) values ('root@example.com', 'Root Administrator', 'active'), ('analyst@example.com', 'Finance Analyst', 'active') on conflict (email) do nothing; insert into app_logs (level, message) values ('info', 'System initialized'), ('warn', 'Background job delayed');