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);
};