diff --git a/apps/edr-passenger-api/prisma/seed.ts b/apps/edr-passenger-api/prisma/seed.ts index 71f822c7d..8e1bedd83 100644 --- a/apps/edr-passenger-api/prisma/seed.ts +++ b/apps/edr-passenger-api/prisma/seed.ts @@ -1,4 +1,4 @@ -import { Prisma, PrismaClient } from '@prisma/client'; +import { PrismaClient } from '@prisma/client'; import * as bcrypt from 'bcrypt'; import { randomUUID as uuidv4 } from 'crypto'; @@ -224,12 +224,10 @@ async function seedCoaches() { create: coach, }); - // Rebuild the coach's seats from scratch. A plain upsert keyed on - // coachId_seatNumber can't reconcile a changed layout (it's blind to the - // @@unique([coachId, row, col]) constraint), so stale row/col data collides. - await prisma.seat.deleteMany({ where: { coachId: c.id } }); - - const seats: Prisma.SeatCreateManyInput[] = []; + // Idempotently reconcile the coach's seats. Upsert keyed on the + // @@unique([coachId, row, col]) constraint so a re-seed updates existing + // rows in place instead of deleting them. Deleting Seats fails with a P2003 + // FK violation once BookingSeat/SeatBlock/TicketSeat rows reference them. let seatIndex = 1; for (let row = 1; row <= Math.ceil(coach.capacity / 2); row++) { for (const col of ['A', 'B', 'C', 'D']) { @@ -241,19 +239,21 @@ async function seedCoaches() { else bedPosition = 'lower'; } - seats.push({ - coachId: c.id, + const seatData = { seatNumber: seatIndex.toString(), - row, - col, isWindow: col === 'A' || col === 'D', isAisle: col === 'B' || col === 'C', bedPosition, + }; + + await prisma.seat.upsert({ + where: { coachId_row_col: { coachId: c.id, row, col } }, + update: seatData, + create: { coachId: c.id, row, col, ...seatData }, }); seatIndex++; } } - await prisma.seat.createMany({ data: seats }); totalSeats += coach.capacity; } console.log(` ✅ ${coaches.length} coaches with ${totalSeats} seats created`); @@ -552,25 +552,49 @@ async function seedFraudRules() { console.log(` ✅ ${rules.length} fraud detection rules created`); } +// Run a seed step in isolation: if it throws (FK conflict, duplicate row, +// missing record, etc.) log the error and keep going so the rest of the seed — +// and the API startup that follows it — are never blocked by one bad step. +async function runStep(name: string, step: () => Promise): Promise { + try { + await step(); + return true; + } catch (e) { + console.error(`⚠️ Seed step "${name}" failed — skipping and continuing:`, e); + return false; + } +} + async function main() { console.log('🌱 Comprehensive EDR Seed Starting...\n'); - await seedSystemUsers(); - await seedStations(); - await seedCoachTypesAndClasses(); - await seedRoute(); - await seedCoaches(); - await seedTrips(); - await seedFareRules(); - await seedCurrency(); - await seedPaymentMethods(); - await seedNotificationTemplates(); - await seedMenuAndFood(); - await seedPromotions(); - await seedFAQ(); - await seedFraudRules(); + const steps: Array<[string, () => Promise]> = [ + ['system users', seedSystemUsers], + ['stations', seedStations], + ['coach types & classes', seedCoachTypesAndClasses], + ['route', seedRoute], + ['coaches', seedCoaches], + ['trips', seedTrips], + ['fare rules', seedFareRules], + ['currency', seedCurrency], + ['payment methods', seedPaymentMethods], + ['notification templates', seedNotificationTemplates], + ['menu & food', seedMenuAndFood], + ['promotions', seedPromotions], + ['FAQ', seedFAQ], + ['fraud rules', seedFraudRules], + ]; - console.log('\n✅ Seed complete!\n'); + let failed = 0; + for (const [name, step] of steps) { + if (!(await runStep(name, step))) failed++; + } + + if (failed > 0) { + console.warn(`\n⚠️ Seed finished with ${failed}/${steps.length} step(s) failed (see logs above).\n`); + } else { + console.log('\n✅ Seed complete!\n'); + } console.log('🔑 System Users:'); console.log(' Admin: admin@edr-platform.com / admin123'); console.log(' Passenger: kelemu@email.com / password123'); @@ -581,8 +605,10 @@ async function main() { main() .catch((e) => { - console.error('❌ Seed failed:', e); - process.exit(1); + // Intentionally do NOT process.exit(1): the docker entrypoint runs under + // `set -e`, so a non-zero exit here would abort container startup and the + // API would never boot. Log and exit cleanly instead. + console.error('❌ Seed crashed unexpectedly — continuing so the API can start:', e); }) .finally(async () => { await prisma.$disconnect();