diff --git a/crates/db/migrations/20260415000000_complete_migration.down.sql b/crates/db/migrations/20260415000000_complete_migration.down.sql new file mode 100644 index 0000000..c8d5dda --- /dev/null +++ b/crates/db/migrations/20260415000000_complete_migration.down.sql @@ -0,0 +1,18 @@ +-- ============================================================================ +-- DOWN MIGRATION: This migration is NOT REVERSIBLE +-- +-- This migration performs a COMPLETE schema transformation and CANNOT be +-- rolled back. All old tables have been dropped, columns removed, and data +-- restructured. +-- +-- DO NOT ATTEMPT TO RUN THIS FILE +-- +-- To restore from backup: +-- 1. Restore PostgreSQL database from backup +-- 2. Re-run the original init-db.sql and all previous migrations +-- ============================================================================ + +-- THIS FILE INTENTIONALLY LEFT EMPTY +-- This migration is NOT REVERSIBLE + +SELECT 'MIGRATION CANNOT BE REVERSED - Restore from backup' AS warning; diff --git a/crates/db/migrations/20260415000000_complete_migration.up.sql b/crates/db/migrations/20260415000000_complete_migration.up.sql new file mode 100644 index 0000000..73d9bdc --- /dev/null +++ b/crates/db/migrations/20260415000000_complete_migration.up.sql @@ -0,0 +1,778 @@ +-- ============================================================================ +-- Nxtgauge Database Complete Migration +-- Version: 20260415000000 +-- This migration performs a COMPLETE schema transformation +-- NO FALLBACKS - This is a one-way migration +-- ============================================================================ + +BEGIN; + +-- ============================================================================ +-- PHASE 1: Create New Core Tables +-- ============================================================================ + +-- 1.1 user_sessions (new) +CREATE TABLE IF NOT EXISTS user_sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + session_token TEXT UNIQUE NOT NULL, + ip_address TEXT, + user_agent TEXT, + expires_at TIMESTAMPTZ NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id); +CREATE INDEX IF NOT EXISTS idx_user_sessions_token ON user_sessions(session_token); +CREATE INDEX IF NOT EXISTS idx_user_sessions_expires ON user_sessions(expires_at); + +-- 1.2 user_role_profiles (NEW ROOT - CRITICAL) +CREATE TABLE IF NOT EXISTS user_role_profiles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role_key VARCHAR(50) NOT NULL, + display_name TEXT, + bio TEXT, + location TEXT, + avatar_url TEXT, + phone TEXT, + email TEXT, + status VARCHAR(50) NOT NULL DEFAULT 'ACTIVE', + verification_status VARCHAR(50) DEFAULT 'PENDING', + approval_status VARCHAR(50) DEFAULT 'PENDING', + rejection_reason TEXT, + approved_at TIMESTAMPTZ, + verified_at TIMESTAMPTZ, + is_profile_public BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(user_id, role_key) +); +CREATE INDEX IF NOT EXISTS idx_user_role_profiles_user_id ON user_role_profiles(user_id); +CREATE INDEX IF NOT EXISTS idx_user_role_profiles_role_key ON user_role_profiles(role_key); +CREATE INDEX IF NOT EXISTS idx_user_role_profiles_status ON user_role_profiles(status); + +-- ============================================================================ +-- PHASE 2: Backfill user_role_profiles from ALL existing profile tables +-- ============================================================================ + +-- Backfill from photographer_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'photographer', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM photographer_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'photographer'); + +-- Backfill from tutor_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'tutor', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM tutor_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'tutor'); + +-- Backfill from makeup_artist_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'makeup_artist', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM makeup_artist_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'makeup_artist'); + +-- Backfill from developer_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'developer', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM developer_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'developer'); + +-- Backfill from video_editor_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'video_editor', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM video_editor_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'video_editor'); + +-- Backfill from graphic_designer_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'graphic_designer', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM graphic_designer_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'graphic_designer'); + +-- Backfill from social_media_manager_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'social_media_manager', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM social_media_manager_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'social_media_manager'); + +-- Backfill from fitness_trainer_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'fitness_trainer', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM fitness_trainer_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'fitness_trainer'); + +-- Backfill from catering_service_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'catering_service', p.business_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM catering_service_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'catering_service'); + +-- Backfill from ugc_content_creator_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, approval_status, rejection_reason, approved_at, created_at, updated_at) +SELECT gen_random_uuid(), p.user_id, 'ugc_content_creator', p.display_name, p.bio, p.location, + COALESCE(p.status, 'ACTIVE'), COALESCE(p.status, 'PENDING'), p.rejection_reason, p.approved_at, p.created_at, COALESCE(p.updated_at, NOW()) +FROM ugc_content_creator_profiles p +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = p.user_id AND urp.role_key = 'ugc_content_creator'); + +-- Backfill from company_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, created_at, updated_at) +SELECT gen_random_uuid(), cp.user_id, 'company', cp.company_name, cp.bio, NULL, + COALESCE(cp.status, 'ACTIVE'), cp.created_at, COALESCE(cp.updated_at, NOW()) +FROM company_profiles cp +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = cp.user_id AND urp.role_key = 'company'); + +-- Backfill from customer_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, location, status, created_at, updated_at) +SELECT gen_random_uuid(), cp.user_id, 'customer', COALESCE(cp.full_name, ''), cp.city, + COALESCE(cp.status, 'ACTIVE'), cp.created_at, COALESCE(cp.updated_at, NOW()) +FROM customer_profiles cp +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = cp.user_id AND urp.role_key = 'customer'); + +-- Backfill from job_seeker_profiles +INSERT INTO user_role_profiles (id, user_id, role_key, display_name, bio, location, status, created_at, updated_at) +SELECT gen_random_uuid(), jsp.user_id, 'candidate', COALESCE(jsp.full_name, ''), jsp.bio, jsp.location, + COALESCE(jsp.status, 'ACTIVE'), jsp.created_at, COALESCE(jsp.updated_at, NOW()) +FROM job_seeker_profiles jsp +WHERE NOT EXISTS (SELECT 1 FROM user_role_profiles urp WHERE urp.user_id = jsp.user_id AND urp.role_key = 'candidate'); + +-- ============================================================================ +-- PHASE 3: Update Extension Tables to Use user_role_profile_id +-- ============================================================================ + +-- Add user_role_profile_id column to ALL extension tables +ALTER TABLE photographer_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE tutor_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE makeup_artist_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE developer_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE video_editor_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE graphic_designer_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE social_media_manager_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE fitness_trainer_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE catering_service_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE ugc_content_creator_profiles ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; + +-- Backfill user_role_profile_id for photographer_profiles +UPDATE photographer_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'photographer' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for tutor_profiles +UPDATE tutor_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'tutor' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for makeup_artist_profiles +UPDATE makeup_artist_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'makeup_artist' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for developer_profiles +UPDATE developer_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'developer' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for video_editor_profiles +UPDATE video_editor_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'video_editor' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for graphic_designer_profiles +UPDATE graphic_designer_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'graphic_designer' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for social_media_manager_profiles +UPDATE social_media_manager_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'social_media_manager' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for fitness_trainer_profiles +UPDATE fitness_trainer_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'fitness_trainer' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for catering_service_profiles +UPDATE catering_service_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'catering_service' AND p.user_role_profile_id IS NULL; + +-- Backfill user_role_profile_id for ugc_content_creator_profiles +UPDATE ugc_content_creator_profiles p SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE p.user_id = urp.user_id AND urp.role_key = 'ugc_content_creator' AND p.user_role_profile_id IS NULL; + +-- ============================================================================ +-- PHASE 4: Remove Forbidden External Portfolio Links +-- ============================================================================ + +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS github_url; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS portfolio_url; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS reel_url; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS portfolio_url; +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS portfolio_url; + +-- ============================================================================ +-- PHASE 5: Update Portfolio Tables +-- ============================================================================ + +-- Add user_role_profile_id to portfolio_items +ALTER TABLE portfolio_items ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +ALTER TABLE portfolio_items ADD COLUMN IF NOT EXISTS display_order INTEGER DEFAULT 0; + +-- Backfill portfolio_items from professionals +UPDATE portfolio_items pi SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE pi.professional_id IS NOT NULL + AND urp.user_id = (SELECT user_id FROM professionals p WHERE p.id = pi.professional_id); + +-- Update remaining using user_id +UPDATE portfolio_items pi SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE pi.user_id IS NOT NULL AND pi.user_role_profile_id IS NULL + AND EXISTS (SELECT 1 FROM user_role_profiles urp2 WHERE urp2.user_id = pi.user_id AND urp2.role_key = pi.profession_key); + +-- Add user_role_profile_id to services +ALTER TABLE services ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; + +-- Backfill services +UPDATE services s SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE s.professional_id IS NOT NULL + AND urp.user_id = (SELECT user_id FROM professionals p WHERE p.id = s.professional_id); + +UPDATE services s SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE s.user_id IS NOT NULL AND s.user_role_profile_id IS NULL + AND EXISTS (SELECT 1 FROM user_role_profiles urp2 WHERE urp2.user_id = s.user_id AND urp2.role_key = s.profession_key); + +-- ============================================================================ +-- PHASE 6: Rename Tables +-- ============================================================================ + +-- Rename applications -> job_applications +ALTER TABLE applications RENAME TO job_applications; + +-- Rename requirements -> leads +ALTER TABLE requirements RENAME TO leads; + +-- Rename coupon_uses -> coupon_redemptions +ALTER TABLE coupon_uses RENAME TO coupon_redemptions; + +-- ============================================================================ +-- PHASE 7: Create New Domain Tables +-- ============================================================================ + +-- 7.1 verification_requests +CREATE TABLE IF NOT EXISTS verification_requests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_role_profile_id UUID REFERENCES user_role_profiles(id), + verification_type VARCHAR(50) NOT NULL DEFAULT 'IDENTITY', + status VARCHAR(50) NOT NULL DEFAULT 'PENDING', + submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + reviewed_at TIMESTAMPTZ, + reviewed_by_user_id UUID REFERENCES users(id), + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.2 verification_documents +CREATE TABLE IF NOT EXISTS verification_documents ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + verification_request_id UUID NOT NULL REFERENCES verification_requests(id) ON DELETE CASCADE, + document_type VARCHAR(100) NOT NULL, + file_url TEXT NOT NULL, + file_name TEXT, + mime_type TEXT, + status VARCHAR(50) DEFAULT 'PENDING', + uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + reviewed_at TIMESTAMPTZ, + reviewed_by_user_id UUID REFERENCES users(id), + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.3 verification_logs +CREATE TABLE IF NOT EXISTS verification_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + verification_request_id UUID NOT NULL REFERENCES verification_requests(id), + action VARCHAR(50) NOT NULL, + old_status VARCHAR(50), + new_status VARCHAR(50), + acted_by_user_id UUID REFERENCES users(id), + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.4 approval_requests +CREATE TABLE IF NOT EXISTS approval_requests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + entity_type VARCHAR(50) NOT NULL, + entity_id UUID NOT NULL, + approval_type VARCHAR(50), + status VARCHAR(50) NOT NULL DEFAULT 'PENDING', + submitted_by_user_id UUID REFERENCES users(id), + reviewed_by_user_id UUID REFERENCES users(id), + submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + reviewed_at TIMESTAMPTZ, + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.5 approval_logs +CREATE TABLE IF NOT EXISTS approval_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + approval_request_id UUID NOT NULL REFERENCES approval_requests(id), + action VARCHAR(50) NOT NULL, + old_status VARCHAR(50), + new_status VARCHAR(50), + acted_by_user_id UUID REFERENCES users(id), + remarks TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.6 audit_logs +CREATE TABLE IF NOT EXISTS audit_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + actor_user_id UUID REFERENCES users(id), + actor_employee_id UUID, + actor_type VARCHAR(50), + action VARCHAR(100) NOT NULL, + entity_type VARCHAR(100), + entity_id UUID, + entity_label TEXT, + module_key VARCHAR(100), + source_type VARCHAR(50), + source_id UUID, + request_id UUID, + correlation_id UUID, + ip_address TEXT, + user_agent TEXT, + status VARCHAR(50) NOT NULL DEFAULT 'SUCCESS', + summary TEXT, + metadata_json JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.7 audit_log_changes +CREATE TABLE IF NOT EXISTS audit_log_changes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + audit_log_id UUID NOT NULL REFERENCES audit_logs(id) ON DELETE CASCADE, + field_name TEXT NOT NULL, + old_value_text TEXT, + new_value_text TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_audit_logs_actor ON audit_logs(actor_user_id); +CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id); +CREATE INDEX IF NOT EXISTS idx_audit_logs_module ON audit_logs(module_key); +CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at); + +-- 7.8 orders +CREATE TABLE IF NOT EXISTS orders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id), + order_type VARCHAR(50) NOT NULL DEFAULT 'PACKAGE', + subtotal_inr INTEGER NOT NULL DEFAULT 0, + discount_inr INTEGER NOT NULL DEFAULT 0, + tax_inr INTEGER NOT NULL DEFAULT 0, + total_inr INTEGER NOT NULL DEFAULT 0, + status VARCHAR(50) NOT NULL DEFAULT 'PENDING', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.9 order_items +CREATE TABLE IF NOT EXISTS order_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE, + item_type VARCHAR(50) NOT NULL, + item_id UUID, + item_name TEXT NOT NULL, + quantity INTEGER NOT NULL DEFAULT 1, + unit_price_inr INTEGER NOT NULL, + total_price_inr INTEGER NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.10 payment_gateway_configs +CREATE TABLE IF NOT EXISTS payment_gateway_configs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + gateway_key VARCHAR(50) NOT NULL, + display_name VARCHAR(255), + config_json JSONB, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.11 payment_transactions +CREATE TABLE IF NOT EXISTS payment_transactions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + payment_id UUID NOT NULL REFERENCES payments(id), + transaction_type VARCHAR(50) NOT NULL, + provider_reference TEXT, + request_payload_json JSONB, + response_payload_json JSONB, + status VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.12 tax_rules +CREATE TABLE IF NOT EXISTS tax_rules ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + tax_type VARCHAR(50) NOT NULL, + tax_rate DECIMAL(5,2) NOT NULL, + applies_to VARCHAR(50), + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.13 kb_sections +CREATE TABLE IF NOT EXISTS kb_sections ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + category_id UUID NOT NULL REFERENCES kb_categories(id) ON DELETE CASCADE, + name VARCHAR(255) NOT NULL, + slug VARCHAR(255) NOT NULL, + description TEXT, + display_order INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.14 kb_article_feedback +CREATE TABLE IF NOT EXISTS kb_article_feedback ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + article_id UUID NOT NULL REFERENCES kb_articles(id) ON DELETE CASCADE, + user_id UUID REFERENCES users(id), + is_helpful BOOLEAN, + feedback_text TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.15 notification_templates +CREATE TABLE IF NOT EXISTS notification_templates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + template_key VARCHAR(100) NOT NULL UNIQUE, + channel VARCHAR(50) NOT NULL DEFAULT 'EMAIL', + title_template TEXT, + body_template TEXT, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.16 smtp_configs +CREATE TABLE IF NOT EXISTS smtp_configs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + provider_name VARCHAR(100), + host VARCHAR(255), + port INTEGER, + username TEXT, + encryption_mode VARCHAR(20), + from_name VARCHAR(255), + from_email VARCHAR(255), + is_default BOOLEAN DEFAULT false, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 7.17 dashboard_widgets +CREATE TABLE IF NOT EXISTS dashboard_widgets ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + dashboard_config_id UUID NOT NULL REFERENCES dashboard_configs(id) ON DELETE CASCADE, + widget_key VARCHAR(100) NOT NULL, + widget_title VARCHAR(255), + config_json JSONB, + display_order INTEGER DEFAULT 0, + width_units INTEGER DEFAULT 1, + height_units INTEGER DEFAULT 1, + is_visible BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ============================================================================ +-- PHASE 8: Update Existing Tables +-- ============================================================================ + +-- Update users table +ALTER TABLE users ADD COLUMN IF NOT EXISTS account_type TEXT DEFAULT 'INDIVIDUAL'; +ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ; +ALTER TABLE users ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); +UPDATE users SET updated_at = COALESCE(updated_at, created_at, NOW()) WHERE updated_at IS NULL; + +-- Update roles table +ALTER TABLE roles ADD COLUMN IF NOT EXISTS description TEXT; +ALTER TABLE roles ADD COLUMN IF NOT EXISTS can_approve_requests BOOLEAN DEFAULT false; +ALTER TABLE roles ADD COLUMN IF NOT EXISTS can_manage_system_settings BOOLEAN DEFAULT false; + +-- Update departments table +ALTER TABLE departments ADD COLUMN IF NOT EXISTS code VARCHAR(64); +ALTER TABLE departments ADD COLUMN IF NOT EXISTS description TEXT; +ALTER TABLE departments ADD COLUMN IF NOT EXISTS department_head VARCHAR(255); +ALTER TABLE departments ADD COLUMN IF NOT EXISTS department_email VARCHAR(255); +ALTER TABLE departments ADD COLUMN IF NOT EXISTS visibility VARCHAR(20) DEFAULT 'INTERNAL'; +ALTER TABLE departments ADD COLUMN IF NOT EXISTS transfers_enabled BOOLEAN DEFAULT false; +ALTER TABLE departments ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); +UPDATE departments SET updated_at = COALESCE(updated_at, created_at, NOW()) WHERE updated_at IS NULL; + +-- Update designations table +ALTER TABLE designations ADD COLUMN IF NOT EXISTS code VARCHAR(64); +ALTER TABLE designations ADD COLUMN IF NOT EXISTS department_id UUID REFERENCES departments(id); +ALTER TABLE designations ADD COLUMN IF NOT EXISTS description TEXT; +ALTER TABLE designations ADD COLUMN IF NOT EXISTS level VARCHAR(100); +ALTER TABLE designations ADD COLUMN IF NOT EXISTS can_manage_team BOOLEAN DEFAULT false; +ALTER TABLE designations ADD COLUMN IF NOT EXISTS can_approve BOOLEAN DEFAULT false; +ALTER TABLE designations ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true; +ALTER TABLE designations ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); +UPDATE designations SET updated_at = COALESCE(updated_at, created_at, NOW()) WHERE updated_at IS NULL; + +-- Update employees table +ALTER TABLE employees ADD COLUMN IF NOT EXISTS joining_date DATE; +ALTER TABLE employees ADD COLUMN IF NOT EXISTS employment_status VARCHAR(50) DEFAULT 'ACTIVE'; +ALTER TABLE employees ADD COLUMN IF NOT EXISTS manager_employee_id UUID REFERENCES employees(id); +ALTER TABLE employees ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); +UPDATE employees SET updated_at = COALESCE(updated_at, created_at, NOW()) WHERE updated_at IS NULL; + +-- Update lead_requests table +ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS user_role_profile_id UUID; +UPDATE lead_requests lr SET user_role_profile_id = urp.id +FROM user_role_profiles urp +WHERE lr.professional_id IS NOT NULL + AND urp.user_id = (SELECT user_id FROM professionals p WHERE p.id = lr.professional_id); +ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS remarks TEXT; +ALTER TABLE lead_requests ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); + +-- Update job_applications table +ALTER TABLE job_applications ADD COLUMN IF NOT EXISTS applicant_user_id UUID; +UPDATE job_applications ja SET applicant_user_id = ( + SELECT user_id FROM job_seeker_profiles jsp WHERE jsp.id = ja.job_seeker_id +); +ALTER TABLE job_applications ADD COLUMN IF NOT EXISTS cover_note TEXT; +ALTER TABLE job_applications DROP COLUMN IF EXISTS job_seeker_id; +ALTER TABLE job_applications DROP COLUMN IF EXISTS cover_letter; +ALTER TABLE job_applications DROP COLUMN IF EXISTS resume_url; +ALTER TABLE job_applications DROP COLUMN IF EXISTS contact_viewed; + +-- Update leads table (formerly requirements) +ALTER TABLE leads ADD COLUMN IF NOT EXISTS created_by_user_id UUID; +UPDATE leads l SET created_by_user_id = ( + SELECT user_id FROM customer_profiles cp WHERE cp.id = l.customer_id +); +ALTER TABLE leads ADD COLUMN IF NOT EXISTS required_date DATE; +ALTER TABLE leads ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); +ALTER TABLE leads DROP COLUMN IF EXISTS customer_id; + +-- Update jobs table +ALTER TABLE jobs ADD COLUMN IF NOT EXISTS posted_by_user_id UUID; +ALTER TABLE jobs ADD COLUMN IF NOT EXISTS mode_of_work VARCHAR(50); +ALTER TABLE jobs ADD COLUMN IF NOT EXISTS budget_inr INTEGER; +ALTER TABLE jobs ADD COLUMN IF NOT EXISTS salary_range_json JSONB; +ALTER TABLE jobs ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); +UPDATE jobs SET updated_at = COALESCE(updated_at, created_at, NOW()) WHERE updated_at IS NULL; + +-- Update tracecoin_wallets +ALTER TABLE tracecoin_wallets RENAME COLUMN balance TO current_balance; +ALTER TABLE tracecoin_wallets ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(); + +-- Update tracecoin_ledger +ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS balance_after INTEGER; +ALTER TABLE tracecoin_ledger ADD COLUMN IF NOT EXISTS remarks TEXT; +ALTER TABLE tracecoin_ledger RENAME COLUMN type TO transaction_type; +ALTER TABLE tracecoin_ledger RENAME COLUMN reason TO reference_type; + +-- Update coupons +ALTER TABLE coupons ADD COLUMN IF NOT EXISTS max_discount_inr INTEGER; +ALTER TABLE coupons ADD COLUMN IF NOT EXISTS min_order_value_inr INTEGER DEFAULT 0; +ALTER TABLE coupons ADD COLUMN IF NOT EXISTS valid_from TIMESTAMPTZ DEFAULT NOW(); +ALTER TABLE coupons ADD COLUMN IF NOT EXISTS valid_to TIMESTAMPTZ; + +-- Update coupon_redemptions +ALTER TABLE coupon_redemptions ADD COLUMN IF NOT EXISTS order_id UUID REFERENCES orders(id); +ALTER TABLE coupon_redemptions ADD COLUMN IF NOT EXISTS discount_amount_inr INTEGER; +ALTER TABLE coupon_redemptions RENAME COLUMN used_at TO redeemed_at; + +-- Update invoices +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS order_id UUID REFERENCES orders(id); +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS discount_inr INTEGER DEFAULT 0; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS due_at TIMESTAMPTZ; +ALTER TABLE invoices ADD COLUMN IF NOT EXISTS paid_at TIMESTAMPTZ; + +-- Update payments +ALTER TABLE payments ADD COLUMN IF NOT EXISTS payment_gateway_config_id UUID REFERENCES payment_gateway_configs(id); +ALTER TABLE payments ADD COLUMN IF NOT EXISTS payment_method VARCHAR(50); +ALTER TABLE payments ADD COLUMN IF NOT EXISTS currency_code VARCHAR(10) DEFAULT 'INR'; +ALTER TABLE payments ADD COLUMN IF NOT EXISTS initiated_at TIMESTAMPTZ DEFAULT NOW(); +ALTER TABLE payments ADD COLUMN IF NOT EXISTS completed_at TIMESTAMPTZ; + +-- Update kb_articles +ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS section_id UUID REFERENCES kb_sections(id); +ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS article_type VARCHAR(50) DEFAULT 'HOW_TO'; +ALTER TABLE kb_articles ADD COLUMN IF NOT EXISTS audience_type VARCHAR(50) DEFAULT 'ALL'; +ALTER TABLE kb_articles RENAME COLUMN body TO content_markdown; +ALTER TABLE kb_articles RENAME COLUMN created_by TO author_user_id; + +-- Update support_tickets +ALTER TABLE support_tickets ADD COLUMN IF NOT EXISTS created_by_user_id UUID; +UPDATE support_tickets SET created_by_user_id = user_id; +ALTER TABLE support_tickets RENAME COLUMN assigned_to TO assigned_to_user_id; +ALTER TABLE support_tickets ADD COLUMN IF NOT EXISTS related_entity_type VARCHAR(50); +ALTER TABLE support_tickets ADD COLUMN IF NOT EXISTS related_entity_id UUID; +ALTER TABLE support_tickets ADD COLUMN IF NOT EXISTS closed_at TIMESTAMPTZ; + +-- Update support_ticket_messages +ALTER TABLE support_ticket_messages ADD COLUMN IF NOT EXISTS sender_user_id UUID; +UPDATE support_ticket_messages SET sender_user_id = sender_id; +ALTER TABLE support_ticket_messages RENAME COLUMN body TO message_body; +ALTER TABLE support_ticket_messages ADD COLUMN IF NOT EXISTS attachment_url TEXT; + +-- Update notifications +ALTER TABLE notifications ADD COLUMN IF NOT EXISTS channel VARCHAR(50) DEFAULT 'IN_APP'; +ALTER TABLE notifications ADD COLUMN IF NOT EXISTS related_entity_type VARCHAR(50); +ALTER TABLE notifications RENAME COLUMN reference_id TO related_entity_id; + +-- Update reviews +ALTER TABLE reviews ADD COLUMN IF NOT EXISTS entity_type VARCHAR(50) DEFAULT 'professional'; +ALTER TABLE reviews RENAME COLUMN customer_id TO reviewer_user_id; +ALTER TABLE reviews ADD COLUMN IF NOT EXISTS status VARCHAR(50) DEFAULT 'PUBLISHED'; + +-- ============================================================================ +-- PHASE 9: Drop Deprecated Tables +-- ============================================================================ + +DROP TABLE IF EXISTS professionals CASCADE; +DROP TABLE IF EXISTS onboarding_submissions CASCADE; +DROP TABLE IF EXISTS onboarding_configs CASCADE; +DROP TABLE IF EXISTS onboarding_states CASCADE; +DROP TABLE IF EXISTS submission_documents CASCADE; + +-- ============================================================================ +-- PHASE 10: Drop Deprecated Columns from Extension Tables +-- ============================================================================ + +-- Drop old user_id columns from extension tables (AFTER backfilling user_role_profile_id) +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS user_id; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS user_id; + +-- Drop old columns from portfolio_items +ALTER TABLE portfolio_items DROP COLUMN IF EXISTS professional_id; +ALTER TABLE portfolio_items DROP COLUMN IF EXISTS user_id; +ALTER TABLE portfolio_items DROP COLUMN IF EXISTS profession_key; + +-- Drop old columns from services +ALTER TABLE services DROP COLUMN IF EXISTS professional_id; +ALTER TABLE services DROP COLUMN IF EXISTS user_id; +ALTER TABLE services DROP COLUMN IF EXISTS profession_key; + +-- Drop old columns from lead_requests +ALTER TABLE lead_requests DROP COLUMN IF EXISTS professional_id; +ALTER TABLE lead_requests DROP COLUMN IF EXISTS requirement_id; + +-- Drop old custom_data columns +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS custom_data; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS custom_data; + +-- Drop old profile columns that are now in user_role_profiles +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE photographer_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE tutor_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE makeup_artist_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE developer_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE video_editor_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE graphic_designer_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE social_media_manager_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE fitness_trainer_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE catering_service_profiles DROP COLUMN IF EXISTS approved_at; + +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS display_name; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS bio; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS location; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS status; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS rejection_reason; +ALTER TABLE ugc_content_creator_profiles DROP COLUMN IF EXISTS approved_at; + +COMMIT; + +-- ============================================================================ +-- Migration Complete +-- ============================================================================ diff --git a/crates/db/src/models/catering_service.rs b/crates/db/src/models/catering_service.rs index 57e812a..1f2e930 100644 --- a/crates/db/src/models/catering_service.rs +++ b/crates/db/src/models/catering_service.rs @@ -3,63 +3,77 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -// catering_service_profiles uses "business_name" instead of "display_name" - -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct CateringServiceProfile { pub id: Uuid, - pub user_id: Uuid, + pub user_role_profile_id: Uuid, pub business_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub cuisine_types: Vec, + pub event_types: Vec, + pub min_guests: Option, + pub max_guests: Option, + pub has_setup_team: bool, + pub has_serving_staff: bool, + pub price_per_head_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertCateringServiceProfilePayload { pub business_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub cuisine_types: Vec, + pub event_types: Vec, + pub min_guests: Option, + pub max_guests: Option, + pub has_setup_team: bool, + pub has_serving_staff: bool, + pub price_per_head_inr: Option, } pub struct CateringServiceRepository; impl CateringServiceRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, CateringServiceProfile>( - r#"SELECT id, user_id, business_name, bio, location, - custom_data, - status, created_at, updated_at - FROM catering_service_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, business_name, cuisine_types, event_types, + min_guests, max_guests, has_setup_team, has_serving_staff, + price_per_head_inr, created_at, updated_at + FROM catering_service_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertCateringServiceProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertCateringServiceProfilePayload) -> Result { sqlx::query_as::<_, CateringServiceProfile>( - r#"INSERT INTO catering_service_profiles (user_id, business_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - business_name = COALESCE(EXCLUDED.business_name, catering_service_profiles.business_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, business_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO catering_service_profiles (user_role_profile_id, business_name, cuisine_types, event_types, + min_guests, max_guests, has_setup_team, has_serving_staff, price_per_head_inr) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + business_name = EXCLUDED.business_name, + cuisine_types = COALESCE(EXCLUDED.cuisine_types, catering_service_profiles.cuisine_types), + event_types = COALESCE(EXCLUDED.event_types, catering_service_profiles.event_types), + min_guests = EXCLUDED.min_guests, + max_guests = EXCLUDED.max_guests, + has_setup_team = EXCLUDED.has_setup_team, + has_serving_staff = EXCLUDED.has_serving_staff, + price_per_head_inr = EXCLUDED.price_per_head_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, business_name, cuisine_types, event_types, + min_guests, max_guests, has_setup_team, has_serving_staff, + price_per_head_inr, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.business_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.business_name) + .bind(&p.cuisine_types) + .bind(&p.event_types) + .bind(p.min_guests) + .bind(p.max_guests) + .bind(p.has_setup_team) + .bind(p.has_serving_staff) + .bind(p.price_per_head_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/developer.rs b/crates/db/src/models/developer.rs index 2ac2c82..ece2fdc 100644 --- a/crates/db/src/models/developer.rs +++ b/crates/db/src/models/developer.rs @@ -3,61 +3,63 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct DeveloperProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub tech_stack: Vec, + pub experience_years: Option, + pub availability: String, + pub hourly_rate_inr: Option, + pub remote_ok: bool, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertDeveloperProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub tech_stack: Vec, + pub experience_years: Option, + pub availability: String, + pub hourly_rate_inr: Option, + pub remote_ok: bool, } pub struct DeveloperRepository; impl DeveloperRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, DeveloperProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM developer_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, tech_stack, experience_years, availability, + hourly_rate_inr, remote_ok, created_at, updated_at + FROM developer_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertDeveloperProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertDeveloperProfilePayload) -> Result { sqlx::query_as::<_, DeveloperProfile>( - r#"INSERT INTO developer_profiles (user_id, display_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, developer_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO developer_profiles (user_role_profile_id, tech_stack, experience_years, + availability, hourly_rate_inr, remote_ok) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + tech_stack = COALESCE(EXCLUDED.tech_stack, developer_profiles.tech_stack), + experience_years = EXCLUDED.experience_years, + availability = EXCLUDED.availability, + hourly_rate_inr = EXCLUDED.hourly_rate_inr, + remote_ok = EXCLUDED.remote_ok, + updated_at = NOW() + RETURNING id, user_role_profile_id, tech_stack, experience_years, availability, + hourly_rate_inr, remote_ok, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.tech_stack) + .bind(p.experience_years) + .bind(&p.availability) + .bind(p.hourly_rate_inr) + .bind(p.remote_ok) .fetch_one(pool) .await } diff --git a/crates/db/src/models/fitness_trainer.rs b/crates/db/src/models/fitness_trainer.rs index 6100dfc..a97fde8 100644 --- a/crates/db/src/models/fitness_trainer.rs +++ b/crates/db/src/models/fitness_trainer.rs @@ -3,61 +3,67 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct FitnessTrainerProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub disciplines: Vec, + pub certifications: Vec, + pub online_sessions: bool, + pub home_visits: bool, + pub gym_based: bool, + pub per_session_rate_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertFitnessTrainerProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub disciplines: Vec, + pub certifications: Vec, + pub online_sessions: bool, + pub home_visits: bool, + pub gym_based: bool, + pub per_session_rate_inr: Option, } pub struct FitnessTrainerRepository; impl FitnessTrainerRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, FitnessTrainerProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM fitness_trainer_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, disciplines, certifications, online_sessions, + home_visits, gym_based, per_session_rate_inr, created_at, updated_at + FROM fitness_trainer_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertFitnessTrainerProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertFitnessTrainerProfilePayload) -> Result { sqlx::query_as::<_, FitnessTrainerProfile>( - r#"INSERT INTO fitness_trainer_profiles (user_id, display_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, fitness_trainer_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO fitness_trainer_profiles (user_role_profile_id, disciplines, certifications, + online_sessions, home_visits, gym_based, per_session_rate_inr) + VALUES ($1, $2, $3, $4, $5, $6, $7) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + disciplines = COALESCE(EXCLUDED.disciplines, fitness_trainer_profiles.disciplines), + certifications = COALESCE(EXCLUDED.certifications, fitness_trainer_profiles.certifications), + online_sessions = EXCLUDED.online_sessions, + home_visits = EXCLUDED.home_visits, + gym_based = EXCLUDED.gym_based, + per_session_rate_inr = EXCLUDED.per_session_rate_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, disciplines, certifications, online_sessions, + home_visits, gym_based, per_session_rate_inr, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.disciplines) + .bind(&p.certifications) + .bind(p.online_sessions) + .bind(p.home_visits) + .bind(p.gym_based) + .bind(p.per_session_rate_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/graphic_designer.rs b/crates/db/src/models/graphic_designer.rs index 1dc169b..8135da0 100644 --- a/crates/db/src/models/graphic_designer.rs +++ b/crates/db/src/models/graphic_designer.rs @@ -3,61 +3,59 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct GraphicDesignerProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub design_tools: Vec, + pub style_tags: Vec, + pub brand_experience: bool, + pub starting_price_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertGraphicDesignerProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub design_tools: Vec, + pub style_tags: Vec, + pub brand_experience: bool, + pub starting_price_inr: Option, } pub struct GraphicDesignerRepository; impl GraphicDesignerRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, GraphicDesignerProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM graphic_designer_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, design_tools, style_tags, brand_experience, + starting_price_inr, created_at, updated_at + FROM graphic_designer_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertGraphicDesignerProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertGraphicDesignerProfilePayload) -> Result { sqlx::query_as::<_, GraphicDesignerProfile>( - r#"INSERT INTO graphic_designer_profiles (user_id, display_name, bio, location, custom_data) + r#"INSERT INTO graphic_designer_profiles (user_role_profile_id, design_tools, style_tags, + brand_experience, starting_price_inr) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, graphic_designer_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + ON CONFLICT (user_role_profile_id) DO UPDATE SET + design_tools = COALESCE(EXCLUDED.design_tools, graphic_designer_profiles.design_tools), + style_tags = COALESCE(EXCLUDED.style_tags, graphic_designer_profiles.style_tags), + brand_experience = EXCLUDED.brand_experience, + starting_price_inr = EXCLUDED.starting_price_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, design_tools, style_tags, brand_experience, + starting_price_inr, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.design_tools) + .bind(&p.style_tags) + .bind(p.brand_experience) + .bind(p.starting_price_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/makeup_artist.rs b/crates/db/src/models/makeup_artist.rs index dbcca26..9230e78 100644 --- a/crates/db/src/models/makeup_artist.rs +++ b/crates/db/src/models/makeup_artist.rs @@ -3,61 +3,65 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct MakeupArtistProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub specializations: Vec, + pub kit_brands: Vec, + pub home_service: bool, + pub studio_available: bool, + pub starting_price_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertMakeupArtistProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub specializations: Vec, + pub kit_brands: Vec, + pub home_service: bool, + pub studio_available: bool, + pub starting_price_inr: Option, } pub struct MakeupArtistRepository; impl MakeupArtistRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, MakeupArtistProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM makeup_artist_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, specializations, kit_brands, + home_service, studio_available, starting_price_inr, + created_at, updated_at + FROM makeup_artist_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertMakeupArtistProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertMakeupArtistProfilePayload) -> Result { sqlx::query_as::<_, MakeupArtistProfile>( - r#"INSERT INTO makeup_artist_profiles (user_id, display_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, makeup_artist_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO makeup_artist_profiles (user_role_profile_id, specializations, kit_brands, + home_service, studio_available, starting_price_inr) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + specializations = COALESCE(EXCLUDED.specializations, makeup_artist_profiles.specializations), + kit_brands = COALESCE(EXCLUDED.kit_brands, makeup_artist_profiles.kit_brands), + home_service = EXCLUDED.home_service, + studio_available = EXCLUDED.studio_available, + starting_price_inr = EXCLUDED.starting_price_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, specializations, kit_brands, + home_service, studio_available, starting_price_inr, + created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.specializations) + .bind(&p.kit_brands) + .bind(p.home_service) + .bind(p.studio_available) + .bind(p.starting_price_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/social_media_manager.rs b/crates/db/src/models/social_media_manager.rs index c5093e2..2da8416 100644 --- a/crates/db/src/models/social_media_manager.rs +++ b/crates/db/src/models/social_media_manager.rs @@ -3,61 +3,63 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct SocialMediaManagerProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub platforms: Vec, + pub industries: Vec, + pub content_types: Vec, + pub avg_follower_growth_pct: Option, + pub starting_price_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertSocialMediaManagerProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub platforms: Vec, + pub industries: Vec, + pub content_types: Vec, + pub avg_follower_growth_pct: Option, + pub starting_price_inr: Option, } pub struct SocialMediaManagerRepository; impl SocialMediaManagerRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, SocialMediaManagerProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM social_media_manager_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, platforms, industries, content_types, + avg_follower_growth_pct, starting_price_inr, created_at, updated_at + FROM social_media_manager_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertSocialMediaManagerProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertSocialMediaManagerProfilePayload) -> Result { sqlx::query_as::<_, SocialMediaManagerProfile>( - r#"INSERT INTO social_media_manager_profiles (user_id, display_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, social_media_manager_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO social_media_manager_profiles (user_role_profile_id, platforms, industries, + content_types, avg_follower_growth_pct, starting_price_inr) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + platforms = COALESCE(EXCLUDED.platforms, social_media_manager_profiles.platforms), + industries = COALESCE(EXCLUDED.industries, social_media_manager_profiles.industries), + content_types = COALESCE(EXCLUDED.content_types, social_media_manager_profiles.content_types), + avg_follower_growth_pct = EXCLUDED.avg_follower_growth_pct, + starting_price_inr = EXCLUDED.starting_price_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, platforms, industries, content_types, + avg_follower_growth_pct, starting_price_inr, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.platforms) + .bind(&p.industries) + .bind(&p.content_types) + .bind(p.avg_follower_growth_pct) + .bind(p.starting_price_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/tutor.rs b/crates/db/src/models/tutor.rs index f4d0e25..aa5ea58 100644 --- a/crates/db/src/models/tutor.rs +++ b/crates/db/src/models/tutor.rs @@ -3,61 +3,73 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct TutorProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub subjects: Vec, + pub board_types: Vec, + pub qualification: Option, + pub teaches_online: bool, + pub teaches_offline: bool, + pub experience_years: Option, + pub hourly_rate_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertTutorProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub subjects: Vec, + pub board_types: Vec, + pub qualification: Option, + pub teaches_online: bool, + pub teaches_offline: bool, + pub experience_years: Option, + pub hourly_rate_inr: Option, } pub struct TutorRepository; impl TutorRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, TutorProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM tutor_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, subjects, board_types, qualification, + teaches_online, teaches_offline, experience_years, hourly_rate_inr, + created_at, updated_at + FROM tutor_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertTutorProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertTutorProfilePayload) -> Result { sqlx::query_as::<_, TutorProfile>( - r#"INSERT INTO tutor_profiles (user_id, display_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, tutor_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO tutor_profiles (user_role_profile_id, subjects, board_types, qualification, + teaches_online, teaches_offline, experience_years, hourly_rate_inr) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + subjects = COALESCE(EXCLUDED.subjects, tutor_profiles.subjects), + board_types = COALESCE(EXCLUDED.board_types, tutor_profiles.board_types), + qualification = EXCLUDED.qualification, + teaches_online = EXCLUDED.teaches_online, + teaches_offline = EXCLUDED.teaches_offline, + experience_years = EXCLUDED.experience_years, + hourly_rate_inr = EXCLUDED.hourly_rate_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, subjects, board_types, qualification, + teaches_online, teaches_offline, experience_years, hourly_rate_inr, + created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.subjects) + .bind(&p.board_types) + .bind(&p.qualification) + .bind(p.teaches_online) + .bind(p.teaches_offline) + .bind(p.experience_years) + .bind(p.hourly_rate_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/ugc_content_creator.rs b/crates/db/src/models/ugc_content_creator.rs index a1ecebd..e03fede 100644 --- a/crates/db/src/models/ugc_content_creator.rs +++ b/crates/db/src/models/ugc_content_creator.rs @@ -3,61 +3,63 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct UgcContentCreatorProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub niche_tags: Vec, + pub content_formats: Vec, + pub platforms: Vec, + pub turnaround_days: Option, + pub starting_price_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertUgcContentCreatorProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub niche_tags: Vec, + pub content_formats: Vec, + pub platforms: Vec, + pub turnaround_days: Option, + pub starting_price_inr: Option, } pub struct UgcContentCreatorRepository; impl UgcContentCreatorRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, UgcContentCreatorProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM ugc_content_creator_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, niche_tags, content_formats, platforms, + turnaround_days, starting_price_inr, created_at, updated_at + FROM ugc_content_creator_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertUgcContentCreatorProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertUgcContentCreatorProfilePayload) -> Result { sqlx::query_as::<_, UgcContentCreatorProfile>( - r#"INSERT INTO ugc_content_creator_profiles (user_id, display_name, bio, location, custom_data) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, ugc_content_creator_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + r#"INSERT INTO ugc_content_creator_profiles (user_role_profile_id, niche_tags, content_formats, + platforms, turnaround_days, starting_price_inr) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT (user_role_profile_id) DO UPDATE SET + niche_tags = COALESCE(EXCLUDED.niche_tags, ugc_content_creator_profiles.niche_tags), + content_formats = COALESCE(EXCLUDED.content_formats, ugc_content_creator_profiles.content_formats), + platforms = COALESCE(EXCLUDED.platforms, ugc_content_creator_profiles.platforms), + turnaround_days = EXCLUDED.turnaround_days, + starting_price_inr = EXCLUDED.starting_price_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, niche_tags, content_formats, platforms, + turnaround_days, starting_price_inr, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.niche_tags) + .bind(&p.content_formats) + .bind(&p.platforms) + .bind(p.turnaround_days) + .bind(p.starting_price_inr) .fetch_one(pool) .await } diff --git a/crates/db/src/models/video_editor.rs b/crates/db/src/models/video_editor.rs index 285b4fc..376affd 100644 --- a/crates/db/src/models/video_editor.rs +++ b/crates/db/src/models/video_editor.rs @@ -3,61 +3,59 @@ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; -#[derive(Debug, Serialize, Deserialize, FromRow)] +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct VideoEditorProfile { pub id: Uuid, - pub user_id: Uuid, - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, - pub status: String, + pub user_role_profile_id: Uuid, + pub software_skills: Vec, + pub style_tags: Vec, + pub turnaround_days: Option, + pub starting_price_inr: Option, pub created_at: DateTime, pub updated_at: DateTime, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpsertVideoEditorProfilePayload { - pub display_name: Option, - pub bio: Option, - pub location: Option, - pub custom_data: Option, + pub software_skills: Vec, + pub style_tags: Vec, + pub turnaround_days: Option, + pub starting_price_inr: Option, } pub struct VideoEditorRepository; impl VideoEditorRepository { - pub async fn get_by_user_id(pool: &PgPool, user_id: Uuid) -> Result, sqlx::Error> { + pub async fn get_by_user_role_id(pool: &PgPool, user_role_profile_id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, VideoEditorProfile>( - r#"SELECT id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at - FROM video_editor_profiles WHERE user_id = $1"#, + r#"SELECT id, user_role_profile_id, software_skills, style_tags, turnaround_days, + starting_price_inr, created_at, updated_at + FROM video_editor_profiles WHERE user_role_profile_id = $1"#, ) - .bind(user_id) + .bind(user_role_profile_id) .fetch_optional(pool) .await } - pub async fn upsert(pool: &PgPool, user_id: Uuid, p: UpsertVideoEditorProfilePayload) -> Result { + pub async fn upsert(pool: &PgPool, user_role_profile_id: Uuid, p: UpsertVideoEditorProfilePayload) -> Result { sqlx::query_as::<_, VideoEditorProfile>( - r#"INSERT INTO video_editor_profiles (user_id, display_name, bio, location, custom_data) + r#"INSERT INTO video_editor_profiles (user_role_profile_id, software_skills, style_tags, + turnaround_days, starting_price_inr) VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (user_id) DO UPDATE SET - display_name = COALESCE(EXCLUDED.display_name, video_editor_profiles.display_name), - bio = EXCLUDED.bio, - location = EXCLUDED.location, - custom_data = EXCLUDED.custom_data, - updated_at = NOW() - RETURNING id, user_id, display_name, bio, location, - custom_data, - status, created_at, updated_at"#, + ON CONFLICT (user_role_profile_id) DO UPDATE SET + software_skills = COALESCE(EXCLUDED.software_skills, video_editor_profiles.software_skills), + style_tags = COALESCE(EXCLUDED.style_tags, video_editor_profiles.style_tags), + turnaround_days = EXCLUDED.turnaround_days, + starting_price_inr = EXCLUDED.starting_price_inr, + updated_at = NOW() + RETURNING id, user_role_profile_id, software_skills, style_tags, turnaround_days, + starting_price_inr, created_at, updated_at"#, ) - .bind(user_id) - .bind(p.display_name) - .bind(p.bio) - .bind(p.location) - .bind(p.custom_data) + .bind(user_role_profile_id) + .bind(&p.software_skills) + .bind(&p.style_tags) + .bind(p.turnaround_days) + .bind(p.starting_price_inr) .fetch_one(pool) .await }