/opt/canhelp/apps/api/src
Edit: /opt/canhelp/apps/api/src/seed.ts (83018B)
import 'dotenv/config'
import { db } from './db.js'
import { users, tasks, offers, categories, locations, plans, specialistCards, portfolioItems } from '@canhelp/db'
import { auth } from './auth.js'
import { and, eq } from 'drizzle-orm'
function normalizeAvatarUrl(url?: string | null) {
if (!url) return url
if (url.includes('api.dicebear.com') && url.includes('/svg?')) {
return url.replace('/svg?', '/png?')
}
return url
}
async function ensureUser(input: {
email: string
password: string
name: string
firstName: string
lastName: string
role: 'admin' | 'customer' | 'specialist' | 'user'
bio?: string
image?: string
profileBackgroundImage?: string
skills?: string[]
specialistCategories?: string[]
specialistLocations?: string[]
locale?: string
showContactInfo?: boolean
}) {
try {
await auth.api.signUpEmail({
body: {
email: input.email,
password: input.password,
name: input.name,
firstName: input.firstName,
lastName: input.lastName,
role: input.role,
},
})
} catch (e: any) {
if (!e?.message?.includes('already') && !e?.message?.includes('exists')) {
console.error(`❌ Failed to create ${input.email}:`, e.message)
}
}
const [user] = await db.select().from(users).where(eq(users.email, input.email))
if (!user) return null
const nextSiteSettings = input.profileBackgroundImage
? {
...(user.siteSettings ?? {}),
profileBackgroundImage: input.profileBackgroundImage,
}
: user.siteSettings
await db.update(users)
.set({
name: input.name,
image: normalizeAvatarUrl(input.image ?? user.image),
firstName: input.firstName,
lastName: input.lastName,
role: input.role,
bio: input.bio ?? user.bio,
siteSettings: nextSiteSettings,
skills: input.skills ?? user.skills ?? [],
specialistCategories: input.specialistCategories ?? user.specialistCategories ?? [],
specialistLocations: input.specialistLocations ?? user.specialistLocations ?? [],
locale: input.locale ?? user.locale ?? 'el',
showContactInfo: input.showContactInfo ?? user.showContactInfo ?? false,
isActive: true,
updatedAt: new Date(),
})
.where(eq(users.id, user.id))
return user
}
async function ensureUserVisual(input: { email: string; image: string; profileBackgroundImage?: string }) {
const [user] = await db.select().from(users).where(eq(users.email, input.email))
if (!user) return
const nextSiteSettings = input.profileBackgroundImage
? {
...(user.siteSettings ?? {}),
profileBackgroundImage: input.profileBackgroundImage,
}
: user.siteSettings
await db.update(users)
.set({
image: normalizeAvatarUrl(input.image),
siteSettings: nextSiteSettings,
updatedAt: new Date(),
})
.where(eq(users.id, user.id))
}
async function ensureSpecialistCard(input: {
specialistId: string
title: string
description: string
skills: string[]
categories: string[]
locations: string[]
}) {
const existingCards = await db.select().from(specialistCards).where(eq(specialistCards.specialistId, input.specialistId))
if (existingCards.length > 0) {
await db.update(specialistCards)
.set({
title: input.title,
description: input.description,
skills: input.skills,
categories: input.categories,
locations: input.locations,
isActive: true,
updatedAt: new Date(),
})
.where(eq(specialistCards.id, existingCards[0].id))
return existingCards[0]
}
return db.insert(specialistCards).values({
specialistId: input.specialistId,
title: input.title,
description: input.description,
skills: input.skills,
categories: input.categories,
locations: input.locations,
isActive: true,
}).returning()
}
async function ensureTask(input: { title: string; description: string; category: string; location: string; budget: string; customerId: string; status?: string; budgetNegotiable?: boolean }) {
const [existing] = await db.select({ id: tasks.id }).from(tasks).where(eq(tasks.title, input.title))
if (existing) return existing
return db.insert(tasks).values({
title: input.title,
description: input.description,
category: input.category,
location: input.location,
budget: input.budget,
budgetNegotiable: input.budgetNegotiable ?? false,
status: (input.status as any) ?? 'open',
customerId: input.customerId,
}).returning()
}
async function ensureOffer(input: { taskId: string; specialistId: string; price: string; message: string; status?: 'pending' | 'accepted' | 'declined' | 'withdrawn' | 'other_accepted' }) {
const [existing] = await db
.select({ id: offers.id })
.from(offers)
.where(and(eq(offers.taskId, input.taskId), eq(offers.specialistId, input.specialistId)))
.limit(1)
if (existing) return existing
return db.insert(offers).values({
taskId: input.taskId,
specialistId: input.specialistId,
price: input.price,
message: input.message,
status: input.status ?? 'pending',
currency: 'EUR',
}).returning()
}
async function ensurePortfolioItems(input: { specialistId: string; items: Array<{ title: string; description: string; imageUrl: string }> }) {
const existing = await db.select({ title: portfolioItems.title }).from(portfolioItems).where(eq(portfolioItems.specialistId, input.specialistId))
const existingTitles = new Set(existing.map((item) => item.title))
for (const [index, item] of input.items.entries()) {
if (existingTitles.has(item.title)) continue
await db.insert(portfolioItems).values({
specialistId: input.specialistId,
title: item.title,
description: item.description,
imageUrl: item.imageUrl,
order: index + 1,
})
}
}
async function seed() {
const isProd = process.argv.includes('--prod')
console.log(isProd ? '🌱 Seeding production (plans + categories + locations only)...' : '🌱 Seeding database...')
// ─── Seed plans ──────────────────────────────────────────────────────────
await seedPlans()
if (isProd) {
await seedCategories()
await seedLocations()
console.log('🎉 Production seed complete!')
process.exit(0)
}
const admin = await ensureUser({
email: 'admin@canhelp.gr',
password: 'Admin123!',
name: 'Admin',
firstName: 'CanHelp',
lastName: 'Admin',
role: 'admin',
locale: 'el',
})
if (admin) console.log('✅ Admin user ready: admin@canhelp.gr / Admin123!')
const customer = await ensureUser({
email: 'customer@canhelp.gr',
password: 'Test123!',
name: 'Γιώργης Παπαδόπουλος',
firstName: 'Γιώργης',
lastName: 'Παπαδόπουλος',
role: 'customer',
bio: 'Αναζητώ γρήγορες και αξιόπιστες υπηρεσίες για το σπίτι και την καθημερινότητα.',
locale: 'el',
showContactInfo: true,
})
if (customer) console.log('✅ Customer ready: customer@canhelp.gr / Test123!')
const customer2 = await ensureUser({
email: 'customer2@canhelp.gr',
password: 'Test123!',
name: 'Μαρία Κουτσουράκη',
firstName: 'Μαρία',
lastName: 'Κουτσουράκη',
role: 'customer',
bio: 'Επιλέγω ειδικούς για μετακόμιση, καθαρισμό και μικρές επισκευές.',
locale: 'el',
showContactInfo: true,
})
if (customer2) console.log('✅ Second customer ready: customer2@canhelp.gr / Test123!')
const customer3 = await ensureUser({
email: 'customer3@canhelp.gr',
password: 'Test123!',
name: 'Δημήτρης Λαμπράκης',
firstName: 'Δημήτρης',
lastName: 'Λαμπράκης',
role: 'customer',
bio: 'Χρειάζομαι επαγγελματίες για μικρές δουλειές στο σπίτι και στο γραφείο.',
locale: 'el',
showContactInfo: true,
})
if (customer3) console.log('✅ Third customer ready: customer3@canhelp.gr / Test123!')
const customer4 = await ensureUser({
email: 'customer4@canhelp.gr',
password: 'Test123!',
name: 'Ειρήνη Μανιάτη',
firstName: 'Ειρήνη',
lastName: 'Μανιάτη',
role: 'customer',
bio: 'Θέλω να συγκεντρώσω προσφορές για ανακαίνιση και μεταφορά.',
locale: 'el',
showContactInfo: true,
})
if (customer4) console.log('✅ Fourth customer ready: customer4@canhelp.gr / Test123!')
const specialist = await ensureUser({
email: 'specialist@canhelp.gr',
password: 'Test123!',
name: 'Νίκος Αντωνίου',
firstName: 'Νίκος',
lastName: 'Αντωνίου',
role: 'specialist',
bio: 'Επαγγελματίας με 5 χρόνια εμπειρία σε καθαρισμό, μετακομίσεις και μικροεπισκευές στην Αθήνα και Θεσσαλονίκη.',
skills: ['Καθαρισμός', 'Μετακόμιση', 'Οργάνωση χώρου', 'Αξιοπιστία'],
specialistCategories: ['cleaning', 'moving'],
specialistLocations: ['athens', 'thessaloniki', 'kolonaki'],
locale: 'el',
showContactInfo: true,
})
if (specialist) {
await ensureSpecialistCard({
specialistId: specialist.id,
title: 'Καθαρισμός & Μετακομίσεις',
description: 'Παρέχω υπηρεσίες καθαρισμού, μετακόμισης και οργάνωσης χώρου με επαγγελματικό εξοπλισμό και έγκαιρη διεκπεραίωση.',
skills: ['Καθαρισμός', 'Μετακόμιση', 'Οργάνωση χώρου'],
categories: ['cleaning', 'moving'],
locations: ['athens', 'thessaloniki', 'kolonaki'],
})
console.log('✅ Specialist card configured for Νίκος')
}
const specialist2 = await ensureUser({
email: 'specialist2@canhelp.gr',
password: 'Test123!',
name: 'Μάριος Περδικάρης',
firstName: 'Μάριος',
lastName: 'Περδικάρης',
role: 'specialist',
bio: 'Ειδικός σε υδραυλικά, μικροεπισκευές και εγκαταστάσεις. Εργάζομαι γρήγορα και με προσοχή στις λεπτομέρειες.',
skills: ['Υδραυλικά', 'Ηλεκτρολογικά', 'Επισκευές'],
specialistCategories: ['plumbing', 'electrical'],
specialistLocations: ['athens', 'piraeus', 'glyfada'],
locale: 'el',
showContactInfo: true,
})
if (specialist2) {
await ensureSpecialistCard({
specialistId: specialist2.id,
title: 'Εγκαταστάσεις & Επισκευές',
description: 'Αναλαμβάνω επισκευές υδραυλικών και ηλεκτρολογικών έργων για σπίτια και μικρές επιχειρήσεις.',
skills: ['Υδραυλικά', 'Ηλεκτρολογικά', 'Επισκευές'],
categories: ['plumbing', 'electrical'],
locations: ['athens', 'piraeus', 'glyfada'],
})
console.log('✅ Specialist card configured for Μάριος')
}
const specialist3 = await ensureUser({
email: 'specialist3@canhelp.gr',
password: 'Test123!',
name: 'Ελένη Σταματοπούλου',
firstName: 'Ελένη',
lastName: 'Σταματοπούλου',
role: 'specialist',
bio: 'Καθηγήτρια αγγλικών και μαθήματα προετοιμασίας εξετάσεων για παιδιά και ενήλικες.',
skills: ['Αγγλικά', 'Προετοιμασία εξετάσεων', 'Μαθήματα online'],
specialistCategories: ['english', 'exam-prep'],
specialistLocations: ['remote', 'athens', 'thessaloniki'],
locale: 'el',
showContactInfo: true,
})
if (specialist3) {
await ensureSpecialistCard({
specialistId: specialist3.id,
title: 'Αγγλικά & Προετοιμασία Εξετάσεων',
description: 'Δίνω μαθήματα αγγλικών, προετοιμασία για εξετάσεις και υποστήριξη για παιδιά και ενήλικες.',
skills: ['Αγγλικά', 'Εξετάσεις', 'Online'],
categories: ['english', 'exam-prep'],
locations: ['remote', 'athens', 'thessaloniki'],
})
console.log('✅ Specialist card configured for Ελένη')
}
const extraSpecialists = [
{
email: 'specialist04@canhelp.gr', name: 'Στέλιος Δημητρίου', firstName: 'Στέλιος', lastName: 'Δημητρίου', bio: 'Φωτογράφος και videographer με έμφαση σε γάμους, εταιρικά events και lifestyle shootings.', skills: ['Φωτογραφία', 'Βίντεο', 'Events'], categories: ['photographer', 'videographer'], locations: ['athens', 'heraklion', 'corfu'], cardTitle: 'Φωτογραφία & Βίντεο Events', cardDescription: 'Παράγω premium φωτογραφίες και βίντεο για γάμους, corporate events και lifestyle sessions.', portfolio: [
{ title: 'Γαμήλιο photoshoot', description: 'Σύνολο από γαμήλια shooting που έδωσαν ιδιαίτερο χαρακτήρα στις στιγμές του event.', imageUrl: 'https://images.unsplash.com/photo-1511285560929-80b456fea0bc?auto=format&fit=crop&w=900&q=80' },
{ title: 'Corporate event reel', description: 'Δημιουργία σύντομου reel με highlight στιγμές από εταιρικό event.', imageUrl: 'https://images.unsplash.com/photo-1492684223066-81342ee5ff30?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist05@canhelp.gr', name: 'Νατάσα Μανωλάκη', firstName: 'Νατάσα', lastName: 'Μανωλάκη', bio: 'Σχεδιάστρια εσωτερικών χώρων που φέρνει ζεστασιά και λειτουργικότητα σε κάθε σπίτι.', skills: ['Interior Design', 'Σχέδιο', 'Χρώματα'], categories: ['interior-design'], locations: ['athens', 'kolonaki', 'thessaloniki'], cardTitle: 'Σχεδιασμός Εσωτερικών Χώρων', cardDescription: 'Δημιουργώ σύγχρονα και λειτουργικά σχέδια για σπίτια, γραφεία και καταστήματα.', portfolio: [
{ title: 'Minimal apartment refresh', description: 'Αναδιαμόρφωση μικρού διαμερίσματος με φωτεινά χρώματα και έξυπνη αποθήκευση.', imageUrl: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist06@canhelp.gr', name: 'Θανάσης Βασιλείου', firstName: 'Θανάσης', lastName: 'Βασιλείου', bio: 'Μηχανικός HVAC και εγκαταστάσεων κλιματισμού με άριστη εξυπηρέτηση και συνέπεια.', skills: ['Κλιματισμός', 'Συντήρηση', 'Εγκατάσταση'], categories: ['ac-installation'], locations: ['athens', 'piraeus', 'patras'], cardTitle: 'Κλιματισμός & Εγκαταστάσεις', cardDescription: 'Εγκατάσταση και συντήρηση κλιματιστικών με εξειδίκευση σε οικιακές και επαγγελματικές ανάγκες.', portfolio: [
{ title: 'Split unit installation', description: 'Εγκατάσταση συστήματος split σε διαμέρισμα με βελτιστοποίηση απόδοσης.', imageUrl: 'https://images.unsplash.com/photo-1581578731548-c64695cc6952?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist07@canhelp.gr', name: 'Ιωάννα Χατζηγεωργίου', firstName: 'Ιωάννα', lastName: 'Χατζηγεωργίου', bio: 'Εξειδικευμένη στην αναβάθμιση ιστότοπων και e-commerce εμπειριών για μικρές επιχειρήσεις.', skills: ['Web Design', 'SEO', 'E-commerce'], categories: ['web-dev', 'seo'], locations: ['remote', 'athens', 'thessaloniki'], cardTitle: 'Web Development & SEO', cardDescription: 'Ανάπτυξη και βελτιστοποίηση ιστοσελίδων για καλύτερη προβολή και πωλήσεις.', portfolio: [
{ title: 'Boutique ecommerce launch', description: 'Ανασχεδιασμός καταστήματος με εύκολη πλοήγηση και κλιμακούμενα προϊόντα.', imageUrl: 'https://images.unsplash.com/photo-1516321497487-e288fb19713f?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist08@canhelp.gr', name: 'Γιάννης Ανδρέου', firstName: 'Γιάννης', lastName: 'Ανδρέου', bio: 'Χειριστής drone και παραγωγός aerial content για τουριστικά και corporate projects.', skills: ['Drone', 'Βίντεο', 'Aerial'], categories: ['drone'], locations: ['athens', 'chania', 'rhodes'], cardTitle: 'Drone Filming', cardDescription: 'Αεροφωτογραφίες και βίντεο για τουριστικές, εμπορικές και event ανάγκες.', portfolio: [
{ title: 'Coastal campaign', description: 'Aerial shots για τουριστική καμπάνια σε παραθαλάσσιο προορισμό.', imageUrl: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist09@canhelp.gr', name: 'Μαριλένα Κορρέ', firstName: 'Μαριλένα', lastName: 'Κορρέ', bio: 'Μακιγιέζ και styling specialist για γάμους, events και καλλιτεχνικές φωτογραφήσεις.', skills: ['Makeup', 'Styling', 'Events'], categories: ['makeup'], locations: ['athens', 'thessaloniki', 'heraklion'], cardTitle: 'Makeup & Styling', cardDescription: 'Υπηρεσίες μακιγιάζ και styling για ξεχωριστές στιγμές και events.', portfolio: [
{ title: 'Wedding beauty styling', description: 'Πλήρης look για γάμο με έμφαση στην φυσική λάμψη.', imageUrl: 'https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist10@canhelp.gr', name: 'Βασίλης Πουλακάκης', firstName: 'Βασίλης', lastName: 'Πουλακάκης', bio: 'Ειδικός στην απολύμανση και καθαρισμό χώρων μετά από ανακαινίσεις και έκτακτες ανάγκες.', skills: ['Απολύμανση', 'Καθαρισμός', 'Μετά-ανακαίνιση'], categories: ['disinfection', 'post-reno-cleaning'], locations: ['athens', 'kifisia', 'marousi'], cardTitle: 'Απολύμανση & Μετά Ανακαίνιση', cardDescription: 'Καθαρισμός και απολύμανση για σπίτια, γραφεία και μετά από ανακαίνιση.', portfolio: [
{ title: 'Post-renovation deep clean', description: 'Εξονυχιστικός καθαρισμός χώρου μετά από ανακαίνιση.', imageUrl: 'https://images.unsplash.com/photo-1484154218962-a197022b5858?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist11@canhelp.gr', name: 'Ευγενία Κωστοπούλου', firstName: 'Ευγενία', lastName: 'Κωστοπούλου', bio: 'Φροντίζω την αισθητική και τη λειτουργικότητα κάθε χώρου μέσα από λεπτομερή σχεδιασμό.', skills: ['Σχεδιασμός', 'Χρώματα', 'Διακόσμηση'], categories: ['interior-design'], locations: ['athens', 'neapoli-th', 'volos'], cardTitle: 'Διακόσμηση & Σχεδιασμός', cardDescription: 'Σχεδιασμός και διαμόρφωση χώρων με έμφαση στα υλικά και τη ροή της κίνησης.', portfolio: [
{ title: 'Living room styling', description: 'Αναβάθμιση σαλονιού με λιτά έπιπλα και φωτεινά υλικά.', imageUrl: 'https://images.unsplash.com/photo-1460317442991-0ec209397118?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist12@canhelp.gr', name: 'Κώστας Θεοδωρόπουλος', firstName: 'Κώστας', lastName: 'Θεοδωρόπουλος', bio: 'Εξειδικευμένος σε μεταφορές, συσκευασία και οργανωμένη μετακόμιση για τη στιγμή της αλλαγής.', skills: ['Μεταφορά', 'Συσκευασία', 'Οργάνωση'], categories: ['home-moving'], locations: ['athens', 'thessaloniki', 'patras'], cardTitle: 'Μετακομίσεις & Μεταφορές', cardDescription: 'Οργανωμένες μετακομίσεις με προσοχή σε όγκο, ασφάλεια και χρόνο.', portfolio: [
{ title: 'Apartment move in 1 day', description: 'Σύντομη και οργανωμένη μεταφορά διαμερίσματος εντός μίας ημέρας.', imageUrl: 'https://images.unsplash.com/photo-1513694203232-719a280e022f?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist13@canhelp.gr', name: 'Χριστίνα Ζαχαροπούλου', firstName: 'Χριστίνα', lastName: 'Ζαχαροπούλου', bio: 'Καθηγήτρια ξένων γλωσσών που δίνει εξατομικευμένα μαθήματα για παιδιά και ενηλίκους.', skills: ['Αγγλικά', 'Γαλλικά', 'Μαθήματα'], categories: ['english', 'languages'], locations: ['remote', 'athens', 'corfu'], cardTitle: 'Ξένες Γλώσσες', cardDescription: 'Μαθήματα αγγλικών και ξένων γλωσσών με προσαρμοσμένο πρόγραμμα ανά επίπεδο.', portfolio: [
{ title: 'Online language club', description: 'Ομαδικά και ατομικά sessions για βελτίωση της ομιλίας και της γραμματικής.', imageUrl: 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist14@canhelp.gr', name: 'Αλέξανδρος Κοντογιάννης', firstName: 'Αλέξανδρος', lastName: 'Κοντογιάννης', bio: 'Εξειδικευμένος σε επισκευές υπολογιστών και laptop, με ανάλυση προβλημάτων σε λίγα λεπτά.', skills: ['PC Repair', 'Laptop', 'Data Recovery'], categories: ['pc-repair'], locations: ['athens', 'piraeus', 'thessaloniki'], cardTitle: 'PC & Laptop Repair', cardDescription: 'Επισκευές υπολογιστών, laptop και ρυθμίσεις συστημάτων για γρήγορη αποκατάσταση.', portfolio: [
{ title: 'Laptop upgrade session', description: 'Αναβάθμιση και καθαρισμός σκληρού δίσκου για καλύτερη απόδοση.', imageUrl: 'https://images.unsplash.com/photo-1518770660439-4636190af475?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist15@canhelp.gr', name: 'Παναγιώτα Γεωργιάδου', firstName: 'Παναγιώτα', lastName: 'Γεωργιάδου', bio: 'Σχέδιο branding και graphic design για μικρές επιχειρήσεις και προσωπικές μάρκες.', skills: ['Branding', 'Graphic Design', 'Logo'], categories: ['graphic-design', 'logo-branding'], locations: ['remote', 'athens', 'larissa'], cardTitle: 'Branding & Graphic Design', cardDescription: 'Σχεδιασμός λογοτύπων, brand assets και visual identity.', portfolio: [
{ title: 'Brand identity kit', description: 'Πλήρες σύνολο brand elements για νέα μικρή επιχείρηση.', imageUrl: 'https://images.unsplash.com/photo-1524758631624-e2822e304c36?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist16@canhelp.gr', name: 'Ανδρέας Σταματάκης', firstName: 'Ανδρέας', lastName: 'Σταματάκης', bio: 'Εγκαταστάσεις και συντήρηση συστημάτων ασφαλείας και κάμερες παρακολούθησης.', skills: ['Security', 'Installation', 'Cameras'], categories: ['tech-repair'], locations: ['athens', 'corfu', 'heraklion'], cardTitle: 'Συστήματα Ασφαλείας', cardDescription: 'Εγκατάσταση και διαχείριση συστημάτων ασφαλείας και κάμερας.', portfolio: [
{ title: 'Home security setup', description: 'Ολοκληρωμένη εγκατάσταση ασφαλείας για σπίτι και αυλή.', imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist17@canhelp.gr', name: 'Ειρήνη Κουτσομύτη', firstName: 'Ειρήνη', lastName: 'Κουτσομύτη', bio: 'Εξειδικευμένη σε βάφες, επισκευές τοίχων και ανανέωση σπιτιού με άμεσο αποτέλεσμα.', skills: ['Painting', 'Renovation', 'Walls'], categories: ['painting'], locations: ['athens', 'kalamata', 'volos'], cardTitle: 'Βαφές & Ανακαινίσεις', cardDescription: 'Βαφές και μικρές ανακαινίσεις για σπίτια και επιχειρήσεις.', portfolio: [
{ title: 'Modern apartment paint refresh', description: 'Ανανέωση διαμερίσματος με μοντέρνα χρωματική παλέτα.', imageUrl: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist18@canhelp.gr', name: 'Φωτεινή Μπρατσή', firstName: 'Φωτεινή', lastName: 'Μπρατσή', bio: 'Τακτοποίησης χώρων και οργανωτική υποστήριξη για μικρές επιχειρήσεις και σπίτι.', skills: ['Οργάνωση', 'Τακτοποίηση', 'Home staging'], categories: ['moving'], locations: ['athens', 'thessaloniki', 'remote'], cardTitle: 'Οργάνωση Χώρων', cardDescription: 'Τακτοποίηση και οργάνωση ανοιχτών χώρων για την καλύτερη χρήση τους.', portfolio: [
{ title: 'Office decluttering project', description: 'Απομάκρυνση άχρηστου υλικού και οργανωμένη διάταξη γραφείου.', imageUrl: 'https://images.unsplash.com/photo-1497366754035-f200968a6e72?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist19@canhelp.gr', name: 'Κωνσταντίνος Τσάκωνας', firstName: 'Κωνσταντίνος', lastName: 'Τσάκωνας', bio: 'Συνεργάζομαι με πελάτες που θέλουν ένα ισχυρό online presence και αποδοτική διαφήμιση.', skills: ['Ads', 'Marketing', 'Social Media'], categories: ['seo'], locations: ['remote', 'athens', 'thessaloniki'], cardTitle: 'Digital Marketing', cardDescription: 'Διαχείριση κοινωνικών δικτύων, βελτιστοποίηση και διαφημιστικές καμπάνιες.', portfolio: [
{ title: 'Social media launch', description: 'Πλήρης καμπάνια για νέα μάρκα με προγραμματισμό περιεχομένου.', imageUrl: 'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?auto=format&fit=crop&w=900&q=80' },
],
},
{
email: 'specialist20@canhelp.gr', name: 'Λυδία Κοτρώτσου', firstName: 'Λυδία', lastName: 'Κοτρώτσου', bio: 'Γιαγιά και σύμβουλος μαγειρικής, προσφέρω διαλέξεις, masterclasses και ιδιωτικά μαθήματα.', skills: ['Μαγειρική', 'Masterclass', 'Cooking'], categories: ['other'], locations: ['athens', 'heraklion', 'remote'], cardTitle: 'Cooking & Workshops', cardDescription: 'Μαθήματα και εργαστήρια μαγειρικής για άτομα και ομάδες.', portfolio: [
{ title: 'Private cooking workshop', description: 'Συνεργασία με ομάδες για ιδιωτικό workshop μαγειρικής.', imageUrl: 'https://images.unsplash.com/photo-1466637574441-749b8f19452f?auto=format&fit=crop&w=900&q=80' },
],
},
]
const seededSpecialists: Record
= {}
for (const profile of extraSpecialists) {
const specialist = await ensureUser({
email: profile.email,
password: 'Test123!',
name: profile.name,
firstName: profile.firstName,
lastName: profile.lastName,
role: 'specialist',
bio: profile.bio,
skills: profile.skills,
specialistCategories: profile.categories,
specialistLocations: profile.locations,
locale: 'el',
showContactInfo: true,
})
seededSpecialists[profile.email] = specialist
if (!specialist) continue
await ensureSpecialistCard({
specialistId: specialist.id,
title: profile.cardTitle,
description: profile.cardDescription,
skills: profile.skills,
categories: profile.categories,
locations: profile.locations,
})
await ensurePortfolioItems({
specialistId: specialist.id,
items: profile.portfolio,
})
}
console.log('✅ Added 20 additional specialists with portfolio items')
const visualProfiles = [
{ email: 'specialist@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/32.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1497366811353-6870744d04b2?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist2@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/44.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist3@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/53.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1516382799247-87df95d790b7?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist04@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/51.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1505373877841-8d25f7d46678?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist05@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/21.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1484101403633-562f891dc89a?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist06@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/9.jpg' },
{ email: 'specialist07@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/34.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist08@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/18.jpg' },
{ email: 'specialist09@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/67.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1512496015851-a90fb38ba796?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist10@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/63.jpg' },
{ email: 'specialist11@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/60.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist12@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/75.jpg' },
{ email: 'specialist13@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/38.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1498243691581-b145c3f54a5a?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist14@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/27.jpg' },
{ email: 'specialist15@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/14.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1545239351-1141bd82e8a6?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist16@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/82.jpg' },
{ email: 'specialist17@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/49.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1460353581641-37baddab0fa2?auto=format&fit=crop&w=1200&q=80' },
{ email: 'specialist18@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/73.jpg' },
{ email: 'specialist19@canhelp.gr', image: 'https://randomuser.me/api/portraits/men/56.jpg' },
{ email: 'specialist20@canhelp.gr', image: 'https://randomuser.me/api/portraits/women/25.jpg', profileBackgroundImage: 'https://images.unsplash.com/photo-1466637574441-749b8f19452f?auto=format&fit=crop&w=1200&q=80' },
{ email: 'customer@canhelp.gr', image: 'https://api.dicebear.com/9.x/shapes/svg?seed=customer-1-canhelp' },
{ email: 'customer2@canhelp.gr', image: 'https://api.dicebear.com/9.x/shapes/svg?seed=customer-2-canhelp' },
{ email: 'customer3@canhelp.gr', image: 'https://api.dicebear.com/9.x/shapes/svg?seed=customer-3-canhelp' },
{ email: 'customer4@canhelp.gr', image: 'https://api.dicebear.com/9.x/shapes/svg?seed=customer-4-canhelp' },
]
for (const visual of visualProfiles) {
await ensureUserVisual(visual)
}
const usersWithExplicitBackground = new Set(
visualProfiles
.filter((v) => !!v.profileBackgroundImage)
.map((v) => v.email),
)
const explicitBackgroundByEmail = new Map(
visualProfiles
.filter((v) => v.profileBackgroundImage != null)
.map((v) => [v.email, v.profileBackgroundImage as string] as const),
)
const explicitImageByEmail = new Map(
visualProfiles
.filter((v) => v.image.trim().length > 0)
.map((v) => [v.email, v.image] as const),
)
// Ensure all specialists have a visible logo + full-card background in local demo data.
const fallbackBackgrounds = [
'https://images.unsplash.com/photo-1497366811353-6870744d04b2?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1516382799247-87df95d790b7?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1505373877841-8d25f7d46678?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1484101403633-562f891dc89a?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1461749280684-dccba630e2f6?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1512496015851-a90fb38ba796?auto=format&fit=crop&w=1200&q=80',
'https://images.unsplash.com/photo-1498243691581-b145c3f54a5a?auto=format&fit=crop&w=1200&q=80',
]
const specialistsWithVisuals = await db
.select({
id: users.id,
firstName: users.firstName,
lastName: users.lastName,
email: users.email,
image: users.image,
siteSettings: users.siteSettings,
})
.from(users)
.where(eq(users.role, 'specialist'))
const usedBackgrounds = new Set()
let autoFilledVisuals = 0
for (let i = 0; i < specialistsWithVisuals.length; i++) {
const specialist = specialistsWithVisuals[i]
const currentImage = (specialist.image ?? '').trim()
const currentBackground = ((specialist.siteSettings as any)?.profileBackgroundImage ?? '').toString().trim()
const hasBackground = currentBackground.length > 0
const shouldShowBackground = usersWithExplicitBackground.has(specialist.email)
const generatedImage = normalizeAvatarUrl(
explicitImageByEmail.get(specialist.email)
?? `https://randomuser.me/api/portraits/${i % 2 === 0 ? 'men' : 'women'}/${(i * 7) % 90}.jpg`,
)
let generatedBackground: string | null = null
if (shouldShowBackground) {
const explicitBackground = explicitBackgroundByEmail.get(specialist.email) ?? null
if (explicitBackground != null && !usedBackgrounds.has(explicitBackground)) {
generatedBackground = explicitBackground
} else if (hasBackground && !usedBackgrounds.has(currentBackground)) {
generatedBackground = currentBackground
} else {
const uniqueFallback = fallbackBackgrounds.find((url) => !usedBackgrounds.has(url))
generatedBackground = uniqueFallback ?? fallbackBackgrounds[i % fallbackBackgrounds.length]
}
usedBackgrounds.add(generatedBackground)
}
const nextSiteSettings = { ...(specialist.siteSettings ?? {}) } as Record
if (generatedBackground != null) {
nextSiteSettings.profileBackgroundImage = generatedBackground
} else {
delete nextSiteSettings.profileBackgroundImage
}
const imageChanged = generatedImage !== normalizeAvatarUrl(currentImage)
const backgroundChanged = (nextSiteSettings.profileBackgroundImage ?? null) !== (hasBackground ? currentBackground : null)
if (!imageChanged && !backgroundChanged) continue
await db.update(users)
.set({
image: generatedImage,
siteSettings: nextSiteSettings,
updatedAt: new Date(),
})
.where(eq(users.id, specialist.id))
autoFilledVisuals++
}
console.log(`✅ Added avatars for many users and profile backgrounds for selected specialists (auto-filled missing visuals for ${autoFilledVisuals} specialists)`)
const offerSeedTasks: Array<{ title: string; specialistEmail: string; price: string; message: string; status?: 'pending' | 'accepted' | 'declined' | 'withdrawn' | 'other_accepted' }> = [
{ title: 'Καθαρισμός διαμερίσματος 80τμ', specialistEmail: 'specialist@canhelp.gr', price: '72', message: 'Μπορώ να το αναλάβω την Παρασκευή πρωί με δικό μου εξοπλισμό.', status: 'pending' },
{ title: 'Καθαρισμός διαμερίσματος 80τμ', specialistEmail: 'specialist04@canhelp.gr', price: '78', message: 'Έχω εμπειρία σε καθαρισμούς μετά από ενοικίαση και μετακόμιση.', status: 'pending' },
{ title: 'Επισκευή βρύσης στο μπάνιο', specialistEmail: 'specialist2@canhelp.gr', price: '45', message: 'Μπορώ να περάσω το απόγευμα και να ελέγξω και τις συνδέσεις.', status: 'accepted' },
{ title: 'Επισκευή βρύσης στο μπάνιο', specialistEmail: 'specialist10@canhelp.gr', price: '42', message: 'Αν χρειάζεται, φέρνω και ανταλλακτικά φλάντζας.', status: 'declined' },
{ title: 'Μαθήματα αγγλικών γλώσσας για παιδί Γ\' Δημοτικού', specialistEmail: 'specialist3@canhelp.gr', price: '110', message: 'Προτείνω 2 μαθήματα την εβδομάδα και δωρεάν αξιολόγηση επιπέδου.', status: 'pending' },
{ title: 'Μεταφορά επίπλων από το σπίτι σε νέο διαμέρισμα', specialistEmail: 'specialist12@canhelp.gr', price: '140', message: 'Μπορώ να οργανώσω μεταφορά με 2 άτομα και προστατευτικά υλικά.', status: 'pending' },
{ title: 'Μεταφορά επίπλων από το σπίτι σε νέο διαμέρισμα', specialistEmail: 'specialist18@canhelp.gr', price: '135', message: 'Έχω διαθέσιμο van και βοηθητικό προσωπικό για το Σάββατο.', status: 'other_accepted' },
{ title: 'Συντήρηση κλιματιστικού και καθαρισμός φίλτρων', specialistEmail: 'specialist06@canhelp.gr', price: '90', message: 'Περιλαμβάνει έλεγχο ψυκτικού και καθαρισμό εσωτερικής μονάδας.', status: 'pending' },
{ title: 'Βοήθεια για οργάνωση γραφείου και αποθήκευσης', specialistEmail: 'specialist18@canhelp.gr', price: '65', message: 'Μπορώ να βοηθήσω με decluttering και τακτοποίηση εγγράφων.', status: 'pending' },
{ title: 'Βοήθεια για οργάνωση γραφείου και αποθήκευσης', specialistEmail: 'specialist11@canhelp.gr', price: '75', message: 'Προτείνω πιο λειτουργική διάταξη για το γραφείο σας.', status: 'pending' },
{ title: 'Εγκατάσταση 2 κλιματιστικών σε νέο διαμέρισμα', specialistEmail: 'specialist06@canhelp.gr', price: '180', message: 'Μπορώ να κάνω εγκατάσταση με ραντεβού εντός εβδομάδας.', status: 'pending' },
{ title: 'Σχεδιασμός logo για μικρή επιχείρηση', specialistEmail: 'specialist15@canhelp.gr', price: '220', message: 'Παραδίδω 3 concept proposals και 2 γύρους διορθώσεων.', status: 'pending' },
{ title: 'Φωτογράφιση εκδήλωσης σε εστιατόριο', specialistEmail: 'specialist04@canhelp.gr', price: '300', message: 'Καλύπτω 3 ώρες event με παράδοση επεξεργασμένων φωτογραφιών.', status: 'pending' },
{ title: 'Καθαρισμός διαμερίσματος 95τμ στη Γλυφάδα', specialistEmail: 'specialist@canhelp.gr', price: '95', message: 'Περιλαμβάνει κουζίνα, μπάνια και μπαλκόνια.', status: 'pending' },
{ title: 'Μαθήματα αγγλικών για ενήλικα αρχάριο', specialistEmail: 'specialist13@canhelp.gr', price: '100', message: 'Μπορώ να προσαρμόσω την ύλη σε γρήγορη καθημερινή χρήση.', status: 'pending' },
]
if (customer3 && customer4) {
const additionalTasks = [
{
title: 'Φωτογράφιση οικογενειακής γιορτής',
description: 'Θέλω φωτογράφο για μικρή οικογενειακή γιορτή σε εστιατόριο στην Αθήνα.',
category: 'photo-video',
location: 'athens-center',
budget: '250',
customerId: customer3.id,
},
{
title: 'Βαφή παιδικού δωματίου και μικρές επιδιορθώσεις',
description: 'Χρειάζομαι βαφή τοίχων και διόρθωση μικρών φθορών πριν τη μετακόμιση.',
category: 'painting',
location: 'marousi',
budget: '160',
customerId: customer3.id,
},
{
title: 'Δημιουργία landing page για υπηρεσία',
description: 'Θέλω simple landing page με φόρμα επικοινωνίας και mobile-first σχεδίαση.',
category: 'web-dev',
location: 'remote',
budget: '320',
customerId: customer4.id,
},
{
title: 'Οργάνωση και μετακόμιση αποθήκης',
description: 'Χρειάζομαι ομάδα για μεταφορά και τακτοποίηση αποθήκης σε νέο χώρο.',
category: 'home-moving',
location: 'piraeus',
budget: '210',
customerId: customer4.id,
},
]
for (const task of additionalTasks) {
await ensureTask(task)
}
console.log('✅ Additional demo tasks created for local iPad fill')
for (const offer of offerSeedTasks) {
const task = await db.select({ id: tasks.id }).from(tasks).where(eq(tasks.title, offer.title)).limit(1)
const specialist = seededSpecialists[offer.specialistEmail] ?? await db.select({ id: users.id }).from(users).where(eq(users.email, offer.specialistEmail)).limit(1).then((rows) => rows[0])
if (!task[0] || !specialist) continue
await ensureOffer({
taskId: task[0].id,
specialistId: specialist.id,
price: offer.price,
message: offer.message,
status: offer.status,
})
}
console.log('✅ Additional demo offers created for local iPad fill')
}
if (customer) {
const sampleTasks = [
{
title: 'Καθαρισμός διαμερίσματος 80τμ',
description: 'Χρειάζομαι γενικό καθαρισμό διαμερίσματος 80 τετραγωνικών μέτρων στην Αθήνα. Κουζίνα, μπάνιο, 2 δωμάτια και σαλόνι.',
category: 'home-cleaning',
location: 'kolonaki',
budget: '80',
customerId: customer.id,
},
{
title: 'Επισκευή βρύσης στο μπάνιο',
description: 'Έχω μια βρύση που στάζει στο μπάνιο και χρειάζεται αντικατάσταση της φλάντζας. Μπορεί επίσης να χρειαστεί νέος μηχανισμός.',
category: 'plumbing',
location: 'thessaloniki',
budget: '50',
customerId: customer.id,
},
{
title: 'Μαθήματα αγγλικών γλώσσας για παιδί Γ\' Δημοτικού',
description: 'Αναζητώ καθηγητή/τρια αγγλικών για παιδί 9 χρονών που φοιτά στην Γ\' Δημοτικού. 2 φορές την εβδομάδα, 1 ώρα.',
category: 'english',
location: 'piraeus',
budget: '120',
customerId: customer.id,
},
{
title: 'Μεταφορά επίπλων από το σπίτι σε νέο διαμέρισμα',
description: 'Χρειάζομαι βοηθούς για μεταφορά επίπλων και μικρών αντικειμένων το Σάββατο το πρωί στην Γλυφάδα.',
category: 'home-moving',
location: 'glyfada',
budget: '150',
customerId: customer.id,
},
{
title: 'Συντήρηση κλιματιστικού και καθαρισμός φίλτρων',
description: 'Θέλω να γίνει έλεγχος και καθαρισμός του κλιματιστικού πριν αρχίσει η ζέστη.',
category: 'ac-installation',
location: 'marousi',
budget: '95',
customerId: customer.id,
},
]
for (const task of sampleTasks) {
await ensureTask(task)
}
console.log('✅ Sample tasks created for demo marketplace')
}
if (customer2) {
await ensureTask({
title: 'Βοήθεια για οργάνωση γραφείου και αποθήκευσης',
description: 'Ψάχνω άτομο για τακτοποίηση αρχείων, ραφιών και οργανωμένη διαρρύθμιση γραφείου.',
category: 'office-cleaning',
location: 'athens-center',
budget: '70',
customerId: customer2.id,
budgetNegotiable: true,
})
console.log('✅ Additional sample task created for second customer')
}
await seedCategories()
await seedLocations()
console.log('🎉 Seeding complete!')
process.exit(0)
}
async function seedCategories() {
// Seed categories — always reset for fresh data
await db.delete(categories)
console.log('🗑️ Cleared existing categories')
// ─── Root categories ────────────────────────────────────────────────────
const rootCats = await db.insert(categories).values([
{ slug: 'repairs', icon: '🔨', namesEl: 'Επισκευές & Κατασκευές', namesEn: 'Repairs & Construction', namesRu: 'Ремонт и строительство', order: 1 },
{ slug: 'cleaning', icon: '🧹', namesEl: 'Καθαρισμός', namesEn: 'Cleaning', namesRu: 'Уборка', order: 2 },
{ slug: 'moving', icon: '🚚', namesEl: 'Μετακόμιση & Μεταφορές', namesEn: 'Moving & Transport', namesRu: 'Переезд и грузоперевозки', order: 3 },
{ slug: 'tutoring', icon: '📚', namesEl: 'Εκπαίδευση', namesEn: 'Tutoring & Education', namesRu: 'Репетиторство', order: 4 },
{ slug: 'it', icon: '💻', namesEl: 'IT & Τεχνολογία', namesEn: 'IT & Technology', namesRu: 'IT и технологии', order: 5 },
{ slug: 'design', icon: '🎨', namesEl: 'Σχεδιασμός', namesEn: 'Design', namesRu: 'Дизайн', order: 6 },
{ slug: 'beauty', icon: '💅', namesEl: 'Ομορφιά & Υγεία', namesEn: 'Beauty & Health', namesRu: 'Красота и здоровье', order: 7 },
{ slug: 'tech-repair',icon: '🔩', namesEl: 'Επισκευή Συσκευών', namesEn: 'Device Repair', namesRu: 'Ремонт техники', order: 8 },
{ slug: 'events', icon: '🎉', namesEl: 'Εκδηλώσεις', namesEn: 'Events', namesRu: 'Мероприятия', order: 9 },
{ slug: 'photo-video',icon: '📷', namesEl: 'Φωτογραφία & Βίντεο', namesEn: 'Photo & Video', namesRu: 'Фото и видео', order: 10 },
{ slug: 'pets', icon: '🐾', namesEl: 'Κατοικίδια', namesEn: 'Pets', namesRu: 'Домашние животные', order: 11 },
{ slug: 'auto', icon: '🚗', namesEl: 'Αυτοκίνητο', namesEn: 'Automotive', namesRu: 'Авто', order: 12 },
{ slug: 'legal', icon: '⚖️', namesEl: 'Νομικές Υπηρεσίες', namesEn: 'Legal Services', namesRu: 'Юридические услуги', order: 13 },
{ slug: 'accounting', icon: '📊', namesEl: 'Λογιστικά', namesEn: 'Accounting', namesRu: 'Бухгалтерия', order: 14 },
{ slug: 'other', icon: '✨', namesEl: 'Άλλο', namesEn: 'Other', namesRu: 'Другое', order: 99 },
]).returning()
const get = (slug: string) => rootCats.find((c) => c.slug === slug)!
// ─── Repairs & Construction ─────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'plumbing', icon: '🔧', namesEl: 'Υδραυλικά', namesEn: 'Plumbing', namesRu: 'Сантехника', parentId: get('repairs').id, order: 1 },
{ slug: 'electrical', icon: '⚡', namesEl: 'Ηλεκτρολογικά', namesEn: 'Electrical', namesRu: 'Электрика', parentId: get('repairs').id, order: 2 },
{ slug: 'painting', icon: '🖌️', namesEl: 'Βαφές', namesEn: 'Painting', namesRu: 'Покраска', parentId: get('repairs').id, order: 3 },
{ slug: 'tiling', icon: '🪟', namesEl: 'Πλακάκια', namesEn: 'Tiling', namesRu: 'Укладка плитки', parentId: get('repairs').id, order: 4 },
{ slug: 'plastering', icon: '🏗️', namesEl: 'Σοβατισμός', namesEn: 'Plastering', namesRu: 'Штукатурные работы', parentId: get('repairs').id, order: 5 },
{ slug: 'flooring', icon: '🪵', namesEl: 'Δάπεδα & Παρκέ', namesEn: 'Flooring & Parquet', namesRu: 'Полы и паркет', parentId: get('repairs').id, order: 6 },
{ slug: 'carpentry', icon: '🪚', namesEl: 'Ξυλουργικά', namesEn: 'Carpentry', namesRu: 'Столярные работы', parentId: get('repairs').id, order: 7 },
{ slug: 'roofing', icon: '🏠', namesEl: 'Στέγη & Μόνωση', namesEn: 'Roofing & Insulation', namesRu: 'Кровля и утепление', parentId: get('repairs').id, order: 8 },
{ slug: 'windows-doors', icon: '🚪', namesEl: 'Πόρτες & Παράθυρα', namesEn: 'Doors & Windows', namesRu: 'Двери и окна', parentId: get('repairs').id, order: 9 },
{ slug: 'ac-installation', icon: '❄️', namesEl: 'Κλιματισμός', namesEn: 'Air Conditioning', namesRu: 'Кондиционеры', parentId: get('repairs').id, order: 10 },
{ slug: 'furniture-assembly',icon: '🛋️',namesEl: 'Συναρμολόγηση Επίπλων', namesEn: 'Furniture Assembly', namesRu: 'Сборка мебели', parentId: get('repairs').id, order: 11 },
{ slug: 'welding', icon: '🔩', namesEl: 'Συγκόλληση', namesEn: 'Welding', namesRu: 'Сварочные работы', parentId: get('repairs').id, order: 12 },
])
// ─── Cleaning ────────────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'home-cleaning', icon: '🏠', namesEl: 'Καθαρισμός Σπιτιού', namesEn: 'Home Cleaning', namesRu: 'Уборка квартиры', parentId: get('cleaning').id, order: 1 },
{ slug: 'office-cleaning', icon: '🏢', namesEl: 'Καθαρισμός Γραφείου', namesEn: 'Office Cleaning', namesRu: 'Уборка офиса', parentId: get('cleaning').id, order: 2 },
{ slug: 'window-cleaning', icon: '🪟', namesEl: 'Καθαρισμός Τζαμιών', namesEn: 'Window Cleaning', namesRu: 'Мытьё окон', parentId: get('cleaning').id, order: 3 },
{ slug: 'carpet-cleaning', icon: '🧺', namesEl: 'Καθαρισμός Χαλιών', namesEn: 'Carpet & Upholstery', namesRu: 'Чистка ковров и мебели', parentId: get('cleaning').id, order: 4 },
{ slug: 'post-reno-cleaning',icon: '🧱', namesEl: 'Μετά Ανακαίνιση', namesEn: 'Post-Renovation', namesRu: 'После ремонта', parentId: get('cleaning').id, order: 5 },
{ slug: 'disinfection', icon: '🦠', namesEl: 'Απολύμανση', namesEn: 'Disinfection', namesRu: 'Дезинфекция', parentId: get('cleaning').id, order: 6 },
{ slug: 'pool-cleaning', icon: '🏊', namesEl: 'Καθαρισμός Πισίνας', namesEn: 'Pool Cleaning', namesRu: 'Чистка бассейна', parentId: get('cleaning').id, order: 7 },
])
// ─── Moving & Transport ──────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'home-moving', icon: '📦', namesEl: 'Οικιακή Μετακόμιση', namesEn: 'Home Moving', namesRu: 'Квартирный переезд', parentId: get('moving').id, order: 1 },
{ slug: 'office-moving', icon: '🏢', namesEl: 'Μετακόμιση Γραφείου', namesEn: 'Office Moving', namesRu: 'Офисный переезд', parentId: get('moving').id, order: 2 },
{ slug: 'freight', icon: '🚛', namesEl: 'Φορτηγό & Αχθοφόροι', namesEn: 'Freight & Movers', namesRu: 'Грузчики и фургон', parentId: get('moving').id, order: 3 },
{ slug: 'courier', icon: '🛵', namesEl: 'Κούριερ & Παράδοση', namesEn: 'Courier & Delivery',namesRu: 'Курьер и доставка', parentId: get('moving').id, order: 4 },
])
// ─── Tutoring & Education ────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'math', icon: '➕', namesEl: 'Μαθηματικά', namesEn: 'Mathematics', namesRu: 'Математика', parentId: get('tutoring').id, order: 1 },
{ slug: 'english', icon: '🇬🇧', namesEl: 'Αγγλικά', namesEn: 'English', namesRu: 'Английский язык', parentId: get('tutoring').id, order: 2 },
{ slug: 'languages', icon: '🌍', namesEl: 'Ξένες Γλώσσες', namesEn: 'Foreign Languages', namesRu: 'Иностранные языки', parentId: get('tutoring').id, order: 3 },
{ slug: 'physics', icon: '🔬', namesEl: 'Φυσική & Χημεία', namesEn: 'Physics & Chemistry',namesRu: 'Физика и химия', parentId: get('tutoring').id, order: 4 },
{ slug: 'exam-prep', icon: '📝', namesEl: 'Προετοιμασία Εξετάσεων', namesEn: 'Exam Preparation', namesRu: 'Подготовка к экзаменам',parentId: get('tutoring').id, order: 5 },
{ slug: 'music-lessons',icon: '🎵',namesEl: 'Μουσικά Μαθήματα', namesEn: 'Music Lessons', namesRu: 'Уроки музыки', parentId: get('tutoring').id, order: 6 },
{ slug: 'art-lessons', icon: '🎨', namesEl: 'Ζωγραφική & Τέχνη', namesEn: 'Art Lessons', namesRu: 'Рисование и творчество',parentId: get('tutoring').id, order: 7 },
])
// ─── IT & Technology ─────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'web-dev', icon: '🌐', namesEl: 'Ανάπτυξη Ιστοσελίδων', namesEn: 'Web Development', namesRu: 'Веб-разработка', parentId: get('it').id, order: 1 },
{ slug: 'mobile-dev', icon: '📱', namesEl: 'Mobile Εφαρμογές', namesEn: 'Mobile Apps', namesRu: 'Мобильные приложения', parentId: get('it').id, order: 2 },
{ slug: 'sysadmin', icon: '🖥️', namesEl: 'Διαχείριση Συστημάτων', namesEn: 'System Admin', namesRu: 'Системное администрирование', parentId: get('it').id, order: 3 },
{ slug: 'pc-setup', icon: '🔌', namesEl: 'Εγκατάσταση & Ρύθμιση', namesEn: 'PC Setup & Config', namesRu: 'Настройка компьютера', parentId: get('it').id, order: 4 },
{ slug: 'seo', icon: '📈', namesEl: 'SEO & Διαφήμιση', namesEn: 'SEO & Advertising', namesRu: 'SEO и реклама', parentId: get('it').id, order: 5 },
{ slug: 'cybersecurity',icon: '🔐',namesEl: 'Ασφάλεια', namesEn: 'Cybersecurity', namesRu: 'Кибербезопасность', parentId: get('it').id, order: 6 },
])
// ─── Design ──────────────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'graphic-design', icon: '🖼️', namesEl: 'Γραφιστική', namesEn: 'Graphic Design', namesRu: 'Графический дизайн', parentId: get('design').id, order: 1 },
{ slug: 'logo-branding', icon: '✒️', namesEl: 'Λογότυπο & Branding', namesEn: 'Logo & Branding', namesRu: 'Логотип и брендинг', parentId: get('design').id, order: 2 },
{ slug: 'interior-design', icon: '🏡', namesEl: 'Σχεδιασμός Εσωτερικού',namesEn: 'Interior Design', namesRu: 'Дизайн интерьера', parentId: get('design').id, order: 3 },
{ slug: 'print-design', icon: '🖨️', namesEl: 'Έντυπα & Πολυγραφία', namesEn: 'Print Design', namesRu: 'Полиграфия', parentId: get('design').id, order: 4 },
{ slug: 'ui-ux', icon: '📐', namesEl: 'UI/UX Design', namesEn: 'UI/UX Design', namesRu: 'UI/UX дизайн', parentId: get('design').id, order: 5 },
])
// ─── Beauty & Health ─────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'haircut', icon: '💇', namesEl: 'Κομμωτική', namesEn: 'Hairdressing', namesRu: 'Парикмахерские услуги', parentId: get('beauty').id, order: 1 },
{ slug: 'manicure', icon: '💅', namesEl: 'Μανικιούρ & Πεντικιούρ', namesEn: 'Manicure & Pedicure',namesRu: 'Маникюр и педикюр', parentId: get('beauty').id, order: 2 },
{ slug: 'massage', icon: '💆', namesEl: 'Μασάζ', namesEn: 'Massage', namesRu: 'Массаж', parentId: get('beauty').id, order: 3 },
{ slug: 'fitness', icon: '🏋️', namesEl: 'Personal Trainer', namesEn: 'Personal Trainer', namesRu: 'Персональный тренер', parentId: get('beauty').id, order: 4 },
{ slug: 'makeup', icon: '👄', namesEl: 'Μακιγιάζ', namesEn: 'Makeup', namesRu: 'Макияж', parentId: get('beauty').id, order: 5 },
{ slug: 'tattoo', icon: '🎨', namesEl: 'Τατουάζ & Piercing', namesEn: 'Tattoo & Piercing', namesRu: 'Тату и пирсинг', parentId: get('beauty').id, order: 6 },
])
// ─── Device Repair ───────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'pc-repair', icon: '🖥️', namesEl: 'Επισκευή Υπολογιστή', namesEn: 'PC & Laptop Repair', namesRu: 'Ремонт ПК и ноутбука', parentId: get('tech-repair').id, order: 1 },
{ slug: 'phone-repair', icon: '📱', namesEl: 'Επισκευή Κινητού', namesEn: 'Phone Repair', namesRu: 'Ремонт телефона', parentId: get('tech-repair').id, order: 2 },
{ slug: 'appliance-repair',icon: '🫙', namesEl: 'Επισκευή Οικιακών Συσκευών',namesEn: 'Appliance Repair', namesRu: 'Ремонт бытовой техники', parentId: get('tech-repair').id, order: 3 },
{ slug: 'fridge-repair', icon: '🥶', namesEl: 'Ψυγείο & Κλιματιστικό', namesEn: 'Fridge & AC Repair', namesRu: 'Ремонт холодильника', parentId: get('tech-repair').id, order: 4 },
{ slug: 'washer-repair', icon: '🫧', namesEl: 'Πλυντήριο & Στεγνωτήριο',namesEn: 'Washer & Dryer Repair',namesRu: 'Ремонт стиральной машины',parentId: get('tech-repair').id, order: 5 },
])
// ─── Events ──────────────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'photographer',icon: '📷', namesEl: 'Φωτογράφος', namesEn: 'Photographer', namesRu: 'Фотограф', parentId: get('events').id, order: 1 },
{ slug: 'videographer',icon: '🎬', namesEl: 'Βιντεογράφος', namesEn: 'Videographer', namesRu: 'Видеограф', parentId: get('events').id, order: 2 },
{ slug: 'dj', icon: '🎧', namesEl: 'DJ', namesEn: 'DJ', namesRu: 'DJ', parentId: get('events').id, order: 3 },
{ slug: 'mc-host', icon: '🎤', namesEl: 'Εκφωνητής & MC', namesEn: 'MC & Host', namesRu: 'Ведущий и MC', parentId: get('events').id, order: 4 },
{ slug: 'animator', icon: '🤹', namesEl: 'Animator & Clown', namesEn: 'Animator & Clown', namesRu: 'Аниматор', parentId: get('events').id, order: 5 },
{ slug: 'catering', icon: '🍽️', namesEl: 'Catering', namesEn: 'Catering', namesRu: 'Кейтеринг', parentId: get('events').id, order: 6 },
{ slug: 'decorations', icon: '🎊', namesEl: 'Διακόσμηση', namesEn: 'Decorations', namesRu: 'Декор', parentId: get('events').id, order: 7 },
])
// ─── Photo & Video ───────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'photo-editing', icon: '🖼️', namesEl: 'Επεξεργασία Φωτογραφιών', namesEn: 'Photo Editing', namesRu: 'Обработка фото', parentId: get('photo-video').id, order: 1 },
{ slug: 'video-editing', icon: '✂️', namesEl: 'Μοντάζ Βίντεο', namesEn: 'Video Editing', namesRu: 'Монтаж видео', parentId: get('photo-video').id, order: 2 },
{ slug: 'drone', icon: '🚁', namesEl: 'Drone & Εναέρια Λήψη', namesEn: 'Drone Filming', namesRu: 'Аэросъёмка', parentId: get('photo-video').id, order: 3 },
{ slug: 'portrait', icon: '🤳', namesEl: 'Πορτρέτο & Shooting', namesEn: 'Portrait & Shoot',namesRu: 'Портретная съёмка', parentId: get('photo-video').id, order: 4 },
])
// ─── Pets ────────────────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'vet', icon: '🏥', namesEl: 'Κτηνίατρος', namesEn: 'Veterinarian', namesRu: 'Ветеринар', parentId: get('pets').id, order: 1 },
{ slug: 'grooming', icon: '✂️', namesEl: 'Grooming Κατοικίδιων',namesEn: 'Pet Grooming', namesRu: 'Груминг', parentId: get('pets').id, order: 2 },
{ slug: 'dog-walking', icon: '🦮', namesEl: 'Βόλτα Σκύλου', namesEn: 'Dog Walking', namesRu: 'Выгул собаки', parentId: get('pets').id, order: 3 },
{ slug: 'pet-sitting', icon: '🐕', namesEl: 'Φύλαξη Κατοικίδιου', namesEn: 'Pet Sitting', namesRu: 'Передержка животных', parentId: get('pets').id, order: 4 },
{ slug: 'pet-training',icon: '🐩', namesEl: 'Εκπαίδευση Σκύλου', namesEn: 'Dog Training', namesRu: 'Дрессировка собак', parentId: get('pets').id, order: 5 },
])
// ─── Automotive ──────────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'car-repair', icon: '🔧', namesEl: 'Επισκευή Αυτοκινήτου', namesEn: 'Car Repair', namesRu: 'Ремонт автомобиля', parentId: get('auto').id, order: 1 },
{ slug: 'detailing', icon: '✨', namesEl: 'Detailing', namesEn: 'Car Detailing', namesRu: 'Детейлинг', parentId: get('auto').id, order: 2 },
{ slug: 'auto-electric', icon: '⚡', namesEl: 'Αυτοκινητική Ηλεκτρική',namesEn: 'Auto Electrical', namesRu: 'Автоэлектрик', parentId: get('auto').id, order: 3 },
{ slug: 'tow-truck', icon: '🚐', namesEl: 'Ρυμούλκηση', namesEn: 'Towing', namesRu: 'Эвакуатор', parentId: get('auto').id, order: 4 },
{ slug: 'tire-service', icon: '🛞', namesEl: 'Ελαστικά', namesEn: 'Tire Service', namesRu: 'Шиномонтаж', parentId: get('auto').id, order: 5 },
])
// ─── Legal ───────────────────────────────────────────────────────────────
await db.insert(categories).values([
{ slug: 'legal-consultation',icon: '📋', namesEl: 'Νομική Συμβουλή', namesEn: 'Legal Consultation', namesRu: 'Юридическая консультация', parentId: get('legal').id, order: 1 },
{ slug: 'contract', icon: '📄', namesEl: 'Συντακτή Συμβάσεων', namesEn: 'Contracts & Docs', namesRu: 'Составление договоров', parentId: get('legal').id, order: 2 },
{ slug: 'real-estate-legal', icon: '🏠', namesEl: 'Ακίνητα & Συμβόλαια', namesEn: 'Real Estate Law', namesRu: 'Недвижимость и нотариус', parentId: get('legal').id, order: 3 },
])
// ─── Accounting ──────────────────────────────────────────────────────────
await db.insert(categories).values(withSvgIcons([
{ slug: 'tax-declaration', icon: '📊', namesEl: 'Φορολογική Δήλωση', namesEn: 'Tax Declaration', namesRu: 'Налоговая декларация', parentId: get('accounting').id, order: 1 },
{ slug: 'bookkeeping', icon: '📒', namesEl: 'Λογιστήριο', namesEn: 'Bookkeeping', namesRu: 'Бухгалтерский учёт', parentId: get('accounting').id, order: 2 },
{ slug: 'payroll', icon: '💰', namesEl: 'Μισθοδοσία', namesEn: 'Payroll', namesRu: 'Расчёт зарплаты', parentId: get('accounting').id, order: 3 },
]))
console.log('✅ Categories seeded (15 root + subcategories)')
}
async function seedLocations() {
// ─── Seed locations ─────────────────────────────────────────────────────
await db.delete(locations)
const cities = await db.insert(locations).values([
{ slug: 'athens', nameEl: 'Αθήνα', nameEn: 'Athens', nameRu: 'Афины', order: 1 },
{ slug: 'thessaloniki', nameEl: 'Θεσσαλονίκη', nameEn: 'Thessaloniki', nameRu: 'Салоники', order: 2 },
{ slug: 'patras', nameEl: 'Πάτρα', nameEn: 'Patras', nameRu: 'Патры', order: 3 },
{ slug: 'heraklion', nameEl: 'Ηράκλειο', nameEn: 'Heraklion', nameRu: 'Ираклион', order: 4 },
{ slug: 'larissa', nameEl: 'Λάρισα', nameEn: 'Larissa', nameRu: 'Лариса', order: 5 },
{ slug: 'volos', nameEl: 'Βόλος', nameEn: 'Volos', nameRu: 'Волос', order: 6 },
{ slug: 'ioannina', nameEl: 'Ιωάννινα', nameEn: 'Ioannina', nameRu: 'Янина', order: 7 },
{ slug: 'chania', nameEl: 'Χανιά', nameEn: 'Chania', nameRu: 'Ханья', order: 8 },
{ slug: 'rhodes', nameEl: 'Ρόδος', nameEn: 'Rhodes', nameRu: 'Родос', order: 9 },
{ slug: 'kavala', nameEl: 'Καβάλα', nameEn: 'Kavala', nameRu: 'Кавала', order: 10 },
{ slug: 'corfu', nameEl: 'Κέρκυρα', nameEn: 'Corfu', nameRu: 'Керкира', order: 11 },
{ slug: 'kalamata', nameEl: 'Καλαμάτα', nameEn: 'Kalamata', nameRu: 'Каламата', order: 12 },
{ slug: 'piraeus', nameEl: 'Πειραιάς', nameEn: 'Piraeus', nameRu: 'Пирей', order: 13 },
{ slug: 'remote', nameEl: 'Εξ Αποστάσεως', nameEn: 'Remote', nameRu: 'Удалённо', order: 99 },
]).returning()
const city = (slug: string) => cities.find((c) => c.slug === slug)!
// Athens districts
await db.insert(locations).values([
{ slug: 'athens-center', nameEl: 'Κέντρο Αθήνας', nameEn: 'Athens Center', nameRu: 'Центр Афин', parentId: city('athens').id, order: 1 },
{ slug: 'kolonaki', nameEl: 'Κολωνάκι', nameEn: 'Kolonaki', nameRu: 'Колонаки', parentId: city('athens').id, order: 2 },
{ slug: 'exarchia', nameEl: 'Εξάρχεια', nameEn: 'Exarchia', nameRu: 'Эксархия', parentId: city('athens').id, order: 3 },
{ slug: 'monastiraki', nameEl: 'Μοναστηράκι', nameEn: 'Monastiraki', nameRu: 'Монастираки', parentId: city('athens').id, order: 4 },
{ slug: 'koukaki', nameEl: 'Κουκάκι', nameEn: 'Koukaki', nameRu: 'Кукаки', parentId: city('athens').id, order: 5 },
{ slug: 'pagkrati', nameEl: 'Παγκράτι', nameEn: 'Pagkrati', nameRu: 'Пагкрати', parentId: city('athens').id, order: 6 },
{ slug: 'nea-smyrni', nameEl: 'Νέα Σμύρνη', nameEn: 'Nea Smyrni', nameRu: 'Неа Смирни', parentId: city('athens').id, order: 7 },
{ slug: 'glyfada', nameEl: 'Γλυφάδα', nameEn: 'Glyfada', nameRu: 'Глифада', parentId: city('athens').id, order: 8 },
{ slug: 'kifisia', nameEl: 'Κηφισιά', nameEn: 'Kifisia', nameRu: 'Кифисья', parentId: city('athens').id, order: 9 },
{ slug: 'marousi', nameEl: 'Μαρούσι', nameEn: 'Marousi', nameRu: 'Маруси', parentId: city('athens').id, order: 10 },
{ slug: 'kallithea', nameEl: 'Καλλιθέα', nameEn: 'Kallithea', nameRu: 'Каллифея', parentId: city('athens').id, order: 11 },
{ slug: 'peristeri', nameEl: 'Περιστέρι', nameEn: 'Peristeri', nameRu: 'Периссери', parentId: city('athens').id, order: 12 },
{ slug: 'ilion', nameEl: 'Ίλιον', nameEn: 'Ilion', nameRu: 'Илион', parentId: city('athens').id, order: 13 },
{ slug: 'chalandri', nameEl: 'Χαλάνδρι', nameEn: 'Chalandri', nameRu: 'Халандри', parentId: city('athens').id, order: 14 },
{ slug: 'psychiko', nameEl: 'Ψυχικό', nameEn: 'Psychiko', nameRu: 'Психико', parentId: city('athens').id, order: 15 },
])
// Thessaloniki districts
await db.insert(locations).values([
{ slug: 'thessaloniki-center', nameEl: 'Κέντρο Θεσσαλονίκης', nameEn: 'Thessaloniki Center', nameRu: 'Центр Салоников', parentId: city('thessaloniki').id, order: 1 },
{ slug: 'kalamaria', nameEl: 'Καλαμαριά', nameEn: 'Kalamaria', nameRu: 'Каламарья', parentId: city('thessaloniki').id, order: 2 },
{ slug: 'stavroupoli', nameEl: 'Σταυρούπολη', nameEn: 'Stavroupoli', nameRu: 'Ставруполи', parentId: city('thessaloniki').id, order: 3 },
{ slug: 'ampelokipoi', nameEl: 'Αμπελόκηποι', nameEn: 'Ampelokipoi', nameRu: 'Амбелокипи', parentId: city('thessaloniki').id, order: 4 },
{ slug: 'evosmos', nameEl: 'Εύοσμος', nameEn: 'Evosmos', nameRu: 'Эвосмос', parentId: city('thessaloniki').id, order: 5 },
{ slug: 'panorama-th', nameEl: 'Πανόραμα', nameEn: 'Panorama', nameRu: 'Панорама', parentId: city('thessaloniki').id, order: 6 },
{ slug: 'pylaia', nameEl: 'Πυλαία', nameEn: 'Pylaia', nameRu: 'Пилея', parentId: city('thessaloniki').id, order: 7 },
{ slug: 'neapoli-th', nameEl: 'Νεάπολη', nameEn: 'Neapoli', nameRu: 'Неаполи', parentId: city('thessaloniki').id, order: 8 },
])
// Patras districts
await db.insert(locations).values([
{ slug: 'patras-center', nameEl: 'Κέντρο Πάτρας', nameEn: 'Patras Center', nameRu: 'Центр Патр', parentId: city('patras').id, order: 1 },
{ slug: 'rio', nameEl: 'Ρίο', nameEn: 'Rio', nameRu: 'Рио', parentId: city('patras').id, order: 2 },
{ slug: 'agios-vasileios',nameEl: 'Άγιος Βασίλειος',nameEn: 'Agios Vasileios',nameRu: 'Агиос-Василиос', parentId: city('patras').id, order: 3 },
])
// Heraklion districts
await db.insert(locations).values([
{ slug: 'heraklion-center', nameEl: 'Κέντρο Ηρακλείου', nameEn: 'Heraklion Center', nameRu: 'Центр Ираклиона', parentId: city('heraklion').id, order: 1 },
{ slug: 'nea-alikarnassos', nameEl: 'Νέα Αλικαρνασσός', nameEn: 'Nea Alikarnassos', nameRu: 'Неа-Аликарнасос', parentId: city('heraklion').id, order: 2 },
{ slug: 'gazi', nameEl: 'Γάζι', nameEn: 'Gazi', nameRu: 'Гази', parentId: city('heraklion').id, order: 3 },
])
// Piraeus districts
await db.insert(locations).values([
{ slug: 'piraeus-center', nameEl: 'Κέντρο Πειραιά', nameEn: 'Piraeus Center', nameRu: 'Центр Пирея', parentId: city('piraeus').id, order: 1 },
{ slug: 'kastella', nameEl: 'Καστέλλα', nameEn: 'Kastella', nameRu: 'Кастелла', parentId: city('piraeus').id, order: 2 },
{ slug: 'pasalimani', nameEl: 'Πασαλιμάνι', nameEn: 'Pasalimani', nameRu: 'Пасалимани', parentId: city('piraeus').id, order: 3 },
{ slug: 'keratsini', nameEl: 'Κερατσίνι', nameEn: 'Keratsini', nameRu: 'Кератсини', parentId: city('piraeus').id, order: 4 },
{ slug: 'nikaia', nameEl: 'Νίκαια', nameEn: 'Nikaia', nameRu: 'Никея', parentId: city('piraeus').id, order: 5 },
])
console.log('✅ Locations seeded (14 cities + districts)')
}
async function seedPlans() {
// Upsert 6 plans: customer (free/pro/ultimate) + specialist (free/pro/ultimate)
const planDefs = [
// ── Customer Plans ──
{
id: 'customer_free',
name: 'Customer Free',
description: 'Basic free plan for customers',
role: 'customer' as const,
tier: 'free' as const,
price: '0',
maxMessagesPerDay: 20,
maxOrdersPerDay: 5,
maxProfiles: 1,
canContactFreePlan: false,
canContactProPlan: true,
canContactAll: false,
canShowContactInfo: false,
canViewPhone: false,
hasFavorites: false,
hasVerifiedBadge: false,
hasNeedHelpStatus: false,
hasCanHelpNowStatus: false,
searchBoost: 0,
offersMultiplier: '1.00',
hasAutoMatch: false,
isDefault: false,
isActive: true,
order: 1,
},
{
id: 'customer_pro',
name: 'Customer Pro',
description: 'Pro plan for customers — more responses, priority messaging',
role: 'customer' as const,
tier: 'pro' as const,
price: '9.99',
maxMessagesPerDay: null,
maxOrdersPerDay: null,
maxProfiles: 1,
canContactFreePlan: true,
canContactProPlan: true,
canContactAll: false,
canShowContactInfo: true,
canViewPhone: false,
hasFavorites: true,
hasVerifiedBadge: false,
hasNeedHelpStatus: true,
hasCanHelpNowStatus: false,
searchBoost: 0,
offersMultiplier: '3.00',
hasAutoMatch: false,
durationDays: 30,
isDefault: false,
isActive: true,
order: 2,
},
{
id: 'customer_ultimate',
name: 'Customer Ultimate',
description: 'Ultimate plan for customers — all features, priority in everything',
role: 'customer' as const,
tier: 'ultimate' as const,
price: '24.99',
maxMessagesPerDay: null,
maxOrdersPerDay: null,
maxProfiles: null,
canContactFreePlan: true,
canContactProPlan: true,
canContactAll: true,
canShowContactInfo: true,
canViewPhone: true,
hasFavorites: true,
hasVerifiedBadge: true,
hasNeedHelpStatus: true,
hasCanHelpNowStatus: true,
hasPersonalSite: true,
searchBoost: 2,
offersMultiplier: '3.00',
hasAutoMatch: true,
durationDays: 30,
isDefault: false,
isActive: true,
order: 3,
},
]
for (const plan of planDefs) {
const [existing] = await db.select({ id: plans.id }).from(plans).where(eq(plans.id, plan.id))
if (existing) {
await db.update(plans).set({ ...plan, updatedAt: new Date() }).where(eq(plans.id, plan.id))
} else {
await db.insert(plans).values(plan)
}
}
console.log('✅ 3 unified plans seeded (free/pro/ultimate)')
}
seed().catch((e) => {
console.error(e)
process.exit(1)
})