#!/bin/sh set -eu MAX_MIGRATION_RETRIES="${MIGRATION_MAX_RETRIES:-6}" MIGRATION_RETRY_DELAY_SECONDS="${MIGRATION_RETRY_DELAY_SECONDS:-2}" ATTEMPT=1 # Auto-resolve any previously failed migrations so deploy can proceed. # This handles the case where a migration partially applied and was fixed # in a subsequent deploy — without this, Prisma refuses to run anything. echo "==> Checking for failed migrations..." MIGRATE_STATUS=$(npx prisma migrate status 2>&1 || true) FAILED=$(echo "$MIGRATE_STATUS" | sed -n 's/.*The `\([^`]*\)` migration.*failed.*/\1/p' | head -1) if [ -n "$FAILED" ]; then echo "==> Found failed migration: $FAILED — marking as rolled back..." npx prisma migrate resolve --rolled-back "$FAILED" fi echo "==> Running database migrations (with retry)..." until npx prisma migrate deploy; do if [ "$ATTEMPT" -ge "$MAX_MIGRATION_RETRIES" ]; then echo "ERROR: Migration failed after ${MAX_MIGRATION_RETRIES} attempts." exit 1 fi echo "Migration attempt ${ATTEMPT} failed. Retrying in ${MIGRATION_RETRY_DELAY_SECONDS}s..." ATTEMPT=$((ATTEMPT + 1)) sleep "$MIGRATION_RETRY_DELAY_SECONDS" done echo "==> Generating Prisma client..." npx prisma generate # Auto-seed on first startup: check if Users table is empty USER_COUNT=$(node -e " const { PrismaClient } = require('@prisma/client'); const p = new PrismaClient(); p.user.count().then(c => { console.log(c); p.\$disconnect(); }).catch(() => { console.log('0'); p.\$disconnect(); }); " 2>/dev/null || echo "0") if [ "$USER_COUNT" = "0" ]; then echo "==> Empty database detected — running seed..." npx prisma db seed || echo "WARNING: Seed script failed." else echo "==> Database already seeded ($USER_COUNT users found), skipping seed." fi # Always sync notification email settings (upsert — safe for existing data) echo "==> Syncing notification email settings..." npx tsx prisma/seed-notification-settings.ts || echo "WARNING: Notification settings sync failed." # Sync team lead links only if there are unlinked submitters UNLINKED_COUNT=$(node -e " const { PrismaClient } = require('@prisma/client'); const p = new PrismaClient(); p.\$queryRaw\` SELECT COUNT(*)::int AS c FROM \"Project\" p WHERE p.\"submittedByUserId\" IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM \"TeamMember\" tm WHERE tm.\"projectId\" = p.id AND tm.\"userId\" = p.\"submittedByUserId\" ) \`.then(r => { console.log(r[0].c); p.\$disconnect(); }).catch(() => { console.log('0'); p.\$disconnect(); }); " 2>/dev/null || echo "0") if [ "$UNLINKED_COUNT" != "0" ]; then echo "==> Syncing ${UNLINKED_COUNT} unlinked team lead links..." npx tsx prisma/seed-team-leads.ts || echo "WARNING: Team lead sync failed." else echo "==> Team lead links already synced, skipping." fi echo "==> Starting application..." # Graceful shutdown: forward SIGTERM/SIGINT to the Node process # so in-flight requests can complete before the container exits. shutdown() { echo "==> Received shutdown signal, stopping gracefully..." kill -TERM "$NODE_PID" 2>/dev/null wait "$NODE_PID" exit $? } trap shutdown TERM INT node server.js & NODE_PID=$! wait "$NODE_PID"