nxtgauge-backend-rust/scripts/init-db.sql

1054 lines
45 KiB
PL/PgSQL

-- ============================================================================
-- Nxtgauge Database — Complete Schema
-- Source: nxtgauge_database_source_of_truth.md
-- No duplicates. Every domain table from the document included.
-- ============================================================================
BEGIN;
-- ============================================================================
-- 1. IDENTITY & ACCESS CONTROL (users, roles, permissions, sessions, tokens)
-- ============================================================================
CREATE TABLE IF NOT EXISTS roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
audience VARCHAR(50) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS internal_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL UNIQUE REFERENCES roles(id) ON DELETE CASCADE,
description TEXT,
department_id UUID,
can_approve_requests BOOLEAN NOT NULL DEFAULT false,
can_manage_system_settings BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE IF NOT EXISTS external_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL UNIQUE REFERENCES roles(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
key VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
module VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
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_key VARCHAR(100) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(role_id, permission_key)
);
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20) UNIQUE,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
account_type TEXT DEFAULT 'INDIVIDUAL',
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
email_verified BOOLEAN NOT NULL DEFAULT false,
phone_verified BOOLEAN NOT NULL DEFAULT false,
role_id UUID REFERENCES roles(id) ON DELETE SET NULL,
last_login_at TIMESTAMPTZ,
email_verification_token VARCHAR(255),
email_verification_expires_at TIMESTAMPTZ,
reset_password_token VARCHAR(255),
reset_password_expires_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(255) UNIQUE NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
revoked BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash ON refresh_tokens(token_hash);
CREATE TABLE IF NOT EXISTS user_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
session_token TEXT UNIQUE NOT NULL,
ip_address TEXT,
user_agent TEXT,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_user_sessions_token ON user_sessions(session_token);
CREATE TABLE IF NOT EXISTS user_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, role_id)
);
CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
CREATE TABLE IF NOT EXISTS user_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
settings JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 2. USER ROLE PROFILES (users can have multiple role profiles)
-- ============================================================================
CREATE TABLE IF NOT EXISTS user_role_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_key VARCHAR(50) NOT NULL,
display_name TEXT,
bio TEXT,
location TEXT,
avatar_url TEXT,
phone TEXT,
email TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
verification_status VARCHAR(50) DEFAULT 'PENDING',
approval_status VARCHAR(50) DEFAULT 'PENDING',
rejection_reason TEXT,
approved_at TIMESTAMPTZ,
verified_at TIMESTAMPTZ,
is_profile_public BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, role_key)
);
CREATE INDEX IF NOT EXISTS idx_user_role_profiles_user_id ON user_role_profiles(user_id);
CREATE INDEX IF NOT EXISTS idx_user_role_profiles_role_key ON user_role_profiles(role_key);
-- ============================================================================
-- 3. ROLE EXTENSION TABLES (10 profession profiles)
-- ============================================================================
CREATE TABLE IF NOT EXISTS photographer_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
specialties TEXT[] DEFAULT '{}', camera_brands TEXT[] DEFAULT '{}',
studio_available BOOLEAN NOT NULL DEFAULT false, outdoor_shoots BOOLEAN NOT NULL DEFAULT true,
travel_radius_km INTEGER DEFAULT 50, starting_price_inr INTEGER DEFAULT 0,
custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS tutor_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
subjects TEXT[] DEFAULT '{}', board_types TEXT[] DEFAULT '{}',
qualification VARCHAR(255), teaches_online BOOLEAN NOT NULL DEFAULT true,
teaches_offline BOOLEAN NOT NULL DEFAULT true, experience_years INTEGER DEFAULT 0,
hourly_rate_inr INTEGER DEFAULT 0, custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS makeup_artist_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
specializations TEXT[] DEFAULT '{}', kit_brands TEXT[] DEFAULT '{}',
home_service BOOLEAN NOT NULL DEFAULT true, studio_available BOOLEAN NOT NULL DEFAULT false,
starting_price_inr INTEGER DEFAULT 0, custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS developer_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
tech_stack TEXT[] DEFAULT '{}', experience_years INTEGER DEFAULT 0,
availability VARCHAR(50) DEFAULT 'FULL_TIME', hourly_rate_inr INTEGER DEFAULT 0,
remote_ok BOOLEAN NOT NULL DEFAULT true, custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS video_editor_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
software_skills TEXT[] DEFAULT '{}', style_tags TEXT[] DEFAULT '{}',
turnaround_days INTEGER DEFAULT 7, starting_price_inr INTEGER DEFAULT 0,
custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS graphic_designer_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
design_tools TEXT[] DEFAULT '{}', style_tags TEXT[] DEFAULT '{}',
brand_experience BOOLEAN NOT NULL DEFAULT false, starting_price_inr INTEGER DEFAULT 0,
custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS social_media_manager_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
platforms TEXT[] DEFAULT '{}', industries TEXT[] DEFAULT '{}',
content_types TEXT[] DEFAULT '{}', avg_follower_growth_pct INTEGER DEFAULT 0,
starting_price_inr INTEGER DEFAULT 0, custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS fitness_trainer_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
disciplines TEXT[] DEFAULT '{}', certifications TEXT[] DEFAULT '{}',
online_sessions BOOLEAN NOT NULL DEFAULT true, home_visits BOOLEAN NOT NULL DEFAULT false,
gym_based BOOLEAN NOT NULL DEFAULT false, per_session_rate_inr INTEGER DEFAULT 0,
custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS catering_service_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
business_name VARCHAR(255), bio TEXT, location VARCHAR(255),
cuisine_types TEXT[] DEFAULT '{}', event_types TEXT[] DEFAULT '{}',
min_guests INTEGER DEFAULT 10, max_guests INTEGER DEFAULT 500,
has_setup_team BOOLEAN NOT NULL DEFAULT true, has_serving_staff BOOLEAN NOT NULL DEFAULT true,
price_per_head_inr INTEGER DEFAULT 0, custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS ugc_content_creator_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE SET NULL,
display_name VARCHAR(255), bio TEXT, location VARCHAR(255),
platforms TEXT[] DEFAULT '{}', content_niches TEXT[] DEFAULT '{}',
content_formats TEXT[] DEFAULT '{}', follower_count INTEGER DEFAULT 0,
avg_views_per_post INTEGER DEFAULT 0, has_media_kit BOOLEAN NOT NULL DEFAULT false,
instagram_handle VARCHAR(100), youtube_channel_url VARCHAR(500),
starting_price_inr INTEGER DEFAULT 0, custom_data JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', rejection_reason TEXT, approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 4. COMPANY PROFILES
-- ============================================================================
CREATE TABLE IF NOT EXISTS company_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
company_name VARCHAR(255) NOT NULL,
business_type VARCHAR(100), registration_number VARCHAR(100),
industry VARCHAR(150), website_url VARCHAR(255), employee_count INT,
gst_number VARCHAR(50), contact_name VARCHAR(255),
contact_email VARCHAR(255), contact_phone VARCHAR(20),
address_line1 VARCHAR(500), city VARCHAR(100), state VARCHAR(100),
country VARCHAR(100) NOT NULL DEFAULT 'India', postal_code VARCHAR(20),
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
free_job_slots INTEGER NOT NULL DEFAULT 1,
purchased_job_slots INTEGER NOT NULL DEFAULT 0,
free_contact_views INTEGER NOT NULL DEFAULT 30,
purchased_contact_views INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 5. CUSTOMER PROFILES
-- ============================================================================
CREATE TABLE IF NOT EXISTS customer_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
full_name VARCHAR(255), bio TEXT, phone VARCHAR(20),
city VARCHAR(100), area VARCHAR(100), location VARCHAR(255),
preferred_professions TEXT[] DEFAULT '{}',
active_requirement_count INTEGER NOT NULL DEFAULT 0,
custom_data JSONB DEFAULT '{}'::jsonb,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 6. JOB SEEKER PROFILES
-- ============================================================================
CREATE TABLE IF NOT EXISTS job_seeker_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
full_name VARCHAR(255), bio TEXT, location VARCHAR(255), summary TEXT,
experience_years INTEGER DEFAULT 0, skills TEXT[] DEFAULT '{}',
resume_url VARCHAR(500), active_application_count INTEGER NOT NULL DEFAULT 0,
custom_data JSONB DEFAULT '{}'::jsonb,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 7. PORTFOLIO DOMAIN (native content only, no external links)
-- ============================================================================
CREATE TABLE IF NOT EXISTS portfolio_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL, description TEXT,
tags TEXT[] DEFAULT '{}', display_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_user_id ON portfolio_items(user_id);
CREATE TABLE IF NOT EXISTS portfolio_images (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
portfolio_item_id UUID NOT NULL REFERENCES portfolio_items(id) ON DELETE CASCADE,
file_url VARCHAR(500) NOT NULL, display_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS services (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, description TEXT,
price INTEGER NOT NULL DEFAULT 0, duration_minutes INTEGER,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 8. VERIFICATION DOMAIN
-- ============================================================================
CREATE TABLE IF NOT EXISTS verification_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE CASCADE,
verification_type VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
submitted_at TIMESTAMPTZ,
reviewed_at TIMESTAMPTZ,
reviewed_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_verification_requests_status ON verification_requests(status);
CREATE TABLE IF NOT EXISTS verification_documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
verification_request_id UUID NOT NULL REFERENCES verification_requests(id) ON DELETE CASCADE,
document_type VARCHAR(100) NOT NULL,
file_url VARCHAR(500) NOT NULL,
file_name VARCHAR(255),
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
reviewed_at TIMESTAMPTZ,
reviewed_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS verification_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
verification_request_id UUID NOT NULL REFERENCES verification_requests(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL,
old_status VARCHAR(50),
new_status VARCHAR(50),
acted_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- verifications table used by Rust code
CREATE TABLE IF NOT EXISTS verifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_key VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
priority VARCHAR(10) NOT NULL DEFAULT 'LOW',
case_type VARCHAR(50) NOT NULL,
payload JSONB NOT NULL DEFAULT '{}',
documents JSONB NOT NULL DEFAULT '[]',
notes TEXT,
rejection_reason TEXT,
assigned_to UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_verifications_user_id ON verifications(user_id);
CREATE INDEX IF NOT EXISTS idx_verifications_status ON verifications(status);
CREATE INDEX IF NOT EXISTS idx_verifications_case_type ON verifications(case_type);
-- ============================================================================
-- 9. APPROVAL DOMAIN
-- ============================================================================
CREATE TABLE IF NOT EXISTS approval_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
entity_type VARCHAR(50) NOT NULL,
entity_id UUID NOT NULL,
approval_type VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
submitted_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
reviewed_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
reviewed_at TIMESTAMPTZ,
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_approval_requests_status ON approval_requests(status);
CREATE INDEX IF NOT EXISTS idx_approval_requests_entity ON approval_requests(entity_type, entity_id);
CREATE TABLE IF NOT EXISTS approval_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
approval_request_id UUID NOT NULL REFERENCES approval_requests(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL,
old_status VARCHAR(50),
new_status VARCHAR(50),
acted_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
remarks TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 10. MARKETPLACE DOMAIN (jobs, leads, reviews)
-- ============================================================================
CREATE TABLE IF NOT EXISTS jobs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_id UUID NOT NULL REFERENCES company_profiles(id) ON DELETE CASCADE,
title VARCHAR(200) NOT NULL, category VARCHAR(100),
description TEXT NOT NULL, location VARCHAR(255) NOT NULL,
job_type VARCHAR(50) NOT NULL DEFAULT 'FULL_TIME',
mode_of_work VARCHAR(50),
salary_min INTEGER, salary_max INTEGER, budget_inr INTEGER,
experience_years INTEGER, skills TEXT[] DEFAULT '{}',
posted_by_user_id UUID REFERENCES users(id),
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
rejection_reason TEXT, expires_at TIMESTAMPTZ,
approved_at TIMESTAMPTZ, approved_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_jobs_company_id ON jobs(company_id);
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
CREATE TABLE IF NOT EXISTS job_applications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_id UUID NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
applicant_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
cover_note TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'APPLIED',
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_job_applications_job_id ON job_applications(job_id);
CREATE TABLE IF NOT EXISTS leads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
profession_key VARCHAR(50) NOT NULL,
title VARCHAR(200) NOT NULL, description TEXT NOT NULL,
location VARCHAR(255) NOT NULL, budget_inr INTEGER,
required_date DATE, extra_data_json JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
rejection_reason TEXT,
request_count INTEGER NOT NULL DEFAULT 0,
accepted_count INTEGER NOT NULL DEFAULT 0,
expires_at TIMESTAMPTZ,
approved_at TIMESTAMPTZ, approved_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_leads_status ON leads(status);
CREATE INDEX IF NOT EXISTS idx_leads_profession_key ON leads(profession_key);
CREATE TABLE IF NOT EXISTS lead_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lead_id UUID REFERENCES leads(id) ON DELETE CASCADE,
user_role_profile_id UUID REFERENCES user_role_profiles(id) ON DELETE CASCADE,
professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
tracecoins_reserved INTEGER NOT NULL DEFAULT 25,
remarks TEXT,
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '1 day'),
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_lead_requests_lead_id ON lead_requests(lead_id);
CREATE INDEX IF NOT EXISTS idx_lead_requests_status ON lead_requests(status);
CREATE TABLE IF NOT EXISTS reviews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reviewer_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
entity_type VARCHAR(50) NOT NULL DEFAULT 'PLATFORM',
entity_id VARCHAR(255),
title VARCHAR(255),
rating SMALLINT CHECK (rating >= 1 AND rating <= 5),
review_text TEXT,
reviewer_name VARCHAR(255),
subject_type VARCHAR(50) NOT NULL DEFAULT 'PLATFORM',
subject_id VARCHAR(255),
status VARCHAR(20) NOT NULL DEFAULT 'PUBLISHED',
is_published BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 11. FINANCE DOMAIN (wallets, ledger, pricing, payments, invoices, orders)
-- ============================================================================
CREATE TABLE IF NOT EXISTS tracecoin_wallets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
balance INTEGER NOT NULL DEFAULT 0,
reserved INTEGER NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS tracecoin_ledger (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
wallet_id UUID NOT NULL REFERENCES tracecoin_wallets(id),
amount INTEGER NOT NULL,
transaction_type VARCHAR(20) NOT NULL,
reference_type VARCHAR(100),
reference_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_wallet_id ON tracecoin_ledger(wallet_id);
CREATE OR REPLACE FUNCTION prevent_tracecoin_ledger_mutation()
RETURNS trigger AS $$
BEGIN
RAISE EXCEPTION 'tracecoin_ledger is immutable; % is not allowed', TG_OP;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_prevent_tracecoin_ledger_update ON tracecoin_ledger;
CREATE TRIGGER trg_prevent_tracecoin_ledger_update
BEFORE UPDATE ON tracecoin_ledger FOR EACH ROW
EXECUTE FUNCTION prevent_tracecoin_ledger_mutation();
DROP TRIGGER IF EXISTS trg_prevent_tracecoin_ledger_delete ON tracecoin_ledger;
CREATE TRIGGER trg_prevent_tracecoin_ledger_delete
BEFORE DELETE ON tracecoin_ledger FOR EACH ROW
EXECUTE FUNCTION prevent_tracecoin_ledger_mutation();
CREATE TABLE IF NOT EXISTS pricing_packages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
role_key VARCHAR(50) NOT NULL,
package_type VARCHAR(50) NOT NULL,
tracecoins_amount INTEGER NOT NULL DEFAULT 0,
price_inr INTEGER NOT NULL,
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
package_id UUID NOT NULL REFERENCES pricing_packages(id),
razorpay_order_id VARCHAR(100),
razorpay_payment_id VARCHAR(100),
amount_inr INTEGER NOT NULL,
tracecoins_credited INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
verified_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_payments_user_id ON payments(user_id);
CREATE TABLE IF NOT EXISTS invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
payment_id UUID NOT NULL REFERENCES payments(id),
user_id UUID NOT NULL REFERENCES users(id),
invoice_number VARCHAR(50) NOT NULL UNIQUE,
subtotal INTEGER NOT NULL,
gst_amount INTEGER NOT NULL,
total INTEGER NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ISSUED',
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
file_url VARCHAR(500)
);
CREATE INDEX IF NOT EXISTS idx_invoices_user_id ON invoices(user_id);
CREATE TABLE IF NOT EXISTS orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
total_amount INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
package_id UUID REFERENCES pricing_packages(id),
item_type VARCHAR(50) NOT NULL,
quantity INTEGER NOT NULL DEFAULT 1,
unit_price INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS tax_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
tax_type VARCHAR(50) NOT NULL,
percentage DECIMAL(5,2) NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
applicable_to TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS discounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
scope VARCHAR(20) NOT NULL DEFAULT 'ROLE',
role_key VARCHAR(50),
package_id UUID REFERENCES pricing_packages(id) ON DELETE SET NULL,
discount_type VARCHAR(20) NOT NULL,
discount_value INTEGER NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS coupons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) NOT NULL UNIQUE,
title VARCHAR(255),
description TEXT,
discount_type VARCHAR(20) NOT NULL,
discount_value INTEGER NOT NULL,
applies_to VARCHAR(50) NOT NULL DEFAULT 'ALL',
min_order_amount INTEGER NOT NULL DEFAULT 0,
max_uses INTEGER,
uses_count INTEGER NOT NULL DEFAULT 0,
per_user_limit INTEGER NOT NULL DEFAULT 1,
role_keys TEXT[] NOT NULL DEFAULT '{}',
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS coupon_uses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
coupon_id UUID NOT NULL REFERENCES coupons(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
payment_id UUID REFERENCES payments(id),
used_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(coupon_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_coupon_uses_user_id ON coupon_uses(user_id);
-- ============================================================================
-- 12. KNOWLEDGE BASE
-- ============================================================================
CREATE TABLE IF NOT EXISTS kb_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
slug VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
display_order INTEGER NOT NULL DEFAULT 0,
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 kb_sections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category_id UUID NOT NULL REFERENCES kb_categories(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
display_order INTEGER NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS kb_articles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
category_id UUID NOT NULL REFERENCES kb_categories(id) ON DELETE CASCADE,
section_id UUID REFERENCES kb_sections(id),
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) NOT NULL UNIQUE,
summary TEXT,
body TEXT NOT NULL,
content_markdown TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
target_roles TEXT[] DEFAULT '{}',
tags TEXT[] DEFAULT '{}',
views INTEGER NOT NULL DEFAULT 0,
created_by UUID REFERENCES users(id),
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_kb_articles_category_id ON kb_articles(category_id);
CREATE TABLE IF NOT EXISTS kb_article_feedback (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
article_id UUID NOT NULL REFERENCES kb_articles(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
helpful BOOLEAN NOT NULL,
comment TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 13. SUPPORT SYSTEM
-- ============================================================================
CREATE TABLE IF NOT EXISTS support_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
subject VARCHAR(500) NOT NULL,
description TEXT,
category VARCHAR(50) NOT NULL DEFAULT 'GENERAL',
status VARCHAR(20) NOT NULL DEFAULT 'OPEN',
priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL',
assigned_to UUID REFERENCES users(id),
requester_name VARCHAR(255),
requester_email VARCHAR(255),
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_support_tickets_user_id ON support_tickets(user_id);
CREATE INDEX IF NOT EXISTS idx_support_tickets_status ON support_tickets(status);
CREATE TABLE IF NOT EXISTS support_ticket_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id UUID NOT NULL REFERENCES support_tickets(id) ON DELETE CASCADE,
sender_id UUID NOT NULL REFERENCES users(id),
body TEXT NOT NULL,
is_internal BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_support_ticket_messages_ticket_id ON support_ticket_messages(ticket_id);
-- ============================================================================
-- 14. NOTIFICATION SYSTEM
-- ============================================================================
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
body TEXT,
type VARCHAR(50),
reference_id UUID,
is_read BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id);
CREATE TABLE IF NOT EXISTS notification_preferences (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
preferences JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS notification_templates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
trigger_event VARCHAR(100) NOT NULL UNIQUE,
title_template VARCHAR(500),
body_template TEXT,
channel VARCHAR(20) NOT NULL DEFAULT 'IN_APP',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS email_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
trigger VARCHAR(100) NOT NULL,
to_email VARCHAR(255) NOT NULL,
subject VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS smtp_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
host VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 587,
username VARCHAR(255) NOT NULL,
password_encrypted TEXT NOT NULL,
from_email VARCHAR(255) NOT NULL,
from_name VARCHAR(255),
use_tls BOOLEAN NOT NULL DEFAULT true,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 15. DASHBOARD SYSTEM
-- ============================================================================
CREATE TABLE IF NOT EXISTS dashboard_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
audience VARCHAR(50) NOT NULL,
config_json JSONB NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT true,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_dashboard_per_role_audience
ON dashboard_configs(role_id, audience) WHERE is_active = true;
CREATE TABLE IF NOT EXISTS dashboard_widgets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
dashboard_config_id UUID NOT NULL REFERENCES dashboard_configs(id) ON DELETE CASCADE,
widget_key VARCHAR(100) NOT NULL,
widget_type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
config_json JSONB NOT NULL DEFAULT '{}',
display_order INTEGER NOT NULL DEFAULT 0,
is_visible BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS runtime_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
config_json JSONB NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT true,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_runtime_per_role
ON runtime_configs(role_id) WHERE is_active = true;
-- ============================================================================
-- 16. ONBOARDING (deprecated but tables kept for backward compat)
-- ============================================================================
CREATE TABLE IF NOT EXISTS onboarding_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
schema_json JSONB NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
is_active BOOLEAN NOT NULL DEFAULT true,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_onboarding_per_role
ON onboarding_configs(role_id) WHERE is_active = true;
CREATE TABLE IF NOT EXISTS onboarding_states (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'NOT_STARTED',
progress_json JSONB NOT NULL DEFAULT '{}',
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_onboarding_state_user_role
ON onboarding_states(user_id, role_id);
CREATE TABLE IF NOT EXISTS onboarding_submissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id),
config_id UUID REFERENCES onboarding_configs(id),
data_json JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
submitted_at TIMESTAMPTZ,
reviewed_at TIMESTAMPTZ,
reviewed_by UUID REFERENCES users(id),
rejection_reason TEXT,
document_request TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_onboarding_submissions_user_id ON onboarding_submissions(user_id);
CREATE TABLE IF NOT EXISTS submission_documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
submission_id UUID NOT NULL REFERENCES onboarding_submissions(id) ON DELETE CASCADE,
document_type VARCHAR(100) NOT NULL,
file_url VARCHAR(500) NOT NULL,
file_name VARCHAR(255),
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 17. INTERNAL EMPLOYEES
-- ============================================================================
CREATE TABLE IF NOT EXISTS departments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
code VARCHAR(64),
description TEXT,
department_head VARCHAR(255),
department_email VARCHAR(255),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_departments_code_unique
ON departments(LOWER(code)) WHERE code IS NOT NULL;
CREATE TABLE IF NOT EXISTS designations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
code VARCHAR(64),
department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
description TEXT,
level VARCHAR(100),
can_manage_team BOOLEAN NOT NULL DEFAULT false,
can_approve BOOLEAN NOT NULL DEFAULT false,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_designations_code_unique
ON designations(LOWER(code)) WHERE code IS NOT NULL;
CREATE TABLE IF NOT EXISTS employees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
employee_code VARCHAR(50) UNIQUE,
department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
designation_id UUID REFERENCES designations(id) ON DELETE SET NULL,
role_code VARCHAR(50) NOT NULL DEFAULT 'STAFF',
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
joining_date DATE DEFAULT CURRENT_DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_employees_email ON employees(email);
CREATE TABLE IF NOT EXISTS employee_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE,
token_hash VARCHAR(255) UNIQUE NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
revoked BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================================
-- 18. AUDIT MANAGEMENT
-- ============================================================================
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_user_id UUID NOT NULL,
actor_type VARCHAR(20) NOT NULL,
action VARCHAR(100) NOT NULL,
entity_type VARCHAR(50) NOT NULL,
entity_id UUID NOT NULL,
module_key VARCHAR(100),
request_id UUID,
ip_address VARCHAR(45),
user_agent TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'SUCCESS',
summary TEXT,
metadata_json JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_actor ON audit_logs(actor_type, actor_user_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at DESC);
CREATE TABLE IF NOT EXISTS audit_log_changes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
audit_log_id UUID NOT NULL REFERENCES audit_logs(id) ON DELETE CASCADE,
field_name VARCHAR(255) NOT NULL,
old_value TEXT,
new_value TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- activity_logs used by Rust code
CREATE TABLE IF NOT EXISTS activity_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id UUID NOT NULL,
actor_type VARCHAR(20) NOT NULL,
entity_id UUID NOT NULL,
entity_type VARCHAR(50) NOT NULL,
action VARCHAR(100) NOT NULL,
metadata JSONB,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_activity_logs_entity ON activity_logs(entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at DESC);
-- ============================================================================
-- 19. ACCOUNT DELETION
-- ============================================================================
CREATE TABLE IF NOT EXISTS account_deletion_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
reason TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
processed_at TIMESTAMPTZ,
processed_by UUID REFERENCES users(id)
);
COMMIT;