import { PrismaClient, UserRole, UserStatus, AuthProvider, FieldType } from '@prisma/client'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; import * as argon2 from 'argon2'; async function main() { const connectionString = process.env.DATABASE_URL; const pool = new Pool({ connectionString }); const adapter = new PrismaPg(pool); const prisma = new PrismaClient({ adapter }); console.log('🌱 Starting database seeding...\n'); // ============================================= // Seed Agent Types // ============================================= console.log('šŸ“‹ Seeding Agent Types...'); const agentTypes = [ { name: 'Professional', description: 'Licensed real estate professionals', icon: 'briefcase', sortOrder: 1, }, { name: 'Lender', description: 'Mortgage and loan specialists', icon: 'bank', sortOrder: 2, }, ]; for (const agentType of agentTypes) { const existing = await prisma.agentType.findUnique({ where: { name: agentType.name }, }); if (existing) { console.log(` āš ļø Agent type "${agentType.name}" already exists`); } else { await prisma.agentType.create({ data: agentType, }); console.log(` āœ… Created agent type: ${agentType.name}`); } } console.log(''); // ============================================= // Seed Default Profile Sections & Fields // ============================================= console.log('šŸ“‹ Seeding Default Profile Sections...'); // Define default system sections with their fields const defaultSections = [ { name: 'Basic Information', slug: 'basic-information', description: 'Basic profile information', icon: 'user', sortOrder: 1, isActive: true, isGlobal: true, isSystem: true, fields: [ { name: 'First Name', slug: 'first_name', fieldType: FieldType.TEXT, description: 'Your first name', placeholder: 'Enter your first name', sortOrder: 1, isActive: true, isRequired: true, isSearchableOnly: false, }, { name: 'Last Name', slug: 'last_name', fieldType: FieldType.TEXT, description: 'Your last name', placeholder: 'Enter your last name', sortOrder: 2, isActive: true, isRequired: true, isSearchableOnly: false, }, { name: 'Description', slug: 'description', fieldType: FieldType.TEXTAREA, description: 'Tell us about yourself and your professional background', placeholder: 'Enter a brief description about yourself, your experience, and what makes you unique...', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, validation: { maxLength: 2000, }, uiConfig: { rows: 6, showCharCount: true, }, }, ], }, { name: 'Contact Information', slug: 'contact-information', description: 'Contact details', icon: 'phone', sortOrder: 2, isActive: true, isGlobal: true, isSystem: true, fields: [ { name: 'Email Address', slug: 'email_address', fieldType: FieldType.TEXT, description: 'Your contact email address', placeholder: 'Enter your email address', sortOrder: 1, isActive: true, isRequired: true, isSearchableOnly: false, }, { name: 'Phone Number', slug: 'phone_number', fieldType: FieldType.TEXT, description: 'Your contact phone number', placeholder: 'Enter your phone number', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, }, ], }, { name: 'Location', slug: 'location', description: 'Service areas and location information', icon: 'map-pin', sortOrder: 3, isActive: true, isGlobal: true, isSystem: true, fields: [ { name: 'State', slug: 'state', fieldType: FieldType.CHECKBOX_GROUP, description: 'States where you provide services', placeholder: 'Select states', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Alabama', value: 'AL' }, { label: 'Alaska', value: 'AK' }, { label: 'Arizona', value: 'AZ' }, { label: 'Arkansas', value: 'AR' }, { label: 'California', value: 'CA' }, { label: 'Colorado', value: 'CO' }, { label: 'Connecticut', value: 'CT' }, { label: 'Delaware', value: 'DE' }, { label: 'Florida', value: 'FL' }, { label: 'Georgia', value: 'GA' }, { label: 'Hawaii', value: 'HI' }, { label: 'Idaho', value: 'ID' }, { label: 'Illinois', value: 'IL' }, { label: 'Indiana', value: 'IN' }, { label: 'Iowa', value: 'IA' }, { label: 'Kansas', value: 'KS' }, { label: 'Kentucky', value: 'KY' }, { label: 'Louisiana', value: 'LA' }, { label: 'Maine', value: 'ME' }, { label: 'Maryland', value: 'MD' }, { label: 'Massachusetts', value: 'MA' }, { label: 'Michigan', value: 'MI' }, { label: 'Minnesota', value: 'MN' }, { label: 'Mississippi', value: 'MS' }, { label: 'Missouri', value: 'MO' }, { label: 'Montana', value: 'MT' }, { label: 'Nebraska', value: 'NE' }, { label: 'Nevada', value: 'NV' }, { label: 'New Hampshire', value: 'NH' }, { label: 'New Jersey', value: 'NJ' }, { label: 'New Mexico', value: 'NM' }, { label: 'New York', value: 'NY' }, { label: 'North Carolina', value: 'NC' }, { label: 'North Dakota', value: 'ND' }, { label: 'Ohio', value: 'OH' }, { label: 'Oklahoma', value: 'OK' }, { label: 'Oregon', value: 'OR' }, { label: 'Pennsylvania', value: 'PA' }, { label: 'Rhode Island', value: 'RI' }, { label: 'South Carolina', value: 'SC' }, { label: 'South Dakota', value: 'SD' }, { label: 'Tennessee', value: 'TN' }, { label: 'Texas', value: 'TX' }, { label: 'Utah', value: 'UT' }, { label: 'Vermont', value: 'VT' }, { label: 'Virginia', value: 'VA' }, { label: 'Washington', value: 'WA' }, { label: 'West Virginia', value: 'WV' }, { label: 'Wisconsin', value: 'WI' }, { label: 'Wyoming', value: 'WY' }, ], }, { name: 'City', slug: 'city', fieldType: FieldType.CHECKBOX_GROUP, description: 'Cities where you provide services (max 5)', placeholder: 'Enter cities', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, validation: { maxSelections: 5, }, uiConfig: { allowCustomOptions: true, maxSelections: 5, }, options: [ { label: 'New York', value: 'new_york' }, { label: 'Los Angeles', value: 'los_angeles' }, { label: 'Chicago', value: 'chicago' }, { label: 'Houston', value: 'houston' }, { label: 'Phoenix', value: 'phoenix' }, { label: 'Philadelphia', value: 'philadelphia' }, { label: 'San Antonio', value: 'san_antonio' }, { label: 'San Diego', value: 'san_diego' }, { label: 'Dallas', value: 'dallas' }, { label: 'San Jose', value: 'san_jose' }, { label: 'Austin', value: 'austin' }, { label: 'Jacksonville', value: 'jacksonville' }, { label: 'Fort Worth', value: 'fort_worth' }, { label: 'Columbus', value: 'columbus' }, { label: 'Charlotte', value: 'charlotte' }, { label: 'San Francisco', value: 'san_francisco' }, { label: 'Indianapolis', value: 'indianapolis' }, { label: 'Seattle', value: 'seattle' }, { label: 'Denver', value: 'denver' }, { label: 'Washington DC', value: 'washington_dc' }, { label: 'Boston', value: 'boston' }, { label: 'Nashville', value: 'nashville' }, { label: 'Detroit', value: 'detroit' }, { label: 'Portland', value: 'portland' }, { label: 'Las Vegas', value: 'las_vegas' }, { label: 'Memphis', value: 'memphis' }, { label: 'Louisville', value: 'louisville' }, { label: 'Baltimore', value: 'baltimore' }, { label: 'Milwaukee', value: 'milwaukee' }, { label: 'Albuquerque', value: 'albuquerque' }, { label: 'Tucson', value: 'tucson' }, { label: 'Fresno', value: 'fresno' }, { label: 'Sacramento', value: 'sacramento' }, { label: 'Atlanta', value: 'atlanta' }, { label: 'Miami', value: 'miami' }, { label: 'Oakland', value: 'oakland' }, { label: 'Minneapolis', value: 'minneapolis' }, { label: 'Cleveland', value: 'cleveland' }, { label: 'Raleigh', value: 'raleigh' }, { label: 'Tampa', value: 'tampa' }, ], }, ], }, { name: 'Upload Documents', slug: 'upload-documents', description: 'Upload licensing credentials and verification documents', icon: 'document', sortOrder: 4, isActive: true, isGlobal: true, isSystem: true, fields: [ { name: 'Documents', slug: 'documents', fieldType: FieldType.FILE, description: 'Upload documentation or verification for licensing credentials', placeholder: 'Drag and drop files here or click to upload', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, validation: { allowedTypes: ['pdf', 'doc', 'docx', 'jpg', 'jpeg', 'png'], maxFileSize: 10485760, // 10MB maxFiles: 10, }, uiConfig: { acceptedFormats: '.pdf, .doc, .docx, .jpg, .png', showPreview: true, multiple: true, }, }, ], }, { name: 'Certification', slug: 'certification', description: 'Professional certifications and credentials', icon: 'star', sortOrder: 5, isActive: true, isGlobal: true, isSystem: true, isRepeatable: true, // User can add multiple certifications fields: [ { name: 'Certification Entries', slug: 'certification_entries', fieldType: FieldType.REPEATER, description: 'All certification entries stored as JSON array', sortOrder: 0, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Certification Name', slug: 'certification_name', fieldType: FieldType.TEXT, description: 'Name of your professional certification', placeholder: 'Enter certification name', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Issuing Organization', slug: 'issuing_organization', fieldType: FieldType.TEXT, description: 'Organization that issued the certification', placeholder: 'Enter issuing organization', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Issue Date', slug: 'issue_date', fieldType: FieldType.DATE, description: 'Date the certification was issued', placeholder: 'Select issue date', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Expiration Date', slug: 'expiration_date', fieldType: FieldType.DATE, description: 'Date the certification expires (if applicable)', placeholder: 'Select expiration date', sortOrder: 4, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Certification Number', slug: 'certification_number', fieldType: FieldType.TEXT, description: 'Your certification or license number', placeholder: 'Enter certification number', sortOrder: 5, isActive: true, isRequired: false, isSearchableOnly: false, }, ], }, { name: 'Demographic', slug: 'demographic', description: 'Demographic and contact information', icon: 'user', sortOrder: 6, isActive: true, isGlobal: true, isSystem: true, fields: [ { name: 'Name', slug: 'demographic_name', fieldType: FieldType.TEXT, description: 'Your full name', placeholder: 'Enter your name', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Office Name', slug: 'office_name', fieldType: FieldType.TEXT, description: 'Name of your office or brokerage', placeholder: 'Enter office name', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Registered / Licensed', slug: 'registered_licensed', fieldType: FieldType.RADIO, description: 'Are you registered or licensed?', placeholder: 'Select status', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Registered', value: 'registered' }, { label: 'Licensed', value: 'licensed' }, { label: 'Both', value: 'both' }, ], }, { name: 'Office #', slug: 'office_number', fieldType: FieldType.TEXT, description: 'Your office phone number', placeholder: 'Enter office number', sortOrder: 4, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Cell #', slug: 'cell_number', fieldType: FieldType.TEXT, description: 'Your cell phone number', placeholder: 'Enter cell number', sortOrder: 5, isActive: true, isRequired: false, isSearchableOnly: false, }, { name: 'Availability', slug: 'availability', fieldType: FieldType.CHECKBOX_GROUP, description: 'When are you available?', placeholder: 'Select availability', sortOrder: 6, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'M-F 9-5', value: 'mf_9_5' }, { label: 'Evenings', value: 'evenings' }, { label: 'Weekends', value: 'weekends' }, ], }, { name: 'Age', slug: 'age', fieldType: FieldType.RANGE, description: 'Your age (used for search matching only, not displayed on profile)', placeholder: 'Select age range', sortOrder: 7, isActive: true, isRequired: false, isSearchableOnly: true, rangeConfig: { min: 18, max: 100, step: 1, }, }, { name: 'Gender', slug: 'gender', fieldType: FieldType.TEXT, description: 'Your gender (used for search matching only, not displayed on profile)', placeholder: 'Enter gender', sortOrder: 8, isActive: true, isRequired: false, isSearchableOnly: true, }, { name: 'Race', slug: 'race', fieldType: FieldType.CHECKBOX_GROUP, description: 'Your race (used for search matching only, not displayed on profile)', placeholder: 'Select race', sortOrder: 9, isActive: true, isRequired: false, isSearchableOnly: true, options: [ { label: 'American Indian or Alaska Native', value: 'american_indian_alaska_native' }, { label: 'Asian', value: 'asian' }, { label: 'Black or African American', value: 'black_african_american' }, { label: 'Native Hawaiian or Other Pacific Islander', value: 'native_hawaiian_pacific_islander' }, { label: 'White', value: 'white' }, { label: 'Two or More Races', value: 'two_or_more' }, { label: 'Prefer not to say', value: 'prefer_not_to_say' }, ], }, { name: 'Ethnicity', slug: 'ethnicity', fieldType: FieldType.CHECKBOX_GROUP, description: 'Your ethnicity (used for search matching only, not displayed on profile)', placeholder: 'Select ethnicity', sortOrder: 10, isActive: true, isRequired: false, isSearchableOnly: true, options: [ { label: 'Hispanic or Latino', value: 'hispanic_latino' }, { label: 'Not Hispanic or Latino', value: 'not_hispanic_latino' }, { label: 'Prefer not to say', value: 'prefer_not_to_say' }, ], }, { name: 'Language', slug: 'language', fieldType: FieldType.TAG_INPUT, description: 'Languages you speak', placeholder: 'Add languages', sortOrder: 11, isActive: true, isRequired: false, isSearchableOnly: false, uiConfig: { allowCustomOptions: true, suggestions: ['English', 'Spanish', 'Chinese', 'French', 'Vietnamese', 'Korean', 'Tagalog', 'Arabic', 'Russian', 'Portuguese'], }, }, { name: 'LGBTQ+', slug: 'lgbtq', fieldType: FieldType.RADIO, description: 'LGBTQ+ identification (used for search matching only, not displayed on profile)', placeholder: 'Select option', sortOrder: 12, isActive: true, isRequired: false, isSearchableOnly: true, options: [ { label: 'Yes', value: 'yes' }, { label: 'No', value: 'no' }, { label: 'Prefer not to say', value: 'prefer_not_to_say' }, ], }, { name: 'Highest Education Level', slug: 'highest_education_level', fieldType: FieldType.RADIO, description: 'Your highest level of education', placeholder: 'Select education level', sortOrder: 13, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'High School/GED', value: 'high_school_ged' }, { label: 'Some College', value: 'some_college' }, { label: 'Associates', value: 'associates' }, { label: 'Bachelors', value: 'bachelors' }, { label: 'Masters', value: 'masters' }, { label: 'Doctoral', value: 'doctoral' }, ], }, { name: 'Veteran Status', slug: 'veteran_status', fieldType: FieldType.CHECKBOX_GROUP, description: 'Your military/veteran status', placeholder: 'Select veteran status', sortOrder: 14, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Active', value: 'active' }, { label: 'Reserve', value: 'reserve' }, { label: 'Guard', value: 'guard' }, { label: 'Veteran', value: 'veteran' }, ], }, { name: 'Military Branch', slug: 'military_branch', fieldType: FieldType.CHECKBOX_GROUP, description: 'Military branch(es) you served in', placeholder: 'Select military branch', sortOrder: 15, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Army', value: 'army' }, { label: 'Navy', value: 'navy' }, { label: 'Marines', value: 'marines' }, { label: 'Air Force', value: 'air_force' }, { label: 'Space Force', value: 'space_force' }, { label: 'Coast Guard', value: 'coast_guard' }, ], }, ], }, { name: 'Expertise', slug: 'expertise', description: 'Areas of expertise and specialization', icon: 'star', sortOrder: 7, isActive: true, isGlobal: true, isSystem: true, fields: [ { name: 'About Me', slug: 'about_me_expertise', fieldType: FieldType.TAG_INPUT, description: 'Add your areas of expertise', placeholder: 'Add Expertise', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, uiConfig: { allowCustomOptions: true, }, }, ], }, ]; // Agent-type-specific sections (NOT global) const agentTypeSections = [ { agentTypeName: 'Professional', section: { name: 'Experience', slug: 'professional-experience', description: 'Professional experience and track record for real estate agents', icon: 'star', sortOrder: 4, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Years in Business', slug: 'years_in_business', fieldType: FieldType.RADIO, description: 'How many years have you been in real estate?', placeholder: 'Select years', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Less than 2 years', value: '<2' }, { label: '2+ years', value: '2+' }, { label: '5+ years', value: '5+' }, { label: '10+ years', value: '10+' }, ], }, { name: 'Years Operated in Area', slug: 'years_in_area', fieldType: FieldType.RADIO, description: 'How many years have you operated in your service area?', placeholder: 'Select years', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Less than 2 years', value: '<2' }, { label: '2+ years', value: '2+' }, { label: '5+ years', value: '5+' }, { label: '10+ years', value: '10+' }, ], }, { name: 'Number of Contracts Completed', slug: 'contracts_completed', fieldType: FieldType.RADIO, description: 'Number of contracts completed in the previous year', placeholder: 'Select range', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Less than 3', value: '<3' }, { label: '3-10', value: '3-10' }, { label: '10-20', value: '10-20' }, { label: '20-50', value: '20-50' }, { label: '50+', value: '50+' }, ], }, ], }, }, { agentTypeName: 'Professional', section: { name: 'Specialization', slug: 'professional-specialization', description: 'Areas of specialization for real estate agents', icon: 'briefcase', sortOrder: 5, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Client Specialization', slug: 'client_specialization', fieldType: FieldType.CHECKBOX_GROUP, description: 'Types of clients you specialize in', placeholder: 'Select client types', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Buyers', value: 'buyers' }, { label: 'Sellers', value: 'sellers' }, { label: 'First Time Buyer', value: 'first_time_buyer' }, { label: 'First Time Seller', value: 'first_time_seller' }, { label: 'Divorce', value: 'divorce' }, { label: 'Estate', value: 'estate' }, ], }, { name: 'Price Point', slug: 'price_point', fieldType: FieldType.CHECKBOX_GROUP, description: 'Price ranges you typically work with', placeholder: 'Select price ranges', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Under $100K', value: '<100K' }, { label: '$100K - $300K', value: '100-300K' }, { label: '$300K - $500K', value: '300-500K' }, { label: '$500K - $700K', value: '500-700K' }, { label: '$700K - $1M', value: '700K-1M' }, { label: '$1M+', value: '1M+' }, ], }, { name: 'Property Type', slug: 'property_type', fieldType: FieldType.CHECKBOX_GROUP, description: 'Types of properties you specialize in', placeholder: 'Select property types', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'SFR (Single Family Residence)', value: 'sfr' }, { label: 'Townhome', value: 'townhome' }, { label: 'Condo', value: 'condo' }, { label: 'Manufactured', value: 'manufactured' }, { label: 'Modular', value: 'modular' }, { label: 'Urban', value: 'urban' }, { label: 'Rural', value: 'rural' }, { label: 'Luxury', value: 'luxury' }, { label: 'Retirement', value: 'retirement' }, { label: 'Agriculture', value: 'agriculture' }, { label: 'Commercial', value: 'commercial' }, { label: 'Investment / Income Producing', value: 'investment' }, { label: 'Historical', value: 'historical' }, { label: 'Mountain Property', value: 'mountain' }, { label: 'Undeveloped Land', value: 'undeveloped_land' }, { label: 'Empty Lots', value: 'empty_lots' }, { label: 'Barndaminium', value: 'barndaminium' }, { label: 'Unique Properties', value: 'unique' }, { label: 'Apartment / Multifamily', value: 'apartment_multifamily' }, { label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' }, { label: 'New Build', value: 'new_build' }, ], }, { name: 'Loan Type', slug: 'loan_type', fieldType: FieldType.CHECKBOX_GROUP, description: 'Loan types you are familiar with', placeholder: 'Select loan types', sortOrder: 4, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Conventional', value: 'conventional' }, { label: 'FHA', value: 'fha' }, { label: 'VA', value: 'va' }, { label: 'USDA', value: 'usda' }, { label: 'Downpayment Assistance Programs', value: 'downpayment_assistance' }, ], }, { name: 'Special Interests and Hobbies', slug: 'special_interests', fieldType: FieldType.CHECKBOX_GROUP, description: 'Special property features you have expertise in', placeholder: 'Select special interests', sortOrder: 5, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Workshop / Trade / Craftspace', value: 'workshop' }, { label: 'Boating', value: 'boating' }, { label: 'Automotive', value: 'automotive' }, { label: 'Aviation', value: 'aviation' }, { label: 'Home Theater', value: 'home_theater' }, { label: 'Horses', value: 'horses' }, { label: 'RV', value: 'rv' }, { label: 'Smart Home Features', value: 'smart_home' }, { label: 'Gardening / Landscape', value: 'gardening' }, { label: 'Hobby Farm', value: 'hobby_farm' }, ], }, ], }, }, // Professional - Areas in Expertise & Years Section { agentTypeName: 'Professional', section: { name: 'Areas in Expertise & Years', slug: 'professional-expertise-years', description: 'Your areas of expertise with years of experience', icon: 'award', sortOrder: 6, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Expertise Areas', slug: 'expertise_areas', fieldType: FieldType.TAG_INPUT, description: 'Add your areas of expertise with years of experience', placeholder: '', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, }, ], }, }, // Professional - Licensing & Areas Section { agentTypeName: 'Professional', section: { name: 'Licensing & Areas', slug: 'professional-licensing-areas', description: 'Areas where you are licensed to practice', icon: 'file-text', sortOrder: 7, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Licensed Areas', slug: 'licensed_areas', fieldType: FieldType.TAG_INPUT, description: 'Enter areas where you are licensed to practice', placeholder: 'Type area name, press Enter to add', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, }, ], }, }, { agentTypeName: 'Lender', section: { name: 'Experience', slug: 'lender-experience', description: 'Professional experience and track record for lenders', icon: 'star', sortOrder: 4, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Years in Business', slug: 'years_in_business', fieldType: FieldType.RADIO, description: 'How many years have you been in lending?', placeholder: 'Select years', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Less than 2 years', value: '<2' }, { label: '2+ years', value: '2+' }, { label: '5+ years', value: '5+' }, { label: '10+ years', value: '10+' }, ], }, { name: 'Years Operated in Area', slug: 'years_in_area', fieldType: FieldType.RADIO, description: 'How many years have you operated in your service area?', placeholder: 'Select years', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Less than 2 years', value: '<2' }, { label: '2+ years', value: '2+' }, { label: '5+ years', value: '5+' }, { label: '10+ years', value: '10+' }, ], }, { name: 'Annual Loans', slug: 'annual_loans', fieldType: FieldType.RADIO, description: 'Total value of loans processed annually', placeholder: 'Select range', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: '0-1M', value: '0-1M' }, { label: '1-5M', value: '1-5M' }, { label: '5-10M', value: '5-10M' }, { label: '10-30M', value: '10-30M' }, { label: '30M+', value: '30M+' }, ], }, ], }, }, { agentTypeName: 'Lender', section: { name: 'Specialization', slug: 'lender-specialization', description: 'Areas of specialization for lenders', icon: 'briefcase', sortOrder: 5, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Client Specialization', slug: 'client_specialization', fieldType: FieldType.CHECKBOX_GROUP, description: 'Types of clients you specialize in', placeholder: 'Select client types', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'First Time Buyer', value: 'first_time_buyer' }, ], }, { name: 'Property Type', slug: 'property_type', fieldType: FieldType.CHECKBOX_GROUP, description: 'Types of properties you can finance', placeholder: 'Select property types', sortOrder: 2, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'SFR (Single Family Residence)', value: 'sfr' }, { label: 'Townhome', value: 'townhome' }, { label: 'Condo', value: 'condo' }, { label: 'Manufactured', value: 'manufactured' }, { label: 'Modular', value: 'modular' }, { label: 'Urban', value: 'urban' }, { label: 'Rural', value: 'rural' }, { label: 'Luxury', value: 'luxury' }, { label: 'Retirement', value: 'retirement' }, { label: 'Agriculture', value: 'agriculture' }, { label: 'Commercial', value: 'commercial' }, { label: 'Investment / Income Producing', value: 'investment' }, { label: 'Undeveloped Land', value: 'undeveloped_land' }, { label: 'Construction / Construction-to-Perm', value: 'construction' }, { label: 'Empty Lots', value: 'empty_lots' }, { label: 'Barndaminium', value: 'barndaminium' }, { label: 'Unique Properties', value: 'unique' }, { label: 'Apartment / Multifamily', value: 'apartment_multifamily' }, { label: 'Duplex / Tri-plex / 4-plex', value: 'multiplex' }, { label: 'New Build', value: 'new_build' }, ], }, { name: 'Loan Type', slug: 'loan_type', fieldType: FieldType.CHECKBOX_GROUP, description: 'Types of loans you offer', placeholder: 'Select loan types', sortOrder: 3, isActive: true, isRequired: false, isSearchableOnly: false, options: [ { label: 'Conventional', value: 'conventional' }, { label: 'FHA', value: 'fha' }, { label: 'VA', value: 'va' }, { label: 'USDA', value: 'usda' }, { label: 'Downpayment Assistance Programs', value: 'downpayment_assistance' }, { label: 'Rehab Loan - FHA 203k', value: 'rehab_fha_203k' }, { label: 'Rehab Loan - Conventional Home Style', value: 'rehab_conventional' }, { label: 'Rehab Loan - VA Reno', value: 'rehab_va_reno' }, { label: 'Debt Service Cover Ratio (DSCR)', value: 'dscr' }, { label: 'HELOC', value: 'heloc' }, { label: 'Standalone Second', value: 'standalone_second' }, { label: 'Refinancing - FHA Streamline', value: 'refi_fha_streamline' }, { label: 'Refinancing - VA IRRL', value: 'refi_va_irrl' }, { label: 'Refinancing - Conventional', value: 'refi_conventional' }, { label: 'Jumbo / Non-conforming', value: 'jumbo' }, { label: 'Bank Statement', value: 'bank_statement' }, { label: 'ITIN', value: 'itin' }, ], }, ], }, }, // Lender - Areas in Expertise & Years Section { agentTypeName: 'Lender', section: { name: 'Areas in Expertise & Years', slug: 'lender-expertise-years', description: 'Your areas of expertise with years of experience', icon: 'award', sortOrder: 6, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Expertise Areas', slug: 'expertise_areas', fieldType: FieldType.TAG_INPUT, description: 'Add your areas of expertise with years of experience', placeholder: '', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, }, ], }, }, // Lender - Licensing & Areas Section { agentTypeName: 'Lender', section: { name: 'Licensing & Areas', slug: 'lender-licensing-areas', description: 'Areas where you are licensed to practice', icon: 'file-text', sortOrder: 7, isActive: true, isGlobal: false, isSystem: false, fields: [ { name: 'Licensed Areas', slug: 'licensed_areas', fieldType: FieldType.TAG_INPUT, description: 'Enter areas where you are licensed to practice', placeholder: 'Type area name, press Enter to add', sortOrder: 1, isActive: true, isRequired: false, isSearchableOnly: false, }, ], }, }, ]; for (const sectionData of defaultSections) { const { fields, ...sectionInfo } = sectionData; // Check if section already exists const existingSection = await prisma.profileSection.findUnique({ where: { slug: sectionInfo.slug }, }); if (existingSection) { console.log(` āš ļø Section "${sectionInfo.name}" already exists`); // Update section properties if they've changed (like isRepeatable) if (sectionInfo.isRepeatable !== undefined && existingSection.isRepeatable !== sectionInfo.isRepeatable) { await prisma.profileSection.update({ where: { id: existingSection.id }, data: { isRepeatable: sectionInfo.isRepeatable }, }); console.log(` āœ… Updated isRepeatable to ${sectionInfo.isRepeatable}`); } // Still check and create missing fields, or update existing fields with options for (const fieldData of fields) { const existingField = await prisma.profileField.findFirst({ where: { sectionId: existingSection.id, slug: fieldData.slug, }, }); if (!existingField) { await prisma.profileField.create({ data: { ...fieldData, sectionId: existingSection.id, }, }); console.log(` āœ… Created field: ${fieldData.name}`); } else { // Update existing field with options if options are defined in seed but missing in DB const updates: Record = {}; const fd = fieldData as any; // Cast to any to access optional properties if (fd.options && (!existingField.options || (Array.isArray(existingField.options) && existingField.options.length === 0))) { updates.options = fd.options; } if (fd.validation && !existingField.validation) { updates.validation = fd.validation; } if (fd.uiConfig && !existingField.uiConfig) { updates.uiConfig = fd.uiConfig; } if (Object.keys(updates).length > 0) { await prisma.profileField.update({ where: { id: existingField.id }, data: updates, }); console.log(` āœ… Updated field: ${fieldData.name} (${Object.keys(updates).join(', ')})`); } } } } else { // Create section with fields const section = await prisma.profileSection.create({ data: { ...sectionInfo, fields: { create: fields, }, }, }); console.log(` āœ… Created section: ${sectionInfo.name} with ${fields.length} fields`); } } console.log(''); // ============================================= // Seed Agent-Type-Specific Sections // ============================================= console.log('šŸ“‹ Seeding Agent-Type-Specific Sections...'); // First, delete old global Experience section if exists const oldExperienceSection = await prisma.profileSection.findUnique({ where: { slug: 'experience' }, }); if (oldExperienceSection) { await prisma.profileSection.delete({ where: { slug: 'experience' }, }); console.log(' šŸ—‘ļø Deleted old global Experience section'); } for (const { agentTypeName, section: sectionData } of agentTypeSections) { const { fields, ...sectionInfo } = sectionData; // Find the agent type const agentType = await prisma.agentType.findUnique({ where: { name: agentTypeName }, }); if (!agentType) { console.log(` āš ļø Agent type "${agentTypeName}" not found, skipping section`); continue; } // Check if section already exists let section = await prisma.profileSection.findUnique({ where: { slug: sectionInfo.slug }, }); if (section) { console.log(` āš ļø Section "${sectionInfo.name}" for ${agentTypeName} already exists`); // Still check and create missing fields, or update existing fields with options for (const fieldData of fields) { const existingField = await prisma.profileField.findFirst({ where: { sectionId: section.id, slug: fieldData.slug, }, }); if (!existingField) { await prisma.profileField.create({ data: { ...fieldData, sectionId: section.id, }, }); console.log(` āœ… Created field: ${fieldData.name}`); } else { // Update existing field with options if options are defined in seed but missing in DB const updates: Record = {}; const fd = fieldData as any; // Cast to any to access optional properties if (fd.options && (!existingField.options || (Array.isArray(existingField.options) && existingField.options.length === 0))) { updates.options = fd.options; } if (fd.validation && !existingField.validation) { updates.validation = fd.validation; } if (fd.uiConfig && !existingField.uiConfig) { updates.uiConfig = fd.uiConfig; } if (Object.keys(updates).length > 0) { await prisma.profileField.update({ where: { id: existingField.id }, data: updates, }); console.log(` āœ… Updated field: ${fieldData.name} (${Object.keys(updates).join(', ')})`); } } } } else { // Create section with fields section = await prisma.profileSection.create({ data: { ...sectionInfo, fields: { create: fields, }, }, }); console.log(` āœ… Created section: ${sectionInfo.name} for ${agentTypeName} with ${fields.length} fields`); } // Create AgentTypeSection assignment if not exists const existingAssignment = await prisma.agentTypeSection.findUnique({ where: { agentTypeId_sectionId: { agentTypeId: agentType.id, sectionId: section.id, }, }, }); if (!existingAssignment) { await prisma.agentTypeSection.create({ data: { agentTypeId: agentType.id, sectionId: section.id, sortOrder: sectionInfo.sortOrder, isRequired: false, }, }); console.log(` āœ… Assigned section to ${agentTypeName}`); } } console.log(''); // ============================================= // Seed Admin User // ============================================= console.log('šŸ‘¤ Seeding Admin User...'); const adminEmail = process.env.ADMIN_EMAIL || 'admin@realestate.com'; const adminPassword = process.env.ADMIN_PASSWORD || 'Admin@123456'; // Hash password with Argon2 (more secure than bcrypt) const hashedPassword = await argon2.hash(adminPassword); // Check if admin already exists const existingAdmin = await prisma.user.findUnique({ where: { email: adminEmail }, }); if (existingAdmin) { console.log(` āš ļø Admin user already exists: ${adminEmail}`); } else { // Create admin user const admin = await prisma.user.create({ data: { email: adminEmail, password: hashedPassword, role: UserRole.ADMIN, status: UserStatus.ACTIVE, emailVerified: true, emailVerifiedAt: new Date(), authProvider: AuthProvider.LOCAL, }, }); console.log(' āœ… Admin user created successfully!'); console.log(' ───────────────────────────────'); console.log(` šŸ“§ Email: ${adminEmail}`); console.log(` šŸ”‘ Password: ${adminPassword}`); console.log(` šŸ‘¤ Role: ${admin.role}`); console.log(` šŸ†” ID: ${admin.id}`); console.log(' ───────────────────────────────\n'); } // ============================================= // Summary // ============================================= const userCount = await prisma.user.count(); const adminCount = await prisma.user.count({ where: { role: UserRole.ADMIN } }); const agentTypeCount = await prisma.agentType.count(); const sectionCount = await prisma.profileSection.count(); const fieldCount = await prisma.profileField.count(); const systemSectionCount = await prisma.profileSection.count({ where: { isSystem: true } }); const agentTypeSectionCount = await prisma.agentTypeSection.count(); console.log('šŸ“Š Database Summary:'); console.log(` Total Users: ${userCount}`); console.log(` Admins: ${adminCount}`); console.log(` Agent Types: ${agentTypeCount}`); console.log(` Profile Sections: ${sectionCount} (${systemSectionCount} system)`); console.log(` Profile Fields: ${fieldCount}`); console.log(` Section Assignments: ${agentTypeSectionCount}`); console.log('\n🌱 Seeding completed!\n'); await prisma.$disconnect(); await pool.end(); } main() .catch((e) => { console.error('āŒ Seeding failed:', e); process.exit(1); });