Files
backend/prisma/seed.ts

1608 lines
57 KiB
TypeScript
Raw Normal View History

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,
},
2026-01-24 21:39:14 +05:30
{
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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,
},
2026-01-24 22:19:26 +05:30
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,
2026-01-24 22:31:12 +05:30
isRepeatable: true, // User can add multiple certifications
fields: [
2026-01-24 22:31:12 +05:30
{
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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' },
],
},
],
},
},
2026-01-24 21:36:39 +05:30
// 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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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',
2026-01-24 21:36:39 +05:30
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' },
],
},
],
},
},
2026-01-24 21:36:39 +05:30
// 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`);
2026-01-24 22:31:12 +05:30
// 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<string, unknown> = {};
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<string, unknown> = {};
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');
}
// =============================================
// Seed CMS Content (Landing Page)
// =============================================
console.log('📄 Seeding CMS Content...');
const cmsDefaults = [
{
pageSlug: 'landing',
sectionKey: 'hero',
content: {
headline: '"Discover verified, top-rated real estate professionals to guide your buying, selling, or investing journey."',
description: 'Discover verified, top-rated real estate professionals',
ctaButtonText: 'See All Agents',
helperText: 'Connect with trusted local agents to explore homes, compare options, and make smarter decisions.',
},
},
{
pageSlug: 'landing',
sectionKey: 'features',
content: {
title: 'Find Trusted Real Estate Professionals On Demand',
subtitle: 'Quickly connect with the right agents exactly when you need them.',
features: [
{
iconPath: '/assets/icons/hourglass-icon.svg',
title: 'Hire Quickly',
description: 'Connect with experienced local real estate agents who match your needs and preferences. Our simple process helps you get started quickly and move forward without delays or unnecessary complexity.',
},
{
iconPath: '/assets/icons/verified-badge-icon.svg',
title: 'Verified Agents',
description: 'All agents are carefully identity-verified and background-checked to ensure trust and safety. You can confidently work with professionals who meet quality and reliability standards.',
},
{
iconPath: '/assets/icons/star-orange-icon.svg',
title: 'Top Rated',
description: 'Work with highly rated agents known for strong expertise and positive client feedback. Their consistent performance helps you make informed and confident property decisions.',
},
{
iconPath: '/assets/icons/trusted-people-icon.svg',
title: 'Trusted by Thousands',
description: 'Thousands of buyers, sellers, and renters trust our platform to find reliable agents. Our professionals help people navigate property journeys with confidence and ease.',
},
],
},
},
{
pageSlug: 'landing',
sectionKey: 'topProfessionals',
content: {
title: '"Meet top real estate professionals"',
ctaText: 'Discover 5,000+ Top Real Estate Agents in Our Network.',
ctaButtonText: 'Browse Experts',
agents: [
{
name: 'Arjun Mehta',
subtitle: '(Residential Property Expert)',
location: 'San Francisco, CA',
experience: '10+ years in the real estate industry.',
expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'],
imageUrl: '/assets/images/professional-1.jpg',
},
{
name: 'Arjun Mehta',
subtitle: '(Residential Property Expert)',
location: 'San Francisco, CA',
experience: '10+ years in the real estate industry.',
expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'],
imageUrl: '/assets/images/professional-2.jpg',
},
{
name: 'Arjun Mehta',
subtitle: '(Residential Property Expert)',
location: 'San Francisco, CA',
experience: '10+ years in the real estate industry.',
expertise: ['Residential', 'Rental', 'Commercial', 'Inspection', 'Land', 'Rental'],
imageUrl: '/assets/images/professional-3.jpg',
},
],
lenders: [
{
name: 'Sarah Johnson',
subtitle: '(Mortgage Specialist)',
location: 'Los Angeles, CA',
experience: '10+ years in mortgage lending.',
expertise: ['FHA Loans', 'Conventional', 'VA Loans', 'Refinancing', 'Jumbo'],
imageUrl: '/assets/images/professional-1.jpg',
},
{
name: 'Michael Chen',
subtitle: '(Senior Loan Officer)',
location: 'Seattle, WA',
experience: '7+ years in lending.',
expertise: ['Jumbo Loans', 'Refinancing', 'Investment', 'First-time', 'USDA'],
imageUrl: '/assets/images/professional-2.jpg',
},
{
name: 'Emily Davis',
subtitle: '(Home Loan Advisor)',
location: 'Austin, TX',
experience: '5+ years in mortgage industry.',
expertise: ['First-time Buyers', 'FHA', 'USDA Loans', 'VA Loans', 'Conventional'],
imageUrl: '/assets/images/professional-3.jpg',
},
],
},
},
{
pageSlug: 'landing',
sectionKey: 'testimonials',
content: {
title: "Our Clients' Trust Is Our Foundation",
subtitle: 'We help buyers, sellers, and investors close successful real estate deals with confidence in every transaction.',
ratingInfo: 'Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.',
stats: [
{
iconPath: '/assets/icons/cities-icon.svg',
boldText: '25+ Cities &',
normalText: 'neighborhoods served',
},
{
iconPath: '/assets/icons/properties-icon.svg',
boldText: '1,000+ Properties',
normalText: 'bought and sold for our clients.',
},
],
testimonials: [
{
rating: 5,
title: 'I have been working with Lorem ..',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Sed dignissim, nisl eget tincidunt vulputate, lacus justo bibendum ipsum, vitae tempus risus lorem at nunc. Integer sed arcu vitae risus feugiat vehicula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.',
author: 'Conor Kenney',
role: 'Chief Operations Officer',
},
{
rating: 5,
title: 'I have been working with Lorem ..',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Sed dignissim, nisl eget tincidunt vulputate, lacus justo bibendum ipsum, vitae tempus risus lorem at nunc. Integer sed arcu vitae risus feugiat vehicula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.',
author: 'Conor Kenney',
role: 'Chief Operations Officer',
},
{
rating: 5,
title: 'I have been working with Lorem ..',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean commodo ligula eget dolor. Sed dignissim, nisl eget tincidunt vulputate, lacus justo bibendum ipsum, vitae tempus risus lorem at nunc. Integer sed arcu vitae risus feugiat vehicula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.',
author: 'Conor Kenney',
role: 'Chief Operations Officer',
},
],
},
},
];
for (const cms of cmsDefaults) {
const existing = await prisma.cmsContent.findUnique({
where: {
pageSlug_sectionKey: {
pageSlug: cms.pageSlug,
sectionKey: cms.sectionKey,
},
},
});
if (existing) {
console.log(` ⚠️ CMS content "${cms.pageSlug}/${cms.sectionKey}" already exists`);
} else {
await prisma.cmsContent.create({
data: {
pageSlug: cms.pageSlug,
sectionKey: cms.sectionKey,
content: cms.content,
isPublished: true,
},
});
console.log(` ✅ Created CMS content: ${cms.pageSlug}/${cms.sectionKey}`);
}
}
console.log('');
// =============================================
// Seed Subscription Plans
// =============================================
console.log('💳 Seeding Subscription Plans...');
const subscriptionPlans = [
{
name: 'Professional Annual',
description: 'Full access to all agent features',
stripePriceId: process.env.STRIPE_PRICE_ID,
amount: 49900,
currency: 'usd',
interval: 'year',
features: JSON.stringify([
'Agent Co-Marketing',
'Priority 24/7 Support',
'Whitelabel Client Portals',
'Advanced CRM Tools & Analytics',
]),
isActive: true,
sortOrder: 1,
},
];
for (const plan of subscriptionPlans) {
const existingPlan = await prisma.subscriptionPlan.findUnique({
where: { stripePriceId: plan.stripePriceId },
});
if (existingPlan) {
console.log(` ⚠️ Plan "${plan.name}" already exists`);
} else {
await prisma.subscriptionPlan.create({ data: plan });
console.log(` ✅ Created plan: ${plan.name} ($${(plan.amount / 100).toFixed(2)}/${plan.interval})`);
}
}
console.log('');
// =============================================
// 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();
const cmsContentCount = await prisma.cmsContent.count();
const planCount = await prisma.subscriptionPlan.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(` CMS Content: ${cmsContentCount}`);
console.log(` Plans: ${planCount}`);
console.log('\n🌱 Seeding completed!\n');
await prisma.$disconnect();
await pool.end();
}
main()
.catch((e) => {
console.error('❌ Seeding failed:', e);
process.exit(1);
});