fix(workspace-panel): resolve workspace via /api/workspaces, not URL slug

The panel was fetching /api/workspaces/{urlSlug} where {urlSlug}
is whatever is in the `[workspace]` dynamic segment (e.g.
"mark-account"). That slug has nothing to do with vibn_workspaces.slug,
which is derived from the user's email — so the fetch 404'd, the
component showed "Loading workspace…" forever, and minting/revoking
would target a non-existent workspace.

Now:
- GET /api/workspaces lazy-creates a workspace row if the signed-in
  user has none (migration path for accounts created before the
  signIn hook was added).
- WorkspaceKeysPanel discovers the user's actual workspace from that
  list and uses *its* slug for all subsequent calls (details, keys,
  provisioning, revocation).
- Empty / error states render a proper card with a retry button
  instead of a bare "Workspace not found." line.

Made-with: Cursor
This commit is contained in:
2026-04-20 20:43:46 -07:00
parent 4b2289adfa
commit 0bdf598984
2 changed files with 86 additions and 24 deletions

View File

@@ -9,11 +9,10 @@
import { NextResponse } from 'next/server';
import { authSession } from '@/lib/auth/session-server';
import { queryOne } from '@/lib/db-postgres';
import { listWorkspacesForUser } from '@/lib/workspaces';
import { ensureWorkspaceForUser, listWorkspacesForUser } from '@/lib/workspaces';
import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth';
export async function GET(request: Request) {
// API-key clients are pinned to one workspace
if (request.headers.get('authorization')?.toLowerCase().startsWith('bearer vibn_sk_')) {
const principal = await requireWorkspacePrincipal(request);
if (principal instanceof NextResponse) return principal;
@@ -33,7 +32,23 @@ export async function GET(request: Request) {
return NextResponse.json({ workspaces: [] });
}
const list = await listWorkspacesForUser(userRow.id);
// Migration path: users who signed in before the signIn hook was
// added (or before vibn_workspaces existed) have no row yet. Create
// one on first list so the UI never shows an empty state for them.
let list = await listWorkspacesForUser(userRow.id);
if (list.length === 0) {
try {
await ensureWorkspaceForUser({
userId: userRow.id,
email: session.user.email,
displayName: session.user.name ?? null,
});
list = await listWorkspacesForUser(userRow.id);
} catch (err) {
console.error('[api/workspaces] lazy ensure failed', err);
}
}
return NextResponse.json({ workspaces: list.map(serializeWorkspace) });
}