/opt/canhelp/apps/api/src
NameSizeModeActions
lib/-0755rm
middleware/-0755rm
routes/-0755rm
auth.ts156000644editdlrm
db.ts1740644editdlrm
index.ts239420644editdlrm
redis.ts2490644editdlrm
seed.ts830180644editdlrm
socket.ts57540644editdlrm
sync-categories-from-prod.ts27920644editdlrm
translate.ts220300644editdlrm
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) })