/** * v0.10.0 Location-spine backfill (Release A). * * Moves the live Garden off the freeform `Plant.zone` string onto the shared * `Location` tree WITHOUT data loss. Run once, right after `migrate deploy`: * * npx tsx prisma/scripts/backfill-locations.ts * * Idempotent: re-running only fills in plants that still lack a locationId and * reuses Locations matched case-insensitively by name. Plant.zone is left * intact (the dual-write/read safety net) — it is dropped in Release B. */ import { PrismaClient } from "@prisma/client"; import { createId } from "@paralleldrive/cuid2"; const db = new PrismaClient(); /** Normalize a zone string for dedup: trim + collapse inner whitespace, case-insensitive key. */ function normKey(zone: string): string { return zone.trim().replace(/\s+/g, " ").toLowerCase(); } async function main() { // Plants that have a zone but no location yet. const plants = await db.plant.findMany({ where: { zone: { not: null }, locationId: null }, select: { id: true, zone: true }, }); if (plants.length === 0) { console.log("Backfill: no plants need a Location. Done."); return; } // Seed the lookup with any AREA Locations that already exist (idempotent re-runs). const existing = await db.location.findMany({ where: { kind: "AREA" }, select: { id: true, name: true }, }); const byKey = new Map(); // normKey → locationId for (const loc of existing) byKey.set(normKey(loc.name), loc.id); let createdLocations = 0; let mappedPlants = 0; for (const plant of plants) { const raw = (plant.zone ?? "").trim().replace(/\s+/g, " "); if (!raw) continue; // empty/whitespace-only zone → leave unmapped const key = normKey(raw); let locationId = byKey.get(key); if (!locationId) { locationId = createId(); await db.location.create({ data: { id: locationId, name: raw, kind: "AREA" }, }); byKey.set(key, locationId); createdLocations++; } await db.plant.update({ where: { id: plant.id }, data: { locationId } }); mappedPlants++; } console.log( `Backfill complete: created ${createdLocations} Location(s), mapped ${mappedPlants} plant(s).`, ); console.log( "Review the Locations list in the UI and merge any near-duplicates before Release B drops Plant.zone.", ); } main() .catch((e) => { console.error("Backfill failed:", e); process.exit(1); }) .finally(() => db.$disconnect());