From b3bca954daa0c1300f238af8730f9c17c15de2b6 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Mon, 20 Jul 2026 17:45:31 +0800 Subject: [PATCH] fix: insert other chart profiles directly (#15) --- frontend/src/app/api/chart-profiles/route.ts | 9 +-------- frontend/src/app/page.tsx | 5 ++--- frontend/tests/chart-library-other-profile.test.ts | 12 ++++++++++++ tests/test_supabase_user_data_contract.py | 3 ++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/api/chart-profiles/route.ts b/frontend/src/app/api/chart-profiles/route.ts index e537af1d..af08e3c4 100644 --- a/frontend/src/app/api/chart-profiles/route.ts +++ b/frontend/src/app/api/chart-profiles/route.ts @@ -70,16 +70,9 @@ export async function POST(request: Request) { .single(); ({ data, error } = await query); } else { - const record = { - ...(body.id ? { id: body.id } : {}), - user_id: user.id, - role, - profile: body.profile, - updated_at: updatedAt, - }; ({ data, error } = await supabase .from("chart_profiles") - .upsert(record, { onConflict: "id" }) + .insert({ user_id: user.id, role, profile: body.profile, updated_at: updatedAt }) .select("id, role, profile, updated_at") .single()); } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 15c2ada2..cab54906 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -337,13 +337,12 @@ async function saveCloudChartProfile(record: ChartLibraryRecord) { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - id: record.role === "self" ? undefined : record.id, role: record.role, profile: record.profile, }), }); - if (!response.ok) throw new Error("cloud_chart_profile_save_failed"); - const payload = await response.json().catch(() => null) as { profile?: ChartLibraryApiRecord } | null; + const payload = await response.json().catch(() => null) as { profile?: ChartLibraryApiRecord; error?: string } | null; + if (!response.ok) throw new Error(payload?.error || "cloud_chart_profile_save_failed"); return payload?.profile ? normalizeChartLibraryApiRecord(payload.profile) : record; } diff --git a/frontend/tests/chart-library-other-profile.test.ts b/frontend/tests/chart-library-other-profile.test.ts index 3ddec83a..3507c687 100644 --- a/frontend/tests/chart-library-other-profile.test.ts +++ b/frontend/tests/chart-library-other-profile.test.ts @@ -3,6 +3,7 @@ import { readFileSync } from "node:fs"; import test from "node:test"; const source = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); +const route = readFileSync(new URL("../src/app/api/chart-profiles/route.ts", import.meta.url), "utf8"); test("other chart saves do not require the owner's rectification state", () => { assert.match(source, /function missingOtherProfileStep\(profile: Profile\)/); @@ -35,3 +36,14 @@ test("a successful cloud read replaces stale local other charts", () => { ); assert.doesNotMatch(source, /fetchCloudChartLibrary\(\)[\s\S]{0,800}new Map\(\[[\s\S]{0,500}current\.filter\(\(record\) => record\.role === "other"\)/); }); + +test("other chart creation lets the database create its UUID", () => { + assert.match(route, /\.insert\(\{ user_id: user\.id, role, profile: body\.profile, updated_at: updatedAt \}\)/); + assert.doesNotMatch(route, /\.upsert\(record, \{ onConflict: "id" \}\)/); + assert.doesNotMatch(source, /id: record\.role === "self" \? undefined : record\.id/); +}); + +test("cloud save failures preserve the server error message", () => { + assert.match(source, /const payload = await response\.json\(\)\.catch\(\(\) => null\) as \{ error\?: string \} \| null;/); + assert.match(source, /throw new Error\(payload\?\.error \|\| "cloud_chart_profile_save_failed"\);/); +}); diff --git a/tests/test_supabase_user_data_contract.py b/tests/test_supabase_user_data_contract.py index 0109702b..f45dec94 100644 --- a/tests/test_supabase_user_data_contract.py +++ b/tests/test_supabase_user_data_contract.py @@ -141,9 +141,10 @@ def test_chart_profile_library_has_cloud_table_api_and_local_fallback() -> None: 'eq("user_id", user.id)', 'eq("role", "self")', 'insert({ user_id: user.id, role, profile: body.profile', - 'upsert(record, { onConflict: "id" })', + 'insert({ user_id: user.id, role, profile: body.profile, updated_at: updatedAt })', ): assert token in route + assert 'upsert(record, { onConflict: "id" })' not in route assert 'eq("role", "other")' in delete_route for token in (