// Bulk Location setup — create a nested tree from a pasted outline, and // (optionally) sweep existing top-level garden areas under a parent. DB-touching; // parsing is pure (src/lib/locations/outline.ts). import { db } from "@/lib/db"; import type { AuthContext } from "@/lib/api/authenticate"; import { recomputeAllPaths } from "@/lib/locations/db"; import { parseLocationOutline } from "@/lib/locations/outline"; export type SetupSummary = { created: number; moved: number }; export async function bulkSetupLocations( ctx: AuthContext, opts: { outline: string; groupGardenUnder?: string }, ): Promise { const nodes = parseLocationOutline(opts.outline); // find-or-create by (name, parentId), case-insensitive. const rows = await db.location.findMany({ select: { id: true, name: true, parentId: true } }); const cache = new Map(); for (const r of rows) cache.set(`${r.parentId ?? ""}:${r.name.toLowerCase()}`, r.id); let created = 0; const stack: { depth: number; id: string }[] = []; // ancestor chain for (const node of nodes) { while (stack.length && stack[stack.length - 1].depth >= node.depth) stack.pop(); const parentId = stack.length ? stack[stack.length - 1].id : null; const key = `${parentId ?? ""}:${node.name.toLowerCase()}`; let id = cache.get(key); if (!id) { const loc = await db.location.create({ data: { name: node.name, kind: "OTHER", parentId } }); id = loc.id; cache.set(key, id); created++; } stack.push({ depth: node.depth, id }); } // Sweep existing top-level places that hold plants under the named parent. let moved = 0; if (opts.groupGardenUnder) { const parent = await db.location.findFirst({ where: { active: true, parentId: null, name: { equals: opts.groupGardenUnder, mode: "insensitive" } }, select: { id: true }, }); if (parent) { const gardenAreas = await db.location.findMany({ where: { active: true, parentId: null, id: { not: parent.id }, plants: { some: { active: true } }, }, select: { id: true }, }); for (const a of gardenAreas) { await db.location.update({ where: { id: a.id }, data: { parentId: parent.id } }); } moved = gardenAreas.length; } } if (created > 0 || moved > 0) await recomputeAllPaths(); await db.auditEvent.create({ data: { action: "location.bulk_setup", actorId: ctx.userId, payload: { created, moved, via: ctx.via } }, }); return { created, moved }; }