/** * Data migration script for existing deployments upgrading to v2. * * Run with: npx tsx prisma/seed-migration.ts * * This script: * 1. Creates a SystemConfig singleton (setup complete) * 2. Creates a default Organization from existing data * 3. Creates a default Location * 4. Creates a default "Sunday Service" CollectionDay * 5. Migrates existing Users into OrgMembers * 6. Backfills organizationId and locationId on ResponseCards * 7. Backfills collectionDayId using existing dates */ import "dotenv/config"; import { PrismaClient } from "../src/generated/prisma/client"; import { PrismaPg } from "@prisma/adapter-pg"; import pg from "pg"; const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }); const adapter = new PrismaPg(pool); const prisma = new PrismaClient({ adapter }); async function main() { console.log("Starting v2 data migration...\n"); // 1. SystemConfig const existingConfig = await prisma.systemConfig.findUnique({ where: { id: "singleton" }, }); if (existingConfig?.isSetupComplete) { console.log("Setup already marked complete. Skipping migration."); return; } await prisma.systemConfig.upsert({ where: { id: "singleton" }, update: { isSetupComplete: true, setupStep: 3 }, create: { id: "singleton", isSetupComplete: true, setupStep: 3 }, }); console.log("[1/7] SystemConfig created (setup complete)"); // 2. Default Organization let org = await prisma.organization.findFirst(); if (!org) { org = await prisma.organization.create({ data: { name: "My Church", slug: "my-church", type: "church", timezone: "America/Chicago", }, }); console.log(`[2/7] Created default organization: ${org.name} (${org.id})`); } else { console.log(`[2/7] Organization already exists: ${org.name}`); } // 3. Default Location let location = await prisma.location.findFirst({ where: { organizationId: org.id }, }); if (!location) { location = await prisma.location.create({ data: { name: "Main Campus", organizationId: org.id, }, }); console.log(`[3/7] Created default location: ${location.name} (${location.id})`); } else { console.log(`[3/7] Location already exists: ${location.name}`); } // 4. Default CollectionDay let collectionDay = await prisma.collectionDay.findFirst({ where: { locationId: location.id }, }); if (!collectionDay) { collectionDay = await prisma.collectionDay.create({ data: { locationId: location.id, name: "Sunday Service", dayOfWeek: 0, timeStart: "09:00", timeEnd: "12:00", isRecurring: true, }, }); console.log(`[4/7] Created default collection day: ${collectionDay.name}`); } else { console.log(`[4/7] Collection day already exists: ${collectionDay.name}`); } // 5. Migrate Users to OrgMembers const users = await prisma.user.findMany(); let memberCount = 0; for (const user of users) { const existing = await prisma.orgMember.findUnique({ where: { userId_organizationId: { userId: user.id, organizationId: org.id, }, }, }); if (!existing) { await prisma.orgMember.create({ data: { userId: user.id, organizationId: org.id, role: user.role || "viewer", }, }); memberCount++; } } console.log(`[5/7] Migrated ${memberCount} users to org members (${users.length} total users)`); // 6. Backfill ResponseCard.organizationId and locationId const cardResult = await prisma.responseCard.updateMany({ where: { organizationId: null }, data: { organizationId: org.id, locationId: location.id, }, }); console.log(`[6/7] Backfilled ${cardResult.count} cards with org/location`); // 7. Backfill collectionDayId const cardsWithDates = await prisma.responseCard.findMany({ where: { collectionDayId: null, OR: [ { firstTimeGuestDate: { not: null } }, { createdAt: { not: undefined } }, ], }, select: { id: true, firstTimeGuestDate: true, createdAt: true }, }); let assignedCount = 0; for (const card of cardsWithDates) { const refDate = card.firstTimeGuestDate || card.createdAt; const sunday = new Date(refDate); const day = sunday.getDay(); if (day !== 0) sunday.setDate(sunday.getDate() - day); sunday.setHours(0, 0, 0, 0); await prisma.responseCard.update({ where: { id: card.id }, data: { collectionDayId: collectionDay.id, collectionDate: sunday, }, }); assignedCount++; } console.log(`[7/7] Assigned ${assignedCount} cards to default collection day`); // 8. Backfill ProcessingJob.organizationId await prisma.processingJob.updateMany({ where: { organizationId: null }, data: { organizationId: org.id }, }); console.log("\nMigration complete!"); } main() .catch((e) => { console.error("Migration failed:", e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); await pool.end(); });