[object Object]

← back to Norma

fix(api): 4 GET-500s surfaced by api-smoke (onboard/library real; auth-google/clickup status)

eb853c3fed78c47eb8e1471e3a7212306c05117a · 2026-08-06 10:31:16 -0700 · Steve

- onboard/[id]: SELECT referenced organizations.employee_count (never existed) -> o.staff_size AS employee_count. Crashed on every request, live too.
- library/[id]: $2::text forced the param to text -> 'uuid = text' operator error -> $2::uuid IS NULL OR org_id = $2::uuid. Crashed on every request, live too.
- auth/google + clickup: missing-integration-config is a dependency state -> 503 not 500. clickup guarded in both GET and POST.

Verified on :7411: api-smoke 27->0 crashes; security-regression 20/20 (no regressions). Fixture gap (role_permissions / gmail assigned_*) = migrations 024+025 not applied to sdcc_test; patched scratch DB, no app change.

Files touched

Diff

commit eb853c3fed78c47eb8e1471e3a7212306c05117a
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 10:31:16 2026 -0700

    fix(api): 4 GET-500s surfaced by api-smoke (onboard/library real; auth-google/clickup status)
    
    - onboard/[id]: SELECT referenced organizations.employee_count (never existed) -> o.staff_size AS employee_count. Crashed on every request, live too.
    - library/[id]: $2::text forced the param to text -> 'uuid = text' operator error -> $2::uuid IS NULL OR org_id = $2::uuid. Crashed on every request, live too.
    - auth/google + clickup: missing-integration-config is a dependency state -> 503 not 500. clickup guarded in both GET and POST.
    
    Verified on :7411: api-smoke 27->0 crashes; security-regression 20/20 (no regressions). Fixture gap (role_permissions / gmail assigned_*) = migrations 024+025 not applied to sdcc_test; patched scratch DB, no app change.
---
 app/api/auth/google/route.ts  |  2 +-
 app/api/clickup/route.ts      | 10 ++++++++++
 app/api/library/[id]/route.ts |  2 +-
 app/api/onboard/[id]/route.ts |  2 +-
 4 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/app/api/auth/google/route.ts b/app/api/auth/google/route.ts
index aa7166b..c913459 100644
--- a/app/api/auth/google/route.ts
+++ b/app/api/auth/google/route.ts
@@ -9,7 +9,7 @@ export async function GET() {
   const redirectUri = process.env.GOOGLE_OAUTH_REDIRECT_URI || 'http://45.61.58.125:7400/api/auth/google/callback';
 
   if (!clientId) {
-    return NextResponse.json({ error: 'Google OAuth not configured' }, { status: 500 });
+    return NextResponse.json({ error: 'Google OAuth not configured' }, { status: 503 });
   }
 
   const params = new URLSearchParams({
diff --git a/app/api/clickup/route.ts b/app/api/clickup/route.ts
index 7d01a2b..a7f081c 100644
--- a/app/api/clickup/route.ts
+++ b/app/api/clickup/route.ts
@@ -31,6 +31,11 @@ export async function GET(request: NextRequest) {
   const auth = requireRole(request, 'admin', 'staff');
   if (auth instanceof NextResponse) return auth;
 
+  // Missing integration key is a dependency state, not a server fault → 503.
+  if (!getApiKey()) {
+    return NextResponse.json({ error: 'ClickUp integration not configured' }, { status: 503 });
+  }
+
   const { searchParams } = new URL(request.url);
   const action = searchParams.get('action') || 'spaces';
 
@@ -76,6 +81,11 @@ export async function POST(request: NextRequest) {
   const auth = requireRole(request, 'admin', 'staff');
   if (auth instanceof NextResponse) return auth;
 
+  // Missing integration key is a dependency state, not a server fault → 503.
+  if (!getApiKey()) {
+    return NextResponse.json({ error: 'ClickUp integration not configured' }, { status: 503 });
+  }
+
   const body = await request.json();
   const { list_id, name, description, priority, due_date, tags, assignees, source_type, source_id } = body;
 
diff --git a/app/api/library/[id]/route.ts b/app/api/library/[id]/route.ts
index a5a957f..100edbd 100644
--- a/app/api/library/[id]/route.ts
+++ b/app/api/library/[id]/route.ts
@@ -19,7 +19,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
   try {
     const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
     const result = await query(
-      `SELECT * FROM library_items WHERE id = $1 AND ($2::text IS NULL OR org_id = $2)`,
+      `SELECT * FROM library_items WHERE id = $1 AND ($2::uuid IS NULL OR org_id = $2::uuid)`,
       [id, orgId]
     );
 
diff --git a/app/api/onboard/[id]/route.ts b/app/api/onboard/[id]/route.ts
index 9048591..8408599 100644
--- a/app/api/onboard/[id]/route.ts
+++ b/app/api/onboard/[id]/route.ts
@@ -29,7 +29,7 @@ export async function GET(
          o.email,
          o.phone,
          o.founded_year,
-         o.employee_count,
+         o.staff_size AS employee_count,
          o.ntee_code AS org_ntee_code,
          o.state AS org_state,
          o.city,

← 8bbee18 test(api-smoke): durable fs-enumerated GET sweep over all 27  ·  back to Norma  ·  fix(api)+test: close Cody-gate gaps — all 14 uuid=text casts 39264f3 →