diff --git a/data/units.json b/data/units.json index 738768a..35f1340 100644 --- a/data/units.json +++ b/data/units.json @@ -24,7 +24,9 @@ "labelPlural": "barrels", "symbol": "bbl", "group": "dickloads", - "toBase": 19 + "toBase": 13.319, + "description": "Weight is based on pure water at 4 degrees celsius.", + "hidden": false }, { "id": "hogshead", @@ -101,6 +103,14 @@ "group": "another-group", "toBase": 1, "hidden": true + }, + { + "id": "us-fluid-bbl", + "label": "US fluid barrel", + "labelPlural": "US fluid barrels", + "symbol": "us fluid bbl", + "group": "dickloads", + "toBase": 9.9894438 } ], "groups": [ diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 067a90c..0cf17a9 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -918,8 +918,8 @@

{unitFormNew ? 'New Unit' : 'Edit Unit'}

@@ -977,8 +977,8 @@

{groupFormNew ? 'New Group' : 'Edit Group'}

diff --git a/src/routes/admin/api/groups/[id]/+server.ts b/src/routes/admin/api/groups/[id]/+server.ts index 0be428a..091015b 100644 --- a/src/routes/admin/api/groups/[id]/+server.ts +++ b/src/routes/admin/api/groups/[id]/+server.ts @@ -1,9 +1,18 @@ import { json } from '@sveltejs/kit'; import { loadData, saveData } from '$lib/server/data'; -import { authRequest } from '$lib/server/auth'; +import { authRequest, KEBAB_RE } from '$lib/server/auth'; import type { RequestHandler } from './$types'; import type { Group } from '$lib/types'; +function renameIdInDescription(desc: string, oldId: string, newId: string): string { + const re = new RegExp(`\\{${escapeRegex(oldId)}(:[^}]*)?\\}`, 'g'); + return desc.replace(re, (_, suffix) => `{${newId}${suffix ?? ''}}`); +} + +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + export const PUT: RequestHandler = async ({ request, params }) => { if (!authRequest(request)) return json({ error: 'Unauthorized' }, { status: 401 }); @@ -17,6 +26,18 @@ export const PUT: RequestHandler = async ({ request, params }) => { const patch = body as Partial; const currentGroup = data.groups[gIdx]; + const newId = patch.id; + const idChanging = newId !== undefined && newId !== id; + + // Validate new ID if changing + if (idChanging) { + if (!KEBAB_RE.test(newId!)) { + return json({ error: 'id must be kebab-case' }, { status: 400 }); + } + if (data.groups.some((g) => g.id === newId)) { + return json({ error: `Group id '${newId}' already exists` }, { status: 400 }); + } + } // Special case: changing baseUnitId requires recalculating all unit toBase values if (patch.baseUnitId !== undefined && patch.baseUnitId !== currentGroup.baseUnitId) { @@ -25,10 +46,7 @@ export const PUT: RequestHandler = async ({ request, params }) => { if (!newBaseUnit) { return json({ error: `baseUnitId '${newBaseId}' not found in this group` }, { status: 400 }); } - - const X = newBaseUnit.toBase; // Current toBase of the new base unit - // Recalculate all units in this group: unit.toBase = unit.toBase / X - // Then set new base unit toBase = 1.0 + const X = newBaseUnit.toBase; for (const unit of data.units) { if (unit.group === id) { unit.toBase = unit.id === newBaseId ? 1.0 : unit.toBase / X; @@ -38,8 +56,22 @@ export const PUT: RequestHandler = async ({ request, params }) => { const updated: Group = { ...currentGroup, ...patch }; data.groups[gIdx] = updated; - saveData(data); + // Cascade ID rename across the dataset + if (idChanging) { + // Update unit.group references + for (const u of data.units) { + if (u.group === id) u.group = newId!; + } + // Update {id:*} placeholders in all unit descriptions + for (const u of data.units) { + if (u.description) { + u.description = renameIdInDescription(u.description, id, newId!); + } + } + } + + saveData(data); return json(updated); }; diff --git a/src/routes/admin/api/units/[id]/+server.ts b/src/routes/admin/api/units/[id]/+server.ts index 5719948..0c36869 100644 --- a/src/routes/admin/api/units/[id]/+server.ts +++ b/src/routes/admin/api/units/[id]/+server.ts @@ -4,6 +4,17 @@ import { authRequest, KEBAB_RE } from '$lib/server/auth'; import type { RequestHandler } from './$types'; import type { Unit } from '$lib/types'; +/** Replace all {oldId}, {oldId:field} placeholder tokens in a description string. */ +function renameIdInDescription(desc: string, oldId: string, newId: string): string { + // Match {oldId} and {oldId:anything} + const re = new RegExp(`\\{${escapeRegex(oldId)}(:[^}]*)?\\}`, 'g'); + return desc.replace(re, (_, suffix) => `{${newId}${suffix ?? ''}}`); +} + +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + export const PUT: RequestHandler = async ({ request, params }) => { if (!authRequest(request)) return json({ error: 'Unauthorized' }, { status: 401 }); @@ -16,14 +27,16 @@ export const PUT: RequestHandler = async ({ request, params }) => { if (idx === -1) return json({ error: 'Unit not found' }, { status: 404 }); const patch = body as Partial; + const newId = patch.id; + const idChanging = newId !== undefined && newId !== id; - // If id is changing, validate new id - if (patch.id !== undefined && patch.id !== id) { - if (!KEBAB_RE.test(patch.id)) { + // Validate new ID if changing + if (idChanging) { + if (!KEBAB_RE.test(newId!)) { return json({ error: 'id must be kebab-case' }, { status: 400 }); } - if (data.units.some((u) => u.id === patch.id)) { - return json({ error: `Unit id '${patch.id}' already exists` }, { status: 400 }); + if (data.units.some((u) => u.id === newId)) { + return json({ error: `Unit id '${newId}' already exists` }, { status: 400 }); } } if (patch.toBase !== undefined && patch.toBase <= 0) { @@ -35,8 +48,22 @@ export const PUT: RequestHandler = async ({ request, params }) => { const updated: Unit = { ...data.units[idx], ...patch }; data.units[idx] = updated; - saveData(data); + // Cascade ID rename across the dataset + if (idChanging) { + // Update group.baseUnitId references + for (const g of data.groups) { + if (g.baseUnitId === id) g.baseUnitId = newId!; + } + // Update {id:*} placeholders in all unit descriptions + for (const u of data.units) { + if (u.description) { + u.description = renameIdInDescription(u.description, id, newId!); + } + } + } + + saveData(data); return json(updated); };