import { Router } from "express"; import { db, nextId } from "../db.js"; export const catalogRouter: Router = Router(); catalogRouter.post("/brands", (req, res) => { const { name } = req.body as { name: string }; if (!name?.trim()) return res.status(400).json({ error: "name required" }); const existing = db .prepare<[string], { id: string }>("SELECT id FROM brands WHERE name = ?") .get(name.trim()); if (existing) return res.json({ id: existing.id, name: name.trim() }); const id = nextId("brd", "brands"); db.prepare("INSERT INTO brands (id, name) VALUES (?, ?)").run(id, name.trim()); res.json({ id, name: name.trim() }); }); catalogRouter.patch("/brands/:id", (req, res) => { const { id } = req.params; const { name } = req.body as { name?: string }; if (!name?.trim()) return res.status(400).json({ error: "name required" }); const existing = db .prepare<[string], { id: string }>("SELECT id FROM brands WHERE id = ?") .get(id); if (!existing) return res.status(404).json({ error: "brand not found" }); try { db.prepare("UPDATE brands SET name = ? WHERE id = ?").run(name.trim(), id); res.json({ id, name: name.trim() }); } catch (err) { const msg = err instanceof Error ? err.message : ""; if (msg.includes("UNIQUE")) { return res.status(409).json({ error: "another brand already uses that name" }); } throw err; } }); // Deleting a brand unparents any products and strains that reference it // (brand_id → NULL), so users never lose products when reorganizing. catalogRouter.delete("/brands/:id", (req, res) => { const { id } = req.params; const tx = db.transaction(() => { db.prepare("UPDATE products SET brand_id = NULL WHERE brand_id = ?").run(id); db.prepare("UPDATE strains SET brand_id = NULL WHERE brand_id = ?").run(id); const result = db.prepare("DELETE FROM brands WHERE id = ?").run(id); if (result.changes === 0) throw new Error("not found"); }); try { tx(); res.json({ ok: true }); } catch { res.status(404).json({ error: "brand not found" }); } }); catalogRouter.post("/shops", (req, res) => { const { name, location } = req.body as { name: string; location?: string }; if (!name?.trim()) return res.status(400).json({ error: "name required" }); const id = nextId("shp", "shops"); db.prepare("INSERT INTO shops (id, name, location) VALUES (?, ?, ?)").run( id, name.trim(), location?.trim() ?? null, ); res.json({ id, name: name.trim(), location: location?.trim() ?? null }); }); catalogRouter.patch("/shops/:id", (req, res) => { const { id } = req.params; const { name, location } = req.body as { name?: string; location?: string | null }; const existing = db .prepare<[string], { id: string; name: string; location: string | null }>( "SELECT id, name, location FROM shops WHERE id = ?", ) .get(id); if (!existing) return res.status(404).json({ error: "shop not found" }); const nextName = name?.trim() ? name.trim() : existing.name; const nextLocation = location === undefined ? existing.location : location?.toString().trim() || null; db.prepare("UPDATE shops SET name = ?, location = ? WHERE id = ?").run( nextName, nextLocation, id, ); res.json({ id, name: nextName, location: nextLocation }); }); // Deleting a shop unparents any products that reference it (shop_id → NULL). catalogRouter.delete("/shops/:id", (req, res) => { const { id } = req.params; const tx = db.transaction(() => { db.prepare("UPDATE products SET shop_id = NULL WHERE shop_id = ?").run(id); const result = db.prepare("DELETE FROM shops WHERE id = ?").run(id); if (result.changes === 0) throw new Error("not found"); }); try { tx(); res.json({ ok: true }); } catch { res.status(404).json({ error: "shop not found" }); } }); catalogRouter.post("/bins", (req, res) => { const { name, location, capacity } = req.body as { name: string; location?: string; capacity?: number; }; if (!name?.trim()) return res.status(400).json({ error: "name required" }); const id = nextId("bin", "bins"); const cap = Number.isFinite(capacity) && (capacity as number) > 0 ? Math.floor(capacity as number) : 10; db.prepare("INSERT INTO bins (id, name, location, capacity) VALUES (?, ?, ?, ?)").run( id, name.trim(), location?.trim() ?? null, cap, ); res.json({ id, name: name.trim(), location: location?.trim() ?? null, capacity: cap }); }); catalogRouter.patch("/bins/:id", (req, res) => { const { id } = req.params; const { name, location, capacity } = req.body as { name?: string; location?: string | null; capacity?: number; }; const existing = db .prepare<[string], { id: string; name: string; location: string | null; capacity: number }>( "SELECT id, name, location, capacity FROM bins WHERE id = ?", ) .get(id); if (!existing) return res.status(404).json({ error: "bin not found" }); const nextName = name?.trim() ? name.trim() : existing.name; const nextLocation = location === undefined ? existing.location : location?.toString().trim() || null; const nextCapacity = Number.isFinite(capacity) && (capacity as number) > 0 ? Math.floor(capacity as number) : existing.capacity; db.prepare("UPDATE bins SET name = ?, location = ?, capacity = ? WHERE id = ?").run( nextName, nextLocation, nextCapacity, id, ); res.json({ id, name: nextName, location: nextLocation, capacity: nextCapacity }); }); // Deleting a bin unassigns any products that reference it (bin_id → NULL), // so users never lose products when reorganizing storage. catalogRouter.delete("/bins/:id", (req, res) => { const { id } = req.params; const tx = db.transaction(() => { db.prepare("UPDATE products SET bin_id = NULL WHERE bin_id = ?").run(id); const result = db.prepare("DELETE FROM bins WHERE id = ?").run(id); if (result.changes === 0) throw new Error("not found"); }); try { tx(); res.json({ ok: true }); } catch { res.status(404).json({ error: "bin not found" }); } });