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>
This commit is contained in:
78
prisma/scripts/backfill-locations.ts
Normal file
78
prisma/scripts/backfill-locations.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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());
|
||||
Reference in New Issue
Block a user