Files
Moonbase/prisma/scripts/backfill-locations.ts
tonym 98cabc514d v0.9.1 — Self-update: 403 on trigger mismatch + reliability
webhook returns 200 even when a trigger rule isn't satisfied, which would let
the app's res.ok check falsely report success if the secret drifted. Set
trigger-rule-mismatch-http-response-code: 403 so unauthorized triggers are
clearly rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:44:36 -05:00

79 lines
2.4 KiB
TypeScript

/**
* 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<string, string>(); // 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());