From fc92c69ca5d463ccfaef1b447cf6a2364571d542 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 19:28:34 +0000 Subject: [PATCH 1/2] fix: handle insecure UUID contexts and persist cleared tax id Agent-Logs-Url: https://github.com/kittendevv/Invio/sessions/76566c22-23a0-4bb7-aacd-7d85fe737465 Co-authored-by: kittendevv <121097932+kittendevv@users.noreply.github.com> --- backend/src/controllers/settings.ts | 27 +++++++++---------- .../src/lib/components/InvoiceEditor.svelte | 23 +++++++++++++--- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/backend/src/controllers/settings.ts b/backend/src/controllers/settings.ts index 7446ad9..0bf3866 100644 --- a/backend/src/controllers/settings.ts +++ b/backend/src/controllers/settings.ts @@ -13,8 +13,16 @@ export const getSettings = () => { export const updateSettings = (data: Record) => { const db = getDatabase(); const results: Setting[] = []; + const canonicalKey = (key: string) => + ({ + taxId: "companyTaxId", + phone: "companyPhone", + email: "companyEmail", + countryCode: "companyCountryCode", + })[key] ?? key; for (const [key, raw] of Object.entries(data)) { + const targetKey = canonicalKey(key); // Treat explicit empty strings for certain keys as clearing the setting const shouldClear = [ "companyTaxId", @@ -30,24 +38,15 @@ export const updateSettings = (data: Record) => { "locale", ].includes(key) && String(raw).trim() === ""; - if (shouldClear) { - // delete the setting row if present - db.query("DELETE FROM settings WHERE key = ?", [ - key === "taxId" ? "companyTaxId" : key, - ]); - results.push({ key: key === "taxId" ? "companyTaxId" : key, value: "" }); - continue; - } - - const value = String(raw); + const value = shouldClear ? "" : String(raw); // Upsert the setting - const existing = db.query("SELECT * FROM settings WHERE key = ?", [key]); + const existing = db.query("SELECT * FROM settings WHERE key = ?", [targetKey]); if (existing.length > 0) { - db.query("UPDATE settings SET value = ? WHERE key = ?", [value, key]); + db.query("UPDATE settings SET value = ? WHERE key = ?", [value, targetKey]); } else { - db.query("INSERT INTO settings (key, value) VALUES (?, ?)", [key, value]); + db.query("INSERT INTO settings (key, value) VALUES (?, ?)", [targetKey, value]); } - results.push({ key, value }); + results.push({ key: targetKey, value }); } return results; diff --git a/frontend/src/lib/components/InvoiceEditor.svelte b/frontend/src/lib/components/InvoiceEditor.svelte index 6f122c7..0ccf5d5 100644 --- a/frontend/src/lib/components/InvoiceEditor.svelte +++ b/frontend/src/lib/components/InvoiceEditor.svelte @@ -13,6 +13,23 @@ let saving = $state(false); let error = $state(""); + function createItemId() { + if (typeof globalThis.crypto?.randomUUID === "function") { + return globalThis.crypto.randomUUID(); + } + + if (typeof globalThis.crypto?.getRandomValues === "function") { + const bytes = new Uint8Array(16); + globalThis.crypto.getRandomValues(bytes); + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")); + return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`; + } + + return `${Date.now()}-${Math.random().toString(16).slice(2)}`; + } + let form = $state({ customerId: initInvoice?.customerId || "", invoiceNumber: initInvoice?.invoiceNumber ?? initNextInvoiceNumber, @@ -32,13 +49,13 @@ initInvoice?.items?.length ? initInvoice.items.map((i: any) => ({ ...i, - id: crypto.randomUUID(), + id: createItemId(), unit: i.unit || "", productId: i.productId || "", })) : [ { - id: crypto.randomUUID(), + id: createItemId(), productId: "", description: "", quantity: 1, @@ -56,7 +73,7 @@ function addItem() { items.push({ - id: crypto.randomUUID(), + id: createItemId(), productId: "", description: "", quantity: 1, From eeedb403b54ff5bd4ca5047d6d36d80a06ea12a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 19:29:43 +0000 Subject: [PATCH 2/2] refactor: normalize clearable setting keys via canonical mapping Agent-Logs-Url: https://github.com/kittendevv/Invio/sessions/76566c22-23a0-4bb7-aacd-7d85fe737465 Co-authored-by: kittendevv <121097932+kittendevv@users.noreply.github.com> --- backend/src/controllers/settings.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/backend/src/controllers/settings.ts b/backend/src/controllers/settings.ts index 0bf3866..14ac7d7 100644 --- a/backend/src/controllers/settings.ts +++ b/backend/src/controllers/settings.ts @@ -13,6 +13,7 @@ export const getSettings = () => { export const updateSettings = (data: Record) => { const db = getDatabase(); const results: Setting[] = []; + // Normalize legacy/frontend alias keys to canonical settings table keys. const canonicalKey = (key: string) => ({ taxId: "companyTaxId", @@ -20,23 +21,20 @@ export const updateSettings = (data: Record) => { email: "companyEmail", countryCode: "companyCountryCode", })[key] ?? key; + const clearableKeys = new Set([ + "companyTaxId", + "companyPhone", + "companyEmail", + "companyCountryCode", + "companyCity", + "companyPostalCode", + "locale", + ]); for (const [key, raw] of Object.entries(data)) { const targetKey = canonicalKey(key); // Treat explicit empty strings for certain keys as clearing the setting - const shouldClear = [ - "companyTaxId", - "taxId", // alias that may slip through - "companyPhone", - "phone", // alias - "companyEmail", - "email", // alias - "companyCountryCode", - "countryCode", // alias - "companyCity", - "companyPostalCode", - "locale", - ].includes(key) && String(raw).trim() === ""; + const shouldClear = clearableKeys.has(targetKey) && String(raw).trim() === ""; const value = shouldClear ? "" : String(raw); // Upsert the setting