1322 lines
58 KiB
MySQL
1322 lines
58 KiB
MySQL
|
|
-- 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);
|