From 7be66f60b7bcd1dd915e86b1381c5c3842619006 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Sun, 1 Mar 2026 21:30:12 -0800 Subject: [PATCH] fix: qualify table references in design-surfaces SQL to resolve ambiguous column error Made-with: Cursor --- .../[projectId]/design-surfaces/route.ts | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/app/api/projects/[projectId]/design-surfaces/route.ts b/app/api/projects/[projectId]/design-surfaces/route.ts index 8ef7b81..a1d3593 100644 --- a/app/api/projects/[projectId]/design-surfaces/route.ts +++ b/app/api/projects/[projectId]/design-surfaces/route.ts @@ -3,37 +3,50 @@ import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth/authOptions'; import { query } from '@/lib/db-postgres'; -async function getProject(projectId: string, email: string) { - const rows = await query<{ data: Record }>( - `SELECT p.data FROM fs_projects p +async function verifyOwnership(projectId: string, email: string): Promise { + const rows = await query<{ id: string }>( + `SELECT p.id FROM fs_projects p JOIN fs_users u ON u.id = p.user_id - WHERE p.id = $1 AND u.data->>'email' = $2 LIMIT 1`, + WHERE p.id = $1 AND u.data->>'email' = $2 + LIMIT 1`, [projectId, email] ); - return rows[0] ?? null; + return rows.length > 0; } /** * GET — returns surfaces[] and surfaceThemes{} for the project. - * surfaces: string[] — which design surfaces are active (set by Atlas or manually) - * surfaceThemes: Record — locked-in theme per surface */ export async function GET( _req: Request, { params }: { params: Promise<{ projectId: string }> } ) { - const { projectId } = await params; - const session = await getServerSession(authOptions); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + try { + const { projectId } = await params; + const session = await getServerSession(authOptions); + if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - const row = await getProject(projectId, session.user.email); - if (!row) return NextResponse.json({ error: 'Project not found' }, { status: 404 }); + const rows = await query<{ data: Record }>( + `SELECT p.data FROM fs_projects p + JOIN fs_users u ON u.id = p.user_id + WHERE p.id = $1 AND u.data->>'email' = $2 + LIMIT 1`, + [projectId, session.user.email] + ); - const data = row.data ?? {}; - return NextResponse.json({ - surfaces: (data.surfaces ?? []) as string[], - surfaceThemes: (data.surfaceThemes ?? {}) as Record, - }); + if (rows.length === 0) { + return NextResponse.json({ error: 'Project not found' }, { status: 404 }); + } + + const data = rows[0].data ?? {}; + return NextResponse.json({ + surfaces: (data.surfaces ?? []) as string[], + surfaceThemes: (data.surfaceThemes ?? {}) as Record, + }); + } catch (err) { + console.error('[design-surfaces GET]', err); + return NextResponse.json({ error: 'Internal error' }, { status: 500 }); + } } /** @@ -45,40 +58,44 @@ export async function PATCH( req: Request, { params }: { params: Promise<{ projectId: string }> } ) { - const { projectId } = await params; - const session = await getServerSession(authOptions); - if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + try { + const { projectId } = await params; + const session = await getServerSession(authOptions); + if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - const body = await req.json() as - | { surfaces: string[] } - | { surface: string; theme: string }; + const owned = await verifyOwnership(projectId, session.user.email); + if (!owned) return NextResponse.json({ error: 'Project not found' }, { status: 404 }); - if ('surfaces' in body) { - // Save the surface list - await query( - `UPDATE fs_projects p - SET data = data || jsonb_build_object('surfaces', $3::jsonb), - updated_at = NOW() - FROM fs_users u - WHERE p.id = $1 AND p.user_id = u.id AND u.data->>'email' = $2`, - [projectId, session.user.email, JSON.stringify(body.surfaces)] - ); - } else if ('surface' in body && 'theme' in body) { - // Lock in a theme for one surface - await query( - `UPDATE fs_projects p - SET data = data || jsonb_build_object( - 'surfaceThemes', - COALESCE(data->'surfaceThemes', '{}'::jsonb) || jsonb_build_object($3, $4) - ), - updated_at = NOW() - FROM fs_users u - WHERE p.id = $1 AND p.user_id = u.id AND u.data->>'email' = $2`, - [projectId, session.user.email, body.surface, body.theme] - ); - } else { - return NextResponse.json({ error: 'Invalid body' }, { status: 400 }); + const body = await req.json() as + | { surfaces: string[] } + | { surface: string; theme: string }; + + if ('surfaces' in body) { + await query( + `UPDATE fs_projects + SET data = data || jsonb_build_object('surfaces', $2::jsonb), + updated_at = NOW() + WHERE id = $1`, + [projectId, JSON.stringify(body.surfaces)] + ); + } else if ('surface' in body && 'theme' in body) { + await query( + `UPDATE fs_projects + SET data = data || jsonb_build_object( + 'surfaceThemes', + COALESCE(data->'surfaceThemes', '{}'::jsonb) || jsonb_build_object($2, $3) + ), + updated_at = NOW() + WHERE id = $1`, + [projectId, body.surface, body.theme] + ); + } else { + return NextResponse.json({ error: 'Invalid body' }, { status: 400 }); + } + + return NextResponse.json({ success: true }); + } catch (err) { + console.error('[design-surfaces PATCH]', err); + return NextResponse.json({ error: 'Internal error' }, { status: 500 }); } - - return NextResponse.json({ success: true }); }