feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- Nxtgauge Seed Script
-- Run: psql $DATABASE_URL -f scripts/seed.sql
-- Safe to re-run (uses INSERT ... ON CONFLICT DO UPDATE for configs)
-- ── 1. Roles ─────────────────────────────────────────────────────────────────
2026-04-15 06:23:27 +02:00
-- Internal roles
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
INSERT INTO roles ( key , name , audience ) VALUES
( ' SUPER_ADMIN ' , ' Super Admin ' , ' INTERNAL ' ) ,
( ' ADMIN ' , ' Admin ' , ' INTERNAL ' ) ,
2026-04-15 06:23:27 +02:00
( ' SUPPORT ' , ' Support Agent ' , ' INTERNAL ' )
ON CONFLICT ( key ) DO NOTHING ;
-- Internal role extensions
INSERT INTO internal_roles ( role_id , description )
SELECT id , ' Full system administrator with all permissions ' FROM roles WHERE key = ' SUPER_ADMIN '
ON CONFLICT ( role_id ) DO NOTHING ;
INSERT INTO internal_roles ( role_id , description , can_approve_requests , can_manage_system_settings )
SELECT id , ' Standard administrator ' , true , true FROM roles WHERE key = ' ADMIN '
ON CONFLICT ( role_id ) DO NOTHING ;
INSERT INTO internal_roles ( role_id , description )
SELECT id , ' Customer support agent ' FROM roles WHERE key = ' SUPPORT '
ON CONFLICT ( role_id ) DO NOTHING ;
-- External roles
INSERT INTO roles ( key , name , audience ) VALUES
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
( ' COMPANY ' , ' Company ' , ' EXTERNAL ' ) ,
( ' JOB_SEEKER ' , ' Job Seeker ' , ' EXTERNAL ' ) ,
( ' CUSTOMER ' , ' Customer ' , ' EXTERNAL ' ) ,
( ' PHOTOGRAPHER ' , ' Photographer ' , ' EXTERNAL ' ) ,
( ' MAKEUP_ARTIST ' , ' Makeup Artist ' , ' EXTERNAL ' ) ,
( ' TUTOR ' , ' Tutor ' , ' EXTERNAL ' ) ,
( ' DEVELOPER ' , ' Developer ' , ' EXTERNAL ' ) ,
( ' VIDEO_EDITOR ' , ' Video Editor ' , ' EXTERNAL ' ) ,
( ' GRAPHIC_DESIGNER ' , ' Graphic Designer ' , ' EXTERNAL ' ) ,
( ' SOCIAL_MEDIA_MANAGER ' , ' Social Media Manager ' , ' EXTERNAL ' ) ,
( ' FITNESS_TRAINER ' , ' Fitness Trainer ' , ' EXTERNAL ' ) ,
( ' CATERING_SERVICES ' , ' Catering Services ' , ' EXTERNAL ' )
ON CONFLICT ( key ) DO NOTHING ;
2026-04-15 06:23:27 +02:00
-- External role extensions
INSERT INTO external_roles ( role_id )
SELECT id FROM roles WHERE audience = ' EXTERNAL '
ON CONFLICT ( role_id ) DO NOTHING ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- ── 2. Super Admin User ──────────────────────────────────────────────────────
-- Default password: Admin@nxtgauge1 (bcrypt hash)
-- CHANGE THIS PASSWORD IMMEDIATELY AFTER FIRST LOGIN
DO $ $
DECLARE
super_admin_role_id UUID ;
admin_user_id UUID ;
BEGIN
SELECT id INTO super_admin_role_id FROM roles WHERE key = ' SUPER_ADMIN ' ;
2026-04-15 06:23:27 +02:00
INSERT INTO users ( email , password_hash , status , role_id , first_name , last_name , email_verified )
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
VALUES (
' admin@nxtgauge.com ' ,
' $2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TiGniB9GSmJBGp0K7RqUi/4hY/Ii ' ,
' ACTIVE ' ,
super_admin_role_id ,
2026-04-15 06:23:27 +02:00
' Super ' ,
' Admin ' ,
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
true
)
ON CONFLICT ( email ) DO NOTHING
RETURNING id INTO admin_user_id ;
IF admin_user_id IS NOT NULL THEN
INSERT INTO user_roles ( user_id , role_id , status , approved_at )
VALUES ( admin_user_id , super_admin_role_id , ' APPROVED ' , NOW ( ) )
ON CONFLICT ( user_id , role_id ) DO NOTHING ;
END IF ;
END $ $ ;
-- ── 3. Onboarding Configs ─────────────────────────────────────────────────────
-- Each role gets its own INSERT for clarity and maintainability.
-- Fields use "id" (not "key") to match the frontend field binding.
-- All document uploads accept PDF + images.
-- City fields are readOnly and default to "Chennai, India".
-- version = 2 to force updates on re-run.
-- CUSTOMER (14 steps: 1 service select + 9 profession-specific + 4 shared)
-- The frontend normalizeSchemaPayload() auto-adds visibleWhen to steps
-- matching /^customer_(requirements|budget)_([a-z_]+)$/.
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_service " ,
" title " : " Select Service Category " ,
" fields " : [
{
" id " : " profession " ,
" label " : " Service Category " ,
" type " : " select " ,
" required " : true ,
" options " : [
{ " label " : " Photographer " , " value " : " photographer " } ,
{ " label " : " Makeup Artist " , " value " : " makeup_artist " } ,
{ " label " : " Tutor " , " value " : " tutor " } ,
{ " label " : " Developer " , " value " : " developer " } ,
{ " label " : " Video Editor " , " value " : " video_editor " } ,
{ " label " : " Graphic Designer " , " value " : " graphic_designer " } ,
{ " label " : " Social Media Manager " , " value " : " social_media_manager " } ,
{ " label " : " Fitness Trainer " , " value " : " fitness_trainer " } ,
{ " label " : " Catering Services " , " value " : " catering_services " }
]
}
]
} ,
{
" id " : " customer_requirements_photographer " ,
" title " : " Photography Requirements " ,
" fields " : [
{ " id " : " event_type " , " label " : " Event Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Wedding " , " value " : " Wedding " } , { " label " : " Corporate Event " , " value " : " Corporate Event " } , { " label " : " Birthday " , " value " : " Birthday " } , { " label " : " Product Shoot " , " value " : " Product Shoot " } , { " label " : " Portrait " , " value " : " Portrait " } ] } ,
{ " id " : " coverage_hours " , " label " : " Coverage Hours " , " type " : " number " , " required " : true , " placeholder " : " e.g., 4 " , " validation " : { " min " : 1 } } ,
{ " id " : " photo_style " , " label " : " Photo Style " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Traditional " , " value " : " Traditional " } , { " label " : " Candid " , " value " : " Candid " } , { " label " : " Cinematic " , " value " : " Cinematic " } , { " label " : " Documentary " , " value " : " Documentary " } ] }
]
} ,
{
" id " : " customer_requirements_makeup_artist " ,
" title " : " Makeup Requirements " ,
" fields " : [
{ " id " : " occasion_type " , " label " : " Occasion Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Bridal " , " value " : " Bridal " } , { " label " : " Party/Guest " , " value " : " Party/Guest " } , { " label " : " Photoshoot " , " value " : " Photoshoot " } , { " label " : " Editorial " , " value " : " Editorial " } ] } ,
{ " id " : " people_count " , " label " : " Number of People " , " type " : " number " , " required " : true , " placeholder " : " e.g., 2 " , " validation " : { " min " : 1 } } ,
{ " id " : " skin_preferences " , " label " : " Skin Preferences " , " type " : " textarea " , " placeholder " : " Any allergies or specific product requests? " }
]
} ,
{
" id " : " customer_requirements_tutor " ,
" title " : " Tutoring Requirements " ,
" fields " : [
{ " id " : " subject " , " label " : " Subject " , " type " : " text " , " required " : true , " placeholder " : " e.g., Mathematics, Spoken English " } ,
{ " id " : " grade_level " , " label " : " Grade Level " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Primary " , " value " : " Primary " } , { " label " : " Middle School " , " value " : " Middle School " } , { " label " : " High School " , " value " : " High School " } , { " label " : " College " , " value " : " College " } , { " label " : " Professional " , " value " : " Professional " } ] } ,
{ " id " : " sessions_per_week " , " label " : " Sessions Per Week " , " type " : " number " , " required " : true , " placeholder " : " e.g., 3 " , " validation " : { " min " : 1 } }
]
} ,
{
" id " : " customer_requirements_developer " ,
" title " : " Development Requirements " ,
" fields " : [
{ " id " : " project_type " , " label " : " Project Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Website " , " value " : " Website " } , { " label " : " Mobile App " , " value " : " Mobile App " } , { " label " : " E-commerce " , " value " : " E-commerce " } , { " label " : " Custom Software " , " value " : " Custom Software " } ] } ,
{ " id " : " platform " , " label " : " Platform " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " iOS " , " value " : " iOS " } , { " label " : " Android " , " value " : " Android " } , { " label " : " Web " , " value " : " Web " } , { " label " : " Cross-platform " , " value " : " Cross-platform " } ] } ,
{ " id " : " feature_summary " , " label " : " Feature Summary " , " type " : " textarea " , " required " : true , " placeholder " : " Briefly describe what the app/website should do " }
]
} ,
{
" id " : " customer_requirements_video_editor " ,
" title " : " Video Editing Requirements " ,
" fields " : [
{ " id " : " video_type " , " label " : " Video Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " YouTube Video " , " value " : " YouTube Video " } , { " label " : " Instagram Reel/Shorts " , " value " : " Instagram Reel/Shorts " } , { " label " : " Wedding Highlights " , " value " : " Wedding Highlights " } , { " label " : " Corporate Promo " , " value " : " Corporate Promo " } ] } ,
{ " id " : " video_duration " , " label " : " Video Duration " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Under 1 min " , " value " : " Under 1 min " } , { " label " : " 1-5 mins " , " value " : " 1-5 mins " } , { " label " : " 5-15 mins " , " value " : " 5-15 mins " } , { " label " : " Over 15 mins " , " value " : " Over 15 mins " } ] } ,
{ " id " : " editing_style " , " label " : " Editing Style " , " type " : " text " , " required " : true , " placeholder " : " e.g., Fast-paced, Cinematic, Vlog style " }
]
} ,
{
" id " : " customer_requirements_graphic_designer " ,
" title " : " Design Requirements " ,
" fields " : [
{ " id " : " design_type " , " label " : " Design Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Logo/Branding " , " value " : " Logo/Branding " } , { " label " : " Social Media Posts " , " value " : " Social Media Posts " } , { " label " : " UI/UX " , " value " : " UI/UX " } , { " label " : " Print Media " , " value " : " Print Media " } ] } ,
{ " id " : " brand_guidelines " , " label " : " Brand Guidelines " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Yes - I have them " , " value " : " Yes - I have them " } , { " label " : " No - Need to create them " , " value " : " No - Need to create them " } ] } ,
{ " id " : " asset_count " , " label " : " Asset Count " , " type " : " number " , " required " : true , " placeholder " : " How many images/screens? " , " validation " : { " min " : 1 } }
]
} ,
{
" id " : " customer_requirements_social_media_manager " ,
" title " : " Social Media Requirements " ,
" fields " : [
{ " id " : " platforms " , " label " : " Platforms " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Instagram " , " value " : " Instagram " } , { " label " : " LinkedIn " , " value " : " LinkedIn " } , { " label " : " Facebook " , " value " : " Facebook " } , { " label " : " X/Twitter " , " value " : " X/Twitter " } , { " label " : " YouTube " , " value " : " YouTube " } ] } ,
{ " id " : " posting_frequency " , " label " : " Posting Frequency " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " 1-2 times/week " , " value " : " 1-2 times/week " } , { " label " : " 3-4 times/week " , " value " : " 3-4 times/week " } , { " label " : " Daily " , " value " : " Daily " } ] } ,
{ " id " : " goal " , " label " : " Goal " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Brand Awareness " , " value " : " Brand Awareness " } , { " label " : " Lead Generation " , " value " : " Lead Generation " } , { " label " : " Sales/Conversions " , " value " : " Sales/Conversions " } , { " label " : " Community Building " , " value " : " Community Building " } ] }
]
} ,
{
" id " : " customer_requirements_fitness_trainer " ,
" title " : " Fitness Training Requirements " ,
" fields " : [
{ " id " : " fitness_goal " , " label " : " Fitness Goal " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Weight Loss " , " value " : " Weight Loss " } , { " label " : " Muscle Gain " , " value " : " Muscle Gain " } , { " label " : " Flexibility/Yoga " , " value " : " Flexibility/Yoga " } , { " label " : " General Fitness " , " value " : " General Fitness " } ] } ,
{ " id " : " sessions_per_week " , " label " : " Sessions Per Week " , " type " : " number " , " required " : true , " placeholder " : " e.g., 5 " , " validation " : { " min " : 1 } } ,
{ " id " : " training_mode " , " label " : " Training Mode " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Online/Virtual " , " value " : " Online/Virtual " } , { " label " : " In-person " , " value " : " In-person " } ] }
]
} ,
{
" id " : " customer_requirements_catering_services " ,
" title " : " Catering Requirements " ,
" fields " : [
{ " id " : " event_size " , " label " : " Event Size (Guests) " , " type " : " number " , " required " : true , " placeholder " : " Number of guests/plates " , " validation " : { " min " : 1 } } ,
{ " id " : " menu_preference " , " label " : " Menu Preference " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Pure Veg " , " value " : " Pure Veg " } , { " label " : " Non-Veg " , " value " : " Non-Veg " } , { " label " : " Mixed " , " value " : " Mixed " } ] } ,
{ " id " : " cuisine_type " , " label " : " Cuisine Type " , " type " : " text " , " required " : true , " placeholder " : " e.g., South Indian, North Indian, Continental " }
]
} ,
{
" id " : " step_3_budget_timeline " ,
" title " : " Budget and Timeline " ,
" fields " : [
{ " id " : " budget_range " , " label " : " Budget Range " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Under \u20b95,000 " , " value " : " Under \u20b95,000 " } , { " label " : " \u20b95,000 - \u20b915,000 " , " value " : " \u20b95,000 - \u20b915,000 " } , { " label " : " \u20b915,000 - \u20b950,000 " , " value " : " \u20b915,000 - \u20b950,000 " } , { " label " : " \u20b950,000 - \u20b91,00,000 " , " value " : " \u20b950,000 - \u20b91,00,000 " } , { " label " : " \u20b91,00,000+ " , " value " : " \u20b91,00,000+ " } ] } ,
{ " id " : " expected_start " , " label " : " Expected Start " , " type " : " date " , " required " : true } ,
{ " id " : " urgency " , " label " : " Urgency " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Relaxed (No strict deadline) " , " value " : " Relaxed (No strict deadline) " } , { " label " : " Standard (Within a few weeks) " , " value " : " Standard (Within a few weeks) " } , { " label " : " ASAP (Urgent) " , " value " : " ASAP (Urgent) " } ] }
]
} ,
{
" id " : " step_4_location " ,
" title " : " Location and Preference " ,
" fields " : [
{ " id " : " service_mode " , " label " : " Service Mode " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Onsite (In-person) " , " value " : " Onsite (In-person) " } , { " label " : " Remote (Online) " , " value " : " Remote (Online) " } , { " label " : " Hybrid (Mix of both) " , " value " : " Hybrid (Mix of both) " } ] } ,
{ " id " : " address_line " , " label " : " Address Line " , " type " : " text " , " required " : true , " placeholder " : " Street address, Landmark " } ,
{ " id " : " service_city " , " label " : " Service City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " } ,
{ " id " : " pin_code " , " label " : " PIN Code " , " type " : " text " , " required " : true , " placeholder " : " e.g., 600001 " , " validation " : { " pattern " : " ^[0-9]{6}$ " , " minLength " : 6 , " maxLength " : 6 } }
]
} ,
{
" id " : " step_5_review " ,
" title " : " Final Review " ,
" fields " : [
{ " id " : " summary_note " , " label " : " Additional Instructions " , " type " : " textarea " , " placeholder " : " Any additional instructions or context? " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' CUSTOMER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- COMPANY (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_identity " ,
" title " : " Company Identity " ,
" fields " : [
{ " id " : " company_name " , " label " : " Company Name " , " type " : " text " , " required " : true , " placeholder " : " Your registered company name " } ,
{ " id " : " legal_name " , " label " : " Legal Name " , " type " : " text " , " required " : true , " placeholder " : " As per registration documents " } ,
{ " id " : " industry " , " label " : " Industry " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " IT/Software " , " value " : " IT/Software " } , { " label " : " Marketing/Advertising " , " value " : " Marketing/Advertising " } , { " label " : " EdTech " , " value " : " EdTech " } , { " label " : " Media/Entertainment " , " value " : " Media/Entertainment " } , { " label " : " Health/Wellness " , " value " : " Health/Wellness " } , { " label " : " Food/Beverage " , " value " : " Food/Beverage " } , { " label " : " Other " , " value " : " Other " } ] }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact Details " ,
" fields " : [
{ " id " : " contact_name " , " label " : " Contact Person Name " , " type " : " text " , " required " : true , " placeholder " : " HR Manager or Founder " } ,
{ " id " : " contact_email " , " label " : " Contact Email " , " type " : " email " , " required " : true , " placeholder " : " hr@yourcompany.com " } ,
{ " id " : " contact_phone " , " label " : " Contact Phone " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } }
]
} ,
{
" id " : " step_3_presence " ,
" title " : " Company Presence " ,
" fields " : [
{ " id " : " website " , " label " : " Website URL " , " type " : " url " , " required " : false , " placeholder " : " https://yourcompany.com " } ,
{ " id " : " hq_city " , " label " : " HQ City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " } ,
{ " id " : " team_size " , " label " : " Team Size " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " 1-10 " , " value " : " 1-10 " } , { " label " : " 11-50 " , " value " : " 11-50 " } , { " label " : " 51-200 " , " value " : " 51-200 " } , { " label " : " 200+ " , " value " : " 200+ " } ] }
]
} ,
{
" id " : " step_4_hiring " ,
" title " : " Hiring Preferences " ,
" fields " : [
{ " id " : " hiring_for " , " label " : " Currently Hiring For " , " type " : " text " , " required " : true , " placeholder " : " e.g., Frontend Developer, Sales Executive " } ,
{ " id " : " work_mode " , " label " : " Work Mode " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Onsite (Work from office) " , " value " : " Onsite " } , { " label " : " Remote (Work from home) " , " value " : " Remote " } , { " label " : " Hybrid " , " value " : " Hybrid " } ] } ,
{ " id " : " monthly_openings " , " label " : " Monthly Openings " , " type " : " number " , " required " : true , " placeholder " : " Expected number of hires/month " , " validation " : { " min " : 1 } }
]
} ,
{
" id " : " step_5_compliance " ,
" title " : " Verification and Compliance " ,
" fields " : [
{ " id " : " registration_number " , " label " : " Company Registration Number " , " type " : " text " , " required " : true , " placeholder " : " CIN / MSME / GST Number " } ,
{ " id " : " official_email " , " label " : " Official Email " , " type " : " email " , " required " : true , " placeholder " : " official@yourcompany.com " }
]
} ,
{
" id " : " step_6_business_verification " ,
" title " : " Business Verification " ,
" fields " : [
{ " id " : " company_doc_type " , " label " : " Document Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " GST Certificate " , " value " : " GST Certificate " } , { " label " : " Certificate of Incorporation " , " value " : " Certificate of Incorporation " } , { " label " : " MSME/Udyam Registration " , " value " : " MSME/Udyam Registration " } , { " label " : " Company PAN Card " , " value " : " Company PAN Card " } ] } ,
{ " id " : " company_doc_upload " , " label " : " Upload Company Document " , " type " : " file " , " required " : true , " multiple " : false , " maxFiles " : 1 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' COMPANY '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- JOB_SEEKER (5 steps — NO resume upload to prevent phone number exposure)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_basic " ,
" title " : " Basic Profile " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " } ,
{ " id " : " skills " , " label " : " Key Skills " , " type " : " text " , " required " : true , " placeholder " : " e.g., JavaScript, React, Node.js (comma-separated) " }
]
} ,
{
" id " : " step_2_preferences " ,
" title " : " Job Preferences " ,
" fields " : [
{ " id " : " preferred_role " , " label " : " Preferred Role " , " type " : " text " , " required " : true , " placeholder " : " e.g., Frontend Developer " } ,
{ " id " : " expected_salary " , " label " : " Expected Salary (LPA) " , " type " : " number " , " required " : true , " placeholder " : " Annual salary in Lakhs " , " validation " : { " min " : 0 } } ,
{ " id " : " work_mode " , " label " : " Work Mode " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Onsite " , " value " : " Onsite " } , { " label " : " Remote " , " value " : " Remote " } , { " label " : " Hybrid " , " value " : " Hybrid " } ] }
]
} ,
{
" id " : " step_3_experience " ,
" title " : " Experience Details " ,
" fields " : [
{ " id " : " experience_years " , " label " : " Years of Experience " , " type " : " number " , " required " : true , " placeholder " : " 0 for freshers " , " validation " : { " min " : 0 } } ,
{ " id " : " latest_company " , " label " : " Latest/Current Company " , " type " : " text " , " required " : false , " placeholder " : " Company name or 'Fresher' " } ,
{ " id " : " notice_period " , " label " : " Notice Period " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Immediate " , " value " : " Immediate " } , { " label " : " 15 Days " , " value " : " 15 Days " } , { " label " : " 30 Days " , " value " : " 30 Days " } , { " label " : " 60 Days " , " value " : " 60 Days " } , { " label " : " 90 Days " , " value " : " 90 Days " } ] }
]
} ,
{
" id " : " step_4_review " ,
" title " : " About Me " ,
" fields " : [
{ " id " : " about_me " , " label " : " About Me " , " type " : " textarea " , " required " : true , " placeholder " : " Tell companies about yourself, your strengths, and career goals " } ,
{ " id " : " linkedin_url " , " label " : " LinkedIn URL " , " type " : " url " , " required " : false , " placeholder " : " https://linkedin.com/in/yourprofile " }
]
} ,
{
" id " : " step_5_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' JOB_SEEKER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- PHOTOGRAPHER (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a photographer specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Photography Specialization " ,
" fields " : [
{ " id " : " specialty " , " label " : " Specialty " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Wedding " , " value " : " Wedding " } , { " label " : " Portrait " , " value " : " Portrait " } , { " label " : " Product/Commercial " , " value " : " Product/Commercial " } , { " label " : " Events " , " value " : " Events " } , { " label " : " Real Estate " , " value " : " Real Estate " } , { " label " : " Fashion/Editorial " , " value " : " Fashion/Editorial " } ] } ,
{ " id " : " camera_equipment " , " label " : " Camera Equipment " , " type " : " text " , " required " : true , " placeholder " : " e.g., Canon EOS R5, Sony A7 III " } ,
{ " id " : " editing_software " , " label " : " Editing Software " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Adobe Lightroom " , " value " : " Adobe Lightroom " } , { " label " : " Adobe Photoshop " , " value " : " Adobe Photoshop " } , { " label " : " Capture One " , " value " : " Capture One " } , { " label " : " Other " , " value " : " Other " } ] }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Hourly " , " value " : " Hourly " } , { " label " : " Per Session/Event " , " value " : " Per Session/Event " } , { " label " : " Per Project " , " value " : " Per Project " } , { " label " : " Custom Package " , " value " : " Custom Package " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Weekdays " , " value " : " Weekdays " } , { " label " : " Weekends " , " value " : " Weekends " } , { " label " : " All Days " , " value " : " All Days " } , { " label " : " By Appointment " , " value " : " By Appointment " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Portfolio Images (up to 6) " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload up to 6 images, max 2MB each. Displayed in 3\u00d72 grid. " } ,
{ " id " : " portfolio_url " , " label " : " External Portfolio URL " , " type " : " url " , " required " : false , " placeholder " : " Instagram, website, or Behance link " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your style and best work " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' PHOTOGRAPHER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- MAKEUP_ARTIST (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a makeup artist specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Makeup Specialization " ,
" fields " : [
{ " id " : " makeup_specialty " , " label " : " Makeup Specialty " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Bridal " , " value " : " Bridal " } , { " label " : " Editorial/Fashion " , " value " : " Editorial/Fashion " } , { " label " : " Film/TV " , " value " : " Film/TV " } , { " label " : " Special Effects " , " value " : " Special Effects " } , { " label " : " Party/Events " , " value " : " Party/Events " } ] } ,
{ " id " : " preferred_brands " , " label " : " Preferred Brands " , " type " : " text " , " required " : true , " placeholder " : " e.g., MAC, Huda Beauty, Kryolan " } ,
{ " id " : " services_offered " , " label " : " Services Offered " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Bridal Makeup " , " value " : " Bridal Makeup " } , { " label " : " Party Makeup " , " value " : " Party Makeup " } , { " label " : " Photoshoot Makeup " , " value " : " Photoshoot Makeup " } , { " label " : " Grooming " , " value " : " Grooming " } , { " label " : " Airbrush Makeup " , " value " : " Airbrush Makeup " } ] }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Per Session " , " value " : " Per Session " } , { " label " : " Per Person " , " value " : " Per Person " } , { " label " : " Package-based " , " value " : " Package-based " } , { " label " : " Custom " , " value " : " Custom " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Weekdays " , " value " : " Weekdays " } , { " label " : " Weekends " , " value " : " Weekends " } , { " label " : " All Days " , " value " : " All Days " } , { " label " : " By Appointment " , " value " : " By Appointment " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Portfolio Images (up to 6) " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload up to 6 images, max 2MB each. Displayed in 3\u00d72 grid. " } ,
{ " id " : " portfolio_url " , " label " : " External Portfolio URL " , " type " : " url " , " required " : false , " placeholder " : " Instagram or website link " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your style and best work " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' MAKEUP_ARTIST '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- TUTOR (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a tutor specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Teaching Specialization " ,
" fields " : [
{ " id " : " subjects " , " label " : " Subjects " , " type " : " text " , " required " : true , " placeholder " : " e.g., Mathematics, Physics, Spoken English " } ,
{ " id " : " grade_levels " , " label " : " Grade Levels " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Primary (1-5) " , " value " : " Primary " } , { " label " : " Middle School (6-8) " , " value " : " Middle School " } , { " label " : " High School (9-12) " , " value " : " High School " } , { " label " : " College " , " value " : " College " } , { " label " : " Professional/Adult " , " value " : " Professional " } ] } ,
{ " id " : " teaching_mode " , " label " : " Teaching Mode " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Online " , " value " : " Online " } , { " label " : " Offline (Home visits) " , " value " : " Offline " } , { " label " : " Hybrid " , " value " : " Hybrid " } ] }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Per Hour " , " value " : " Per Hour " } , { " label " : " Per Session " , " value " : " Per Session " } , { " label " : " Monthly Package " , " value " : " Monthly Package " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Weekdays " , " value " : " Weekdays " } , { " label " : " Weekends " , " value " : " Weekends " } , { " label " : " All Days " , " value " : " All Days " } , { " label " : " By Appointment " , " value " : " By Appointment " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Credentials and Work Samples " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Certificates / Work Samples (up to 6) " , " type " : " file " , " required " : false , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload certificates or student work samples, max 2MB each. " } ,
{ " id " : " portfolio_url " , " label " : " Online Profile or Course Link " , " type " : " url " , " required " : false , " placeholder " : " LinkedIn, Vedantu, or website link " } ,
{ " id " : " portfolio_note " , " label " : " Teaching Approach " , " type " : " textarea " , " placeholder " : " Describe your teaching style and methodology " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' TUTOR '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- DEVELOPER (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a developer specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Development Specialization " ,
" fields " : [
{ " id " : " developer_type " , " label " : " Developer Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Frontend " , " value " : " Frontend " } , { " label " : " Backend " , " value " : " Backend " } , { " label " : " Full-stack " , " value " : " Full-stack " } , { " label " : " Mobile (iOS/Android) " , " value " : " Mobile " } , { " label " : " DevOps/Cloud " , " value " : " DevOps " } ] } ,
{ " id " : " tech_stack " , " label " : " Tech Stack " , " type " : " text " , " required " : true , " placeholder " : " e.g., React, Node.js, PostgreSQL, Docker " } ,
{ " id " : " open_source_profile " , " label " : " GitHub / Portfolio URL " , " type " : " url " , " required " : false , " placeholder " : " https://github.com/yourusername " }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Hourly " , " value " : " Hourly " } , { " label " : " Per Project " , " value " : " Per Project " } , { " label " : " Monthly Retainer " , " value " : " Monthly Retainer " } , { " label " : " Custom " , " value " : " Custom " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Full-time " , " value " : " Full-time " } , { " label " : " Part-time " , " value " : " Part-time " } , { " label " : " Weekends Only " , " value " : " Weekends Only " } , { " label " : " Flexible " , " value " : " Flexible " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Screenshots / Work Samples (up to 6) " , " type " : " file " , " required " : false , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload app/website screenshots, max 2MB each. " } ,
{ " id " : " portfolio_url " , " label " : " Live Project / Portfolio URL " , " type " : " url " , " required " : false , " placeholder " : " Deployed project, Behance, or GitHub Pages " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your most impactful projects " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' DEVELOPER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- VIDEO_EDITOR (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a video editor specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Video Editing Specialization " ,
" fields " : [
{ " id " : " editing_software " , " label " : " Primary Editing Software " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Adobe Premiere Pro " , " value " : " Adobe Premiere Pro " } , { " label " : " Final Cut Pro " , " value " : " Final Cut Pro " } , { " label " : " DaVinci Resolve " , " value " : " DaVinci Resolve " } , { " label " : " After Effects " , " value " : " After Effects " } , { " label " : " CapCut " , " value " : " CapCut " } ] } ,
{ " id " : " video_specialties " , " label " : " Video Specialties " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " YouTube Videos " , " value " : " YouTube Videos " } , { " label " : " Instagram Reels/Shorts " , " value " : " Reels/Shorts " } , { " label " : " Wedding Highlights " , " value " : " Wedding Highlights " } , { " label " : " Corporate Promo " , " value " : " Corporate Promo " } , { " label " : " Explainer/Animation " , " value " : " Explainer " } ] } ,
{ " id " : " turnaround_time " , " label " : " Typical Turnaround " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " 24-48 hours " , " value " : " 24-48 hours " } , { " label " : " 3-5 days " , " value " : " 3-5 days " } , { " label " : " 1-2 weeks " , " value " : " 1-2 weeks " } , { " label " : " Depends on project " , " value " : " Depends " } ] }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Per Video " , " value " : " Per Video " } , { " label " : " Per Minute of Output " , " value " : " Per Minute " } , { " label " : " Hourly " , " value " : " Hourly " } , { " label " : " Monthly Retainer " , " value " : " Monthly Retainer " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Full-time " , " value " : " Full-time " } , { " label " : " Part-time " , " value " : " Part-time " } , { " label " : " Weekends Only " , " value " : " Weekends Only " } , { " label " : " Flexible " , " value " : " Flexible " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Work Thumbnails / Stills (up to 6) " , " type " : " file " , " required " : false , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload video thumbnails or stills, max 2MB each. " } ,
{ " id " : " portfolio_url " , " label " : " YouTube / Vimeo Portfolio Link " , " type " : " url " , " required " : true , " placeholder " : " Link to your best video work " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your editing style and best projects " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' VIDEO_EDITOR '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- GRAPHIC_DESIGNER (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a graphic designer specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Design Specialization " ,
" fields " : [
{ " id " : " design_tools " , " label " : " Design Tools " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Figma " , " value " : " Figma " } , { " label " : " Adobe Illustrator " , " value " : " Adobe Illustrator " } , { " label " : " Adobe Photoshop " , " value " : " Adobe Photoshop " } , { " label " : " Canva Pro " , " value " : " Canva Pro " } , { " label " : " Adobe InDesign " , " value " : " Adobe InDesign " } ] } ,
{ " id " : " design_specialties " , " label " : " Design Specialties " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Branding/Logo " , " value " : " Branding/Logo " } , { " label " : " UI/UX Design " , " value " : " UI/UX " } , { " label " : " Print Media " , " value " : " Print Media " } , { " label " : " Social Media Graphics " , " value " : " Social Media " } , { " label " : " Motion Graphics " , " value " : " Motion Graphics " } ] } ,
{ " id " : " style_note " , " label " : " Design Style " , " type " : " text " , " required " : false , " placeholder " : " e.g., Minimalist, Bold, Illustrative " }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Per Design/Asset " , " value " : " Per Design " } , { " label " : " Per Project " , " value " : " Per Project " } , { " label " : " Hourly " , " value " : " Hourly " } , { " label " : " Monthly Retainer " , " value " : " Monthly Retainer " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Full-time " , " value " : " Full-time " } , { " label " : " Part-time " , " value " : " Part-time " } , { " label " : " Weekends Only " , " value " : " Weekends Only " } , { " label " : " Flexible " , " value " : " Flexible " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Design Samples (up to 6) " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload your best design work, max 2MB each. Displayed in 3\u00d72 grid. " } ,
{ " id " : " portfolio_url " , " label " : " Behance / Dribbble / Portfolio URL " , " type " : " url " , " required " : false , " placeholder " : " https://behance.net/yourprofile " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your design philosophy and best projects " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' GRAPHIC_DESIGNER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- SOCIAL_MEDIA_MANAGER (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a social media manager specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Social Media Specialization " ,
" fields " : [
{ " id " : " platforms_managed " , " label " : " Platforms Managed " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Instagram " , " value " : " Instagram " } , { " label " : " LinkedIn " , " value " : " LinkedIn " } , { " label " : " Facebook " , " value " : " Facebook " } , { " label " : " X/Twitter " , " value " : " X/Twitter " } , { " label " : " YouTube " , " value " : " YouTube " } , { " label " : " Pinterest " , " value " : " Pinterest " } ] } ,
{ " id " : " content_types " , " label " : " Content Types " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Graphics/Creatives " , " value " : " Graphics " } , { " label " : " Reels/Short Videos " , " value " : " Reels " } , { " label " : " Copywriting/Captions " , " value " : " Copywriting " } , { " label " : " Stories " , " value " : " Stories " } , { " label " : " Analytics & Reports " , " value " : " Analytics " } ] } ,
{ " id " : " tools_used " , " label " : " Tools Used " , " type " : " text " , " required " : true , " placeholder " : " e.g., Canva, Hootsuite, Buffer, Meta Business Suite " }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Monthly Retainer " , " value " : " Monthly Retainer " } , { " label " : " Per Platform " , " value " : " Per Platform " } , { " label " : " Per Post " , " value " : " Per Post " } , { " label " : " Custom Package " , " value " : " Custom Package " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Full-time " , " value " : " Full-time " } , { " label " : " Part-time " , " value " : " Part-time " } , { " label " : " Flexible " , " value " : " Flexible " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Content Samples (up to 6) " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload posts, stories, or analytics screenshots, max 2MB each. " } ,
{ " id " : " portfolio_url " , " label " : " Sample Brand Account URL " , " type " : " url " , " required " : false , " placeholder " : " Instagram/LinkedIn page you manage " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe brands you have worked with and results achieved " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' SOCIAL_MEDIA_MANAGER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- FITNESS_TRAINER (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name " , " type " : " text " , " required " : true , " placeholder " : " Your full name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Hi, I am a fitness trainer specializing in... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Training Specialization " ,
" fields " : [
{ " id " : " training_specialties " , " label " : " Training Specialties " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Weight Loss " , " value " : " Weight Loss " } , { " label " : " Muscle Gain " , " value " : " Muscle Gain " } , { " label " : " HIIT " , " value " : " HIIT " } , { " label " : " Yoga/Flexibility " , " value " : " Yoga/Flexibility " } , { " label " : " CrossFit " , " value " : " CrossFit " } , { " label " : " Sports-specific " , " value " : " Sports-specific " } , { " label " : " Rehabilitation " , " value " : " Rehabilitation " } ] } ,
{ " id " : " certifications " , " label " : " Certifications " , " type " : " text " , " required " : false , " placeholder " : " e.g., ACE CPT, NASM CPT, RYT-200 " } ,
{ " id " : " training_mode " , " label " : " Training Mode " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Online/Virtual " , " value " : " Online " } , { " label " : " In-person (Client's location) " , " value " : " In-person " } , { " label " : " Both Online and In-person " , " value " : " Both " } ] }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Per Session " , " value " : " Per Session " } , { " label " : " Monthly Package " , " value " : " Monthly Package " } , { " label " : " Quarterly Package " , " value " : " Quarterly Package " } , { " label " : " Custom " , " value " : " Custom " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price in INR " , " validation " : { " min " : 0 } } ,
{ " id " : " availability " , " label " : " Availability " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Early Morning (5-8 AM) " , " value " : " Early Morning " } , { " label " : " Morning (8-12 PM) " , " value " : " Morning " } , { " label " : " Evening (5-9 PM) " , " value " : " Evening " } , { " label " : " Flexible " , " value " : " Flexible " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Transformation Photos / Certificates (up to 6) " , " type " : " file " , " required " : false , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload client transformation photos or certification images, max 2MB each. " } ,
{ " id " : " portfolio_url " , " label " : " Instagram / YouTube Channel " , " type " : " url " , " required " : false , " placeholder " : " Your fitness social media or YouTube link " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your training philosophy and client success stories " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' FITNESS_TRAINER '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- CATERING_SERVICES (6 steps)
INSERT INTO onboarding_configs ( role_id , schema_json , version , is_active )
SELECT id , $ json $ {
" steps " : [
{
" id " : " step_1_profile " ,
" title " : " Profile Details " ,
" fields " : [
{ " id " : " full_name " , " label " : " Full Name / Business Name " , " type " : " text " , " required " : true , " placeholder " : " Your name or catering business name " } ,
{ " id " : " experience " , " label " : " Experience (Years) " , " type " : " number " , " required " : true , " validation " : { " min " : 0 } } ,
{ " id " : " bio " , " label " : " Bio " , " type " : " textarea " , " required " : true , " placeholder " : " Tell us about your catering business and specialties... " }
]
} ,
{
" id " : " step_2_contact " ,
" title " : " Contact and Location " ,
" fields " : [
{ " id " : " email " , " label " : " Email " , " type " : " email " , " required " : true } ,
{ " id " : " phone " , " label " : " Phone Number " , " type " : " tel " , " required " : true , " placeholder " : " 10-digit mobile number " , " validation " : { " pattern " : " ^[0-9]{10}$ " , " minLength " : 10 , " maxLength " : 10 } } ,
{ " id " : " city " , " label " : " City " , " type " : " text " , " required " : true , " readOnly " : true , " defaultValue " : " Chennai, India " }
]
} ,
{
" id " : " step_3_specialization " ,
" title " : " Catering Specialization " ,
" fields " : [
{ " id " : " cuisine_types " , " label " : " Cuisine Types " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " South Indian " , " value " : " South Indian " } , { " label " : " North Indian " , " value " : " North Indian " } , { " label " : " Continental " , " value " : " Continental " } , { " label " : " Chinese " , " value " : " Chinese " } , { " label " : " Fusion " , " value " : " Fusion " } , { " label " : " Biryani/Mughlai " , " value " : " Biryani/Mughlai " } ] } ,
{ " id " : " dietary_options " , " label " : " Dietary Options " , " type " : " select " , " required " : true , " multiple " : true ,
" options " : [ { " label " : " Pure Veg " , " value " : " Pure Veg " } , { " label " : " Non-Veg " , " value " : " Non-Veg " } , { " label " : " Vegan " , " value " : " Vegan " } , { " label " : " Jain " , " value " : " Jain " } ] } ,
{ " id " : " max_capacity " , " label " : " Max Capacity (Plates/Guests) " , " type " : " number " , " required " : true , " placeholder " : " Maximum plates per event " , " validation " : { " min " : 1 } }
]
} ,
{
" id " : " step_4_pricing " ,
" title " : " Pricing and Availability " ,
" fields " : [
{ " id " : " pricing_model " , " label " : " Pricing Model " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Per Plate " , " value " : " Per Plate " } , { " label " : " Per Event " , " value " : " Per Event " } , { " label " : " Package-based " , " value " : " Package-based " } , { " label " : " Custom Quote " , " value " : " Custom Quote " } ] } ,
{ " id " : " base_rate " , " label " : " Base Rate (\u20b9) " , " type " : " number " , " required " : true , " placeholder " : " Starting price (e.g., per plate or per event) " , " validation " : { " min " : 0 } } ,
{ " id " : " advance_notice " , " label " : " Advance Notice Required " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " 24 hours " , " value " : " 24 hours " } , { " label " : " 2-3 days " , " value " : " 2-3 days " } , { " label " : " 1 week " , " value " : " 1 week " } , { " label " : " 2+ weeks " , " value " : " 2+ weeks " } ] }
]
} ,
{
" id " : " step_5_portfolio " ,
" title " : " Portfolio " ,
" fields " : [
{ " id " : " portfolio_images " , " label " : " Food / Event Photos (up to 6) " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 6 , " accept " : " image/jpeg,image/jpg,image/png,image/webp " , " maxSizeMB " : 2 , " helperText " : " Upload your best food and event photos, max 2MB each. Displayed in 3\u00d72 grid. " } ,
{ " id " : " portfolio_url " , " label " : " Instagram / Google Business URL " , " type " : " url " , " required " : false , " placeholder " : " Your food page or Google Business listing " } ,
{ " id " : " portfolio_note " , " label " : " Portfolio Note " , " type " : " textarea " , " placeholder " : " Describe your specialty dishes and memorable events you have catered " }
]
} ,
{
" id " : " step_6_verification " ,
" title " : " Identity Verification " ,
" fields " : [
{ " id " : " id_type " , " label " : " ID Type " , " type " : " select " , " required " : true ,
" options " : [ { " label " : " Aadhaar Card " , " value " : " Aadhaar Card " } , { " label " : " PAN Card " , " value " : " PAN Card " } , { " label " : " Driving License " , " value " : " Driving License " } , { " label " : " Voter ID " , " value " : " Voter ID " } , { " label " : " Passport " , " value " : " Passport " } ] } ,
{ " id " : " id_number " , " label " : " ID Number " , " type " : " text " , " required " : true , " placeholder " : " Enter ID Number " } ,
{ " id " : " id_document_upload " , " label " : " Upload ID Document " , " type " : " file " , " required " : true , " multiple " : true , " maxFiles " : 2 , " accept " : " application/pdf,image/jpeg,image/jpg,image/png " , " maxSizeMB " : 2 }
]
}
]
} $ json $ : : jsonb , 2 , true
FROM roles WHERE key = ' CATERING_SERVICES '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id ) WHERE is_active DO UPDATE SET schema_json = EXCLUDED . schema_json , version = EXCLUDED . version ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- ── 4. Default Dashboard Configs ─────────────────────────────────────────────
INSERT INTO dashboard_configs ( role_id , audience , config_json , version , is_active )
SELECT r . id ,
' EXTERNAL ' ,
jsonb_build_object (
' nav ' , CASE r . key
WHEN ' COMPANY ' THEN ' [
{ " key " : " jobs " , " label " : " My Jobs " , " path " : " /dashboard/jobs " , " icon " : " briefcase " } ,
{ " key " : " applications " , " label " : " Applications " , " path " : " /dashboard/applications " , " icon " : " users " } ,
{ " key " : " profile " , " label " : " Company Profile " , " path " : " /dashboard/profile " , " icon " : " building " }
] ' ::jsonb
WHEN ' JOB_SEEKER ' THEN ' [
{ " key " : " browse_jobs " , " label " : " Browse Jobs " , " path " : " /dashboard/jobs " , " icon " : " search " } ,
{ " key " : " my_applications " , " label " : " My Applications " , " path " : " /dashboard/applications " , " icon " : " file-text " } ,
{ " key " : " profile " , " label " : " My Profile " , " path " : " /dashboard/profile " , " icon " : " user " }
] ' ::jsonb
WHEN ' CUSTOMER ' THEN ' [
{ " key " : " requirements " , " label " : " My Requirements " , " path " : " /dashboard/requirements " , " icon " : " list " } ,
{ " key " : " profile " , " label " : " My Profile " , " path " : " /dashboard/profile " , " icon " : " user " }
] ' ::jsonb
ELSE ' [
{ " key " : " marketplace " , " label " : " Marketplace " , " path " : " /dashboard/marketplace " , " icon " : " store " } ,
{ " key " : " leads " , " label " : " My Leads " , " path " : " /dashboard/leads " , " icon " : " zap " } ,
{ " key " : " portfolio " , " label " : " Portfolio " , " path " : " /dashboard/portfolio " , " icon " : " image " } ,
{ " key " : " services " , " label " : " Services " , " path " : " /dashboard/services " , " icon " : " list " } ,
{ " key " : " wallet " , " label " : " Wallet " , " path " : " /dashboard/wallet " , " icon " : " wallet " } ,
{ " key " : " profile " , " label " : " My Profile " , " path " : " /dashboard/profile " , " icon " : " user " }
] ' ::jsonb
END ,
' enabled_modules ' , CASE r . key
WHEN ' COMPANY ' THEN ' ["jobs", "applications", "profile"] ' : : jsonb
WHEN ' JOB_SEEKER ' THEN ' ["browse_jobs", "my_applications", "profile"] ' : : jsonb
WHEN ' CUSTOMER ' THEN ' ["requirements", "profile"] ' : : jsonb
ELSE ' ["marketplace", "leads", "portfolio", "services", "wallet", "profile"] ' : : jsonb
END
) ,
1 ,
true
FROM roles r
WHERE r . audience = ' EXTERNAL '
2026-03-21 15:17:59 +01:00
ON CONFLICT ( role_id , audience ) WHERE is_active DO NOTHING ;
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
-- ── Done ──────────────────────────────────────────────────────────────────────
SELECT ' Seed completed successfully. ' AS status ;