nxtgauge-backend-rust/scripts/init-db.sql
Ashwin Kumar 23c2edd567 feat: improve startup and routing
- Create scripts/init-db.sql for DB schema initialization
- Enhance start-services.sh to auto-initialize DB if needed
- Fix users admin handler: change root route from '/users' to '/' to avoid double prefix
- Remove deprecated handlers (departments/designations/employees) from users service
- Add missing admin route mappings for users and approval/case endpoints in gateway
- Update gateway to correctly handle /api/admin/users, /api/admin/approvals, etc.
- Update .env.example and docs

These changes enable running the stack without Docker and fix admin panel routing.
2026-04-07 22:12:37 +02:00

1321 lines
58 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 1. ROLES
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, -- INTERNAL or EXTERNAL
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. ONBOARDING CONFIGS
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()
);
-- Only one active onboarding config per role at a time
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_onboarding_per_role
ON onboarding_configs(role_id) WHERE is_active = true;
-- 3. DASHBOARD CONFIGS
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, -- INTERNAL or EXTERNAL
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()
);
-- Only one active dashboard config per role+audience combination
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_dashboard_per_role_audience
ON dashboard_configs(role_id, audience) WHERE is_active = true;
-- 4. RUNTIME CONFIGS
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()
);
-- Only one active runtime config per role at a time
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_runtime_per_role
ON runtime_configs(role_id) WHERE is_active = true;
-- 1. USERS
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE, PENDING, SUSPENDED
role_id UUID REFERENCES roles(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. REFRESH TOKENS
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()
);
-- Index for fast token lookups
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash ON refresh_tokens(token_hash);
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,
-- Photographer Specific Fields
portfolio_url VARCHAR(255),
equipment_list TEXT,
years_of_experience INT,
hourly_rate INTEGER, -- in paise (INR × 100)
specialties TEXT[], -- e.g., ["wedding", "portrait", "commercial"]
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Ensure a user can only have one photographer profile
UNIQUE(user_id)
);
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,
-- Tutor Specific Fields
subjects_taught TEXT[], -- e.g., ["math", "physics", "computer science"]
education_level VARCHAR(255),
certifications TEXT,
years_of_experience INT,
hourly_rate INTEGER, -- in paise (INR × 100)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Ensure a user can only have one tutor profile
UNIQUE(user_id)
);
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,
-- Company Specific Fields
company_name VARCHAR(255) NOT NULL,
registration_number VARCHAR(100),
industry VARCHAR(150),
website_url VARCHAR(255),
employee_count INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Ensure a user can only have one company profile
UNIQUE(user_id)
);
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,
-- Generic Fields for Job Seeker
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Customer
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Makeup Artist
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Developer
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Video Editor
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Graphic Designer
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Social Media Manager
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Fitness Trainer
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
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,
-- Generic Fields for Catering Service
bio TEXT,
experience_years INT,
custom_data JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id)
);
-- Add missing columns to users table
ALTER TABLE users
ADD COLUMN IF NOT EXISTS full_name VARCHAR(255),
ADD COLUMN IF NOT EXISTS phone VARCHAR(20) UNIQUE,
ADD COLUMN IF NOT EXISTS email_verified BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS phone_verified BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
-- user_roles: many-to-many, a user can hold multiple external roles
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', -- PENDING, APPROVED, REJECTED, SUSPENDED
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(user_id, role_id)
);
-- role_permissions
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)
);
-- departments for internal staff
CREATE TABLE IF NOT EXISTS departments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- designations for internal staff
CREATE TABLE IF NOT EXISTS designations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- employees (internal staff records)
CREATE TABLE IF NOT EXISTS employees (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
role_id UUID NOT NULL REFERENCES roles(id),
department_id UUID REFERENCES departments(id),
designation_id UUID REFERENCES designations(id),
employee_code VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- onboarding_submissions: tracks verification submissions
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()
);
-- submission_documents: uploaded files for onboarding
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()
);
CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id);
CREATE INDEX IF NOT EXISTS idx_user_roles_status ON user_roles(status);
CREATE INDEX IF NOT EXISTS idx_onboarding_submissions_user_id ON onboarding_submissions(user_id);
CREATE INDEX IF NOT EXISTS idx_onboarding_submissions_status ON onboarding_submissions(status);
-- Complete company profile (replacing the minimal stub)
ALTER TABLE company_profiles
ADD COLUMN IF NOT EXISTS business_type VARCHAR(100),
ADD COLUMN IF NOT EXISTS gst_number VARCHAR(50),
ADD COLUMN IF NOT EXISTS contact_name VARCHAR(255),
ADD COLUMN IF NOT EXISTS contact_email VARCHAR(255),
ADD COLUMN IF NOT EXISTS contact_phone VARCHAR(20),
ADD COLUMN IF NOT EXISTS address_line1 VARCHAR(500),
ADD COLUMN IF NOT EXISTS city VARCHAR(100),
ADD COLUMN IF NOT EXISTS state VARCHAR(100),
ADD COLUMN IF NOT EXISTS country VARCHAR(100) NOT NULL DEFAULT 'India',
ADD COLUMN IF NOT EXISTS postal_code VARCHAR(20),
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
ADD COLUMN IF NOT EXISTS free_job_slots INTEGER NOT NULL DEFAULT 1,
ADD COLUMN IF NOT EXISTS purchased_job_slots INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS free_contact_views INTEGER NOT NULL DEFAULT 30,
ADD COLUMN IF NOT EXISTS purchased_contact_views INTEGER NOT NULL DEFAULT 0;
-- Jobs
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', -- FULL_TIME, PART_TIME, CONTRACT
salary_min INTEGER, -- in paise
salary_max INTEGER, -- in paise
experience_years INTEGER,
skills TEXT[] DEFAULT '{}',
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
-- DRAFT, PENDING_APPROVAL, LIVE, EXPIRED, CLOSED, REJECTED
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()
);
-- Applications (Job Seeker → Job)
CREATE TABLE IF NOT EXISTS applications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_id UUID NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
job_seeker_id UUID NOT NULL REFERENCES job_seeker_profiles(id) ON DELETE CASCADE,
cover_letter TEXT,
resume_url VARCHAR(500),
status VARCHAR(50) NOT NULL DEFAULT 'APPLIED',
-- APPLIED, SHORTLISTED, INTERVIEW, OFFERED, HIRED, REJECTED, WITHDRAWN
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
contact_viewed BOOLEAN NOT NULL DEFAULT false,
UNIQUE(job_id, job_seeker_id)
);
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 INDEX IF NOT EXISTS idx_applications_job_id ON applications(job_id);
CREATE INDEX IF NOT EXISTS idx_applications_job_seeker_id ON applications(job_seeker_id);
CREATE INDEX IF NOT EXISTS idx_applications_status ON applications(status);
-- Add missing fields to job_seeker_profiles
ALTER TABLE job_seeker_profiles
ADD COLUMN IF NOT EXISTS full_name VARCHAR(255),
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS summary TEXT,
ADD COLUMN IF NOT EXISTS experience_years INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS skills TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS resume_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS active_application_count INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE';
-- Requirements (customer leads)
CREATE TABLE IF NOT EXISTS requirements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL REFERENCES customer_profiles(id) ON DELETE CASCADE,
profession_key VARCHAR(50) NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT NOT NULL,
location VARCHAR(255) NOT NULL,
budget INTEGER, -- in paise
preferred_date DATE,
extra_data_json JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT',
-- DRAFT, PENDING_APPROVAL, OPEN, CLOSED, EXPIRED, REJECTED
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()
);
-- professionals unified table (parent for all 9 profession subtypes)
CREATE TABLE IF NOT EXISTS professionals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE UNIQUE,
profession_key VARCHAR(50) NOT NULL,
display_name VARCHAR(255) NOT NULL,
location VARCHAR(255),
bio TEXT,
extra_data_json JSONB,
status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Lead requests (professional → requirement)
CREATE TABLE IF NOT EXISTS lead_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
requirement_id UUID NOT NULL REFERENCES requirements(id) ON DELETE CASCADE,
professional_id UUID NOT NULL REFERENCES professionals(id) ON DELETE CASCADE,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
-- PENDING, ACCEPTED, REJECTED, EXPIRED, CANCELLED
tracecoins_reserved INTEGER NOT NULL DEFAULT 25,
expires_at TIMESTAMPTZ NOT NULL,
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMPTZ,
UNIQUE(requirement_id, professional_id)
);
-- Add missing fields to customer_profiles
ALTER TABLE customer_profiles
ADD COLUMN IF NOT EXISTS full_name VARCHAR(255),
ADD COLUMN IF NOT EXISTS phone VARCHAR(20),
ADD COLUMN IF NOT EXISTS city VARCHAR(100),
ADD COLUMN IF NOT EXISTS area VARCHAR(100),
ADD COLUMN IF NOT EXISTS preferred_professions TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS active_requirement_count INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE';
CREATE INDEX IF NOT EXISTS idx_requirements_customer_id ON requirements(customer_id);
CREATE INDEX IF NOT EXISTS idx_requirements_status ON requirements(status);
CREATE INDEX IF NOT EXISTS idx_requirements_profession_key ON requirements(profession_key);
CREATE INDEX IF NOT EXISTS idx_lead_requests_requirement_id ON lead_requests(requirement_id);
CREATE INDEX IF NOT EXISTS idx_lead_requests_professional_id ON lead_requests(professional_id);
CREATE INDEX IF NOT EXISTS idx_lead_requests_status ON lead_requests(status);
CREATE INDEX IF NOT EXISTS idx_professionals_profession_key ON professionals(profession_key);
-- Portfolio items (for professionals)
CREATE TABLE IF NOT EXISTS portfolio_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
professional_id UUID NOT NULL REFERENCES professionals(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
tags TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Portfolio images (multiple images per portfolio item)
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
);
-- Services (offered by professionals)
CREATE TABLE IF NOT EXISTS services (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
professional_id UUID NOT NULL REFERENCES professionals(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
price INTEGER NOT NULL DEFAULT 0, -- in paise
duration_minutes INTEGER,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Tracecoin wallets (one per user)
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()
);
-- Tracecoin ledger (IMMUTABLE — never update or delete)
CREATE TABLE IF NOT EXISTS tracecoin_ledger (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
wallet_id UUID NOT NULL REFERENCES tracecoin_wallets(id),
type VARCHAR(20) NOT NULL, -- CREDIT, DEBIT, RESERVE, RELEASE
amount INTEGER NOT NULL,
reason VARCHAR(100) NOT NULL, -- LEAD_REQUEST, LEAD_ACCEPTED, PURCHASE, ADMIN_CREDIT, LEAD_EXPIRED
reference_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Pricing packages (Tracecoin bundles, job slots, contact views)
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, -- JOB_POSTING, CONTACT_VIEWS, TRACECOIN_BUNDLE
tracecoins_amount INTEGER NOT NULL DEFAULT 0,
price_inr INTEGER NOT NULL, -- in paise
description TEXT,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Payments (Razorpay transactions)
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', -- PENDING, SUCCESS, FAILED
verified_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Invoices (generated for every successful payment)
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, -- in paise
gst_amount INTEGER NOT NULL, -- in paise
total INTEGER NOT NULL, -- in paise
status VARCHAR(20) NOT NULL DEFAULT 'ISSUED', -- ISSUED, PAID
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
file_url VARCHAR(500)
);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_professional_id ON portfolio_items(professional_id);
CREATE INDEX IF NOT EXISTS idx_services_professional_id ON services(professional_id);
CREATE INDEX IF NOT EXISTS idx_tracecoin_ledger_wallet_id ON tracecoin_ledger(wallet_id);
CREATE INDEX IF NOT EXISTS idx_payments_user_id ON payments(user_id);
CREATE INDEX IF NOT EXISTS idx_invoices_user_id ON invoices(user_id);
-- Notifications (in-app)
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), -- APPROVAL, LEAD, JOB, PAYMENT
reference_id UUID,
is_read BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Email logs (audit trail)
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, -- PROFILE_APPROVED, JOB_APPROVED, etc.
to_email VARCHAR(255) NOT NULL,
subject VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING, SENT, FAILED
sent_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id);
CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications(is_read);
CREATE INDEX IF NOT EXISTS idx_email_logs_user_id ON email_logs(user_id);
-- Drop the generic professionals table approach; use per-profession profile tables
-- Portfolio and services stay shared (referenced by user_id + profession_key)
-- 1. PHOTOGRAPHER 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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
specialties TEXT[] DEFAULT '{}', -- e.g. ['Wedding', 'Portrait', 'Commercial']
camera_brands TEXT[] DEFAULT '{}', -- e.g. ['Sony', 'Canon']
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, -- in paise
-- Verification & status
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', -- PENDING, APPROVED, REJECTED, SUSPENDED
rejection_reason TEXT,
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. TUTOR PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
subjects TEXT[] DEFAULT '{}', -- e.g. ['Math', 'Physics', 'Hindi']
board_types TEXT[] DEFAULT '{}', -- e.g. ['CBSE', 'ICSE', 'IB']
qualification VARCHAR(255), -- e.g. 'B.Tech IIT Delhi'
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, -- in paise
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()
);
-- 3. MAKEUP ARTIST PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
specializations TEXT[] DEFAULT '{}', -- e.g. ['Bridal', 'Editorial', 'SFX']
kit_brands TEXT[] DEFAULT '{}', -- e.g. ['MAC', 'NARS', 'NYX']
home_service BOOLEAN NOT NULL DEFAULT true,
studio_available BOOLEAN NOT NULL DEFAULT false,
starting_price_inr INTEGER DEFAULT 0,
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. DEVELOPER PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
tech_stack TEXT[] DEFAULT '{}', -- e.g. ['Rust', 'React', 'PostgreSQL']
github_url VARCHAR(500),
portfolio_url VARCHAR(500),
experience_years INTEGER DEFAULT 0,
availability VARCHAR(50) DEFAULT 'FULL_TIME', -- FULL_TIME, PART_TIME, FREELANCE
hourly_rate_inr INTEGER DEFAULT 0,
remote_ok BOOLEAN NOT NULL DEFAULT true,
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()
);
-- 5. VIDEO EDITOR PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
software_skills TEXT[] DEFAULT '{}', -- e.g. ['Premiere Pro', 'DaVinci Resolve']
style_tags TEXT[] DEFAULT '{}', -- e.g. ['Cinematic', 'Corporate', 'Reels']
turnaround_days INTEGER DEFAULT 7,
reel_url VARCHAR(500),
starting_price_inr INTEGER DEFAULT 0,
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()
);
-- 6. GRAPHIC DESIGNER PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
design_tools TEXT[] DEFAULT '{}', -- e.g. ['Figma', 'Illustrator', 'Photoshop']
style_tags TEXT[] DEFAULT '{}', -- e.g. ['Minimalist', 'Bold', 'Corporate']
brand_experience BOOLEAN NOT NULL DEFAULT false,
portfolio_url VARCHAR(500),
starting_price_inr INTEGER DEFAULT 0,
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()
);
-- 7. SOCIAL MEDIA MANAGER PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
platforms TEXT[] DEFAULT '{}', -- e.g. ['Instagram', 'LinkedIn', 'YouTube']
industries TEXT[] DEFAULT '{}', -- e.g. ['F&B', 'Fashion', 'Real Estate']
content_types TEXT[] DEFAULT '{}', -- e.g. ['Reels', 'Carousels', 'Stories']
avg_follower_growth_pct INTEGER DEFAULT 0,
starting_price_inr INTEGER DEFAULT 0,
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()
);
-- 8. FITNESS TRAINER PROFILES
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,
display_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
disciplines TEXT[] DEFAULT '{}', -- e.g. ['Yoga', 'HIIT', 'Zumba', 'CrossFit']
certifications TEXT[] DEFAULT '{}', -- e.g. ['ACE', 'NASM', 'Yoga Alliance RYT']
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,
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()
);
-- 9. CATERING SERVICES PROFILES
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,
business_name VARCHAR(255) NOT NULL,
bio TEXT,
location VARCHAR(255),
-- Profession-specific
cuisine_types TEXT[] DEFAULT '{}', -- e.g. ['North Indian', 'Continental', 'Vegan']
event_types TEXT[] DEFAULT '{}', -- e.g. ['Wedding', 'Corporate', 'Birthday']
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, -- in paise
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()
);
-- Shared: portfolio_items now uses user_id + profession_key (no foreign key to professionals)
-- Drop the professionals-table FK if it was added before
ALTER TABLE portfolio_items
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES users(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS profession_key VARCHAR(50);
ALTER TABLE services
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES users(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS profession_key VARCHAR(50);
-- Lead requests: use user_id instead of professional_id foreign key
ALTER TABLE lead_requests
ADD COLUMN IF NOT EXISTS professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE;
-- Backfill columns when legacy minimal profile tables already exist.
-- This keeps migrations idempotent while upgrading old schemas to the new profile shape.
ALTER TABLE photographer_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS bio TEXT,
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS specialties TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS camera_brands TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS studio_available BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS outdoor_shoots BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS travel_radius_km INTEGER DEFAULT 50,
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE tutor_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS bio TEXT,
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS subjects TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS board_types TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS qualification VARCHAR(255),
ADD COLUMN IF NOT EXISTS teaches_online BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS teaches_offline BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS hourly_rate_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE makeup_artist_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS specializations TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS kit_brands TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS home_service BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS studio_available BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE developer_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS tech_stack TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS github_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS portfolio_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS availability VARCHAR(50) DEFAULT 'FULL_TIME',
ADD COLUMN IF NOT EXISTS hourly_rate_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS remote_ok BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE video_editor_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS software_skills TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS style_tags TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS turnaround_days INTEGER DEFAULT 7,
ADD COLUMN IF NOT EXISTS reel_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE graphic_designer_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS design_tools TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS style_tags TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS brand_experience BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS portfolio_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE social_media_manager_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS platforms TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS industries TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS content_types TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS avg_follower_growth_pct INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE fitness_trainer_profiles
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS disciplines TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS certifications TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS online_sessions BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS home_visits BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS gym_based BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS per_session_rate_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE catering_service_profiles
ADD COLUMN IF NOT EXISTS business_name VARCHAR(255) NOT NULL DEFAULT '',
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
ADD COLUMN IF NOT EXISTS cuisine_types TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS event_types TEXT[] DEFAULT '{}',
ADD COLUMN IF NOT EXISTS min_guests INTEGER DEFAULT 10,
ADD COLUMN IF NOT EXISTS max_guests INTEGER DEFAULT 500,
ADD COLUMN IF NOT EXISTS has_setup_team BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS has_serving_staff BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS price_per_head_inr INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
ALTER TABLE lead_requests
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
-- Indexes
CREATE INDEX IF NOT EXISTS idx_photographer_profiles_status ON photographer_profiles(status);
CREATE INDEX IF NOT EXISTS idx_tutor_profiles_status ON tutor_profiles(status);
CREATE INDEX IF NOT EXISTS idx_makeup_artist_profiles_status ON makeup_artist_profiles(status);
CREATE INDEX IF NOT EXISTS idx_developer_profiles_status ON developer_profiles(status);
CREATE INDEX IF NOT EXISTS idx_video_editor_profiles_status ON video_editor_profiles(status);
CREATE INDEX IF NOT EXISTS idx_graphic_designer_profiles_status ON graphic_designer_profiles(status);
CREATE INDEX IF NOT EXISTS idx_social_media_manager_profiles_status ON social_media_manager_profiles(status);
CREATE INDEX IF NOT EXISTS idx_fitness_trainer_profiles_status ON fitness_trainer_profiles(status);
CREATE INDEX IF NOT EXISTS idx_catering_service_profiles_status ON catering_service_profiles(status);
CREATE INDEX IF NOT EXISTS idx_portfolio_items_user_id ON portfolio_items(user_id);
CREATE INDEX IF NOT EXISTS idx_services_user_id ON services(user_id);
-- Add email verification and password reset columns to users table
ALTER TABLE users
ADD COLUMN IF NOT EXISTS email_verification_token VARCHAR(255),
ADD COLUMN IF NOT EXISTS email_verification_expires_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS reset_password_token VARCHAR(255),
ADD COLUMN IF NOT EXISTS reset_password_expires_at TIMESTAMPTZ;
-- Add index for token lookups
CREATE INDEX IF NOT EXISTS idx_users_email_verification_token ON users(email_verification_token);
CREATE INDEX IF NOT EXISTS idx_users_reset_password_token ON users(reset_password_token);
-- Reviews: customers leave reviews on professionals after an accepted lead
CREATE TABLE IF NOT EXISTS reviews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lead_request_id UUID NOT NULL REFERENCES lead_requests(id) ON DELETE CASCADE UNIQUE,
customer_id UUID NOT NULL REFERENCES customer_profiles(id) ON DELETE CASCADE,
professional_id UUID NOT NULL REFERENCES professionals(id) ON DELETE CASCADE,
rating SMALLINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
comment TEXT,
is_published BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_reviews_professional_id ON reviews(professional_id);
CREATE INDEX IF NOT EXISTS idx_reviews_customer_id ON reviews(customer_id);
-- Knowledge Base categories
CREATE TABLE IF NOT EXISTS kb_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
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()
);
-- Knowledge Base articles
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,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) NOT NULL UNIQUE,
body TEXT NOT NULL,
target_roles TEXT[] DEFAULT '{}', -- empty = visible to all
is_published BOOLEAN NOT NULL DEFAULT false,
views INTEGER NOT NULL DEFAULT 0,
created_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_kb_articles_category_id ON kb_articles(category_id);
CREATE INDEX IF NOT EXISTS idx_kb_articles_slug ON kb_articles(slug);
-- Support tickets
CREATE TABLE IF NOT EXISTS support_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
subject VARCHAR(500) NOT NULL,
category VARCHAR(50) NOT NULL DEFAULT 'GENERAL', -- GENERAL, BILLING, ACCOUNT, LEAD, JOB
status VARCHAR(20) NOT NULL DEFAULT 'OPEN', -- OPEN, IN_PROGRESS, RESOLVED, CLOSED
priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL', -- LOW, NORMAL, HIGH, URGENT
assigned_to UUID REFERENCES users(id),
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Support ticket messages
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, -- true = staff-only note
created_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 INDEX IF NOT EXISTS idx_support_ticket_messages_ticket_id ON support_ticket_messages(ticket_id);
-- Discount coupons for Tracecoin and package purchases
CREATE TABLE IF NOT EXISTS coupons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
discount_type VARCHAR(20) NOT NULL, -- PERCENT, FLAT
discount_value INTEGER NOT NULL, -- percent (0-100) or paise
applies_to VARCHAR(50) NOT NULL DEFAULT 'ALL', -- ALL, TRACECOIN_BUNDLE, JOB_POSTING, CONTACT_VIEWS
min_order_amount INTEGER NOT NULL DEFAULT 0, -- paise
max_uses INTEGER, -- NULL = unlimited
uses_count INTEGER NOT NULL DEFAULT 0,
per_user_limit INTEGER NOT NULL DEFAULT 1,
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Track which users used which coupons
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_coupons_code ON coupons(code);
CREATE INDEX IF NOT EXISTS idx_coupon_uses_user_id ON coupon_uses(user_id);
-- Onboarding state per user per role
-- Tracks progress through the schema-driven onboarding form
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', -- NOT_STARTED | IN_PROGRESS | COMPLETED
progress_json JSONB NOT NULL DEFAULT '{}',
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- One onboarding state record per user per role
CREATE UNIQUE INDEX IF NOT EXISTS idx_onboarding_state_user_role
ON onboarding_states(user_id, role_id);
-- Make display_name / business_name nullable so upserts can work
-- without forcing the name on every call.
-- Add custom_data JSONB to every profession table so all onboarding
-- form fields are preserved even if they don't have a dedicated column.
ALTER TABLE photographer_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE tutor_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE makeup_artist_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE developer_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE video_editor_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE graphic_designer_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE social_media_manager_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE fitness_trainer_profiles ALTER COLUMN display_name DROP NOT NULL;
ALTER TABLE catering_service_profiles ALTER COLUMN business_name DROP NOT NULL;
ALTER TABLE photographer_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE tutor_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE makeup_artist_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE developer_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE video_editor_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE graphic_designer_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE social_media_manager_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE fitness_trainer_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
ALTER TABLE catering_service_profiles ADD COLUMN IF NOT EXISTS custom_data JSONB;
-- Enforce immutable tracecoin ledger: no UPDATE/DELETE allowed.
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();
UPDATE company_profiles
SET status = 'APPROVED'
WHERE status = 'ACTIVE';
UPDATE customer_profiles
SET status = 'APPROVED'
WHERE status = 'ACTIVE';
-- Extend roles table for internal role management
ALTER TABLE roles
ADD COLUMN IF NOT EXISTS description TEXT,
ADD COLUMN IF NOT EXISTS department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS can_approve_requests BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS can_manage_system_settings BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE departments
ADD COLUMN IF NOT EXISTS code VARCHAR(64),
ADD COLUMN IF NOT EXISTS description TEXT,
ADD COLUMN IF NOT EXISTS department_head VARCHAR(255),
ADD COLUMN IF NOT EXISTS department_email VARCHAR(255),
ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS visibility VARCHAR(20) NOT NULL DEFAULT 'INTERNAL',
ADD COLUMN IF NOT EXISTS transfers_enabled BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
UPDATE departments
SET updated_at = COALESCE(updated_at, created_at, NOW());
CREATE UNIQUE INDEX IF NOT EXISTS idx_departments_code_unique
ON departments (LOWER(code))
WHERE code IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_departments_is_active
ON departments (is_active);
ALTER TABLE designations
ADD COLUMN IF NOT EXISTS code VARCHAR(64),
ADD COLUMN IF NOT EXISTS department_id UUID REFERENCES departments(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS description TEXT,
ADD COLUMN IF NOT EXISTS level VARCHAR(100),
ADD COLUMN IF NOT EXISTS can_manage_team BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS can_approve BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
UPDATE designations
SET updated_at = COALESCE(updated_at, created_at, NOW());
CREATE UNIQUE INDEX IF NOT EXISTS idx_designations_code_unique
ON designations (LOWER(code))
WHERE code IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_designations_is_active
ON designations (is_active);
CREATE INDEX IF NOT EXISTS idx_designations_department_id
ON designations (department_id);
-- UP: 20260402030000_strict_employee_separation.up.sql
-- Drop old employees table (was linked to users — replacing with standalone auth)
DROP TABLE IF EXISTS employees CASCADE;
-- 1. EMPLOYEES (Standalone Table - Not Linked to 'users')
CREATE TABLE 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',
joined_at DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. EMPLOYEE SESSIONS (Standalone Auth)
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()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_employees_email ON employees(email);
CREATE INDEX IF NOT EXISTS idx_employees_status ON employees(status);
CREATE INDEX IF NOT EXISTS idx_employee_sessions_token ON employee_sessions(token_hash);
-- Up migration: Create activity_logs table
CREATE TABLE IF NOT EXISTS activity_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id UUID NOT NULL, -- User or Employee who performed the action
actor_type VARCHAR(20) NOT NULL, -- 'USER' or 'EMPLOYEE'
entity_id UUID NOT NULL, -- Target of the action (User ID, Job ID, etc.)
entity_type VARCHAR(50) NOT NULL, -- 'USER', 'JOB', 'REQUIREMENT', 'EMPLOYEE', etc.
action VARCHAR(100) NOT NULL, -- 'APPROVE', 'REJECT', 'STATUS_CHANGE', 'DELETE', etc.
metadata JSONB, -- Optional extra context: { "old_status": "PENDING", "new_status": "APPROVED", "reason": "..." }
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_actor ON activity_logs (actor_type, actor_id);
CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs (created_at DESC);
ALTER TABLE kb_articles
ADD COLUMN IF NOT EXISTS summary TEXT,
ADD COLUMN IF NOT EXISTS tags TEXT[] NOT NULL DEFAULT '{}';
-- Allow admin-created tickets with no linked user
ALTER TABLE support_tickets
ALTER COLUMN user_id DROP NOT NULL;
-- Add description body and requester info for admin-created cases
ALTER TABLE support_tickets
ADD COLUMN IF NOT EXISTS description TEXT,
ADD COLUMN IF NOT EXISTS requester_name VARCHAR(255),
ADD COLUMN IF NOT EXISTS requester_email VARCHAR(255);
-- Extend reviews table to support admin-created reviews and admin moderation
ALTER TABLE reviews
ALTER COLUMN lead_request_id DROP NOT NULL,
ALTER COLUMN customer_id DROP NOT NULL,
ALTER COLUMN professional_id DROP NOT NULL,
ADD COLUMN IF NOT EXISTS title VARCHAR(255),
ADD COLUMN IF NOT EXISTS subject_type VARCHAR(50) NOT NULL DEFAULT 'PLATFORM',
ADD COLUMN IF NOT EXISTS subject_id VARCHAR(255),
ADD COLUMN IF NOT EXISTS reviewer_name VARCHAR(255),
ADD COLUMN IF NOT EXISTS status VARCHAR(20) NOT NULL DEFAULT 'PUBLISHED';
-- Sync status with is_published for existing rows
UPDATE reviews SET status = CASE WHEN is_published THEN 'PUBLISHED' ELSE 'HIDDEN' END;
-- Add title and role_keys to coupons for admin UI
ALTER TABLE coupons
ADD COLUMN IF NOT EXISTS title VARCHAR(255),
ADD COLUMN IF NOT EXISTS role_keys TEXT[] NOT NULL DEFAULT '{}';
-- Backfill title from description
UPDATE coupons SET title = description WHERE title IS NULL AND description IS NOT NULL;
-- Admin-managed automatic discounts (applied before coupon codes)
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, PACKAGE
role_key VARCHAR(50),
package_id UUID REFERENCES pricing_packages(id) ON DELETE SET NULL,
discount_type VARCHAR(20) NOT NULL, -- PERCENT, FIXED
discount_value INTEGER NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 10. UGC CONTENT CREATOR PROFILES
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,
display_name VARCHAR(255) NOT NULL DEFAULT '',
bio TEXT,
location VARCHAR(255),
-- Profession-specific
platforms TEXT[] DEFAULT '{}', -- e.g. ['Instagram', 'YouTube', 'TikTok']
content_niches TEXT[] DEFAULT '{}', -- e.g. ['Beauty', 'Tech', 'Food', 'Lifestyle']
content_formats TEXT[] DEFAULT '{}', -- e.g. ['Reels', 'Unboxing', 'Reviews', 'GRWM']
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),
portfolio_url VARCHAR(500),
starting_price_inr INTEGER DEFAULT 0, -- in paise
custom_data JSONB,
-- Verification & status
status VARCHAR(50) NOT NULL DEFAULT 'PENDING', -- PENDING, APPROVED, REJECTED, SUSPENDED
rejection_reason TEXT,
approved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_ugc_content_creator_profiles_status ON ugc_content_creator_profiles(status);
CREATE INDEX IF NOT EXISTS idx_ugc_content_creator_profiles_user_id ON ugc_content_creator_profiles(user_id);
-- 1. VERIFICATIONS TABLE
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', -- PENDING, UNDER_REVIEW, DOCUMENTS_REQUESTED, REVISION_REQUESTED, APPROVED, REJECTED
priority VARCHAR(10) NOT NULL DEFAULT 'LOW', -- HIGH, MEDIUM, LOW
case_type VARCHAR(50) NOT NULL, -- PROFILE, PORTFOLIO, JOB, REQUIREMENT
payload JSONB NOT NULL DEFAULT '{}', -- full submission data
documents JSONB NOT NULL DEFAULT '[]', -- list of documents [{id, title, url, status}]
notes TEXT,
rejection_reason TEXT,
assigned_to UUID REFERENCES users(id) ON DELETE SET NULL, -- Admin/Employee ID
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. VERIFICATION LOGS (History of actions)
CREATE TABLE IF NOT EXISTS verification_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
verification_id UUID NOT NULL REFERENCES verifications(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL, -- STATUS_CHANGE, NOTE_ADDED, DOCS_REQUESTED, REASSIGNED
actor_id UUID REFERENCES users(id) ON DELETE SET NULL,
old_status VARCHAR(50),
new_status VARCHAR(50),
message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 3. INDEXES
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);
CREATE INDEX IF NOT EXISTS idx_verification_logs_ver_id ON verification_logs(verification_id);