Files
vibn-frontend/app/api/projects/[projectId]/knowledge/route.ts
Mark Henderson 651ddf1e11 Rip out Theia, ship P5.1 attach E2E + Justine UI work-in-progress
Theia rip-out:
- Delete app/api/theia-auth/route.ts (Traefik ForwardAuth shim)
- Delete app/api/projects/[projectId]/workspace/route.ts and
  app/api/projects/prewarm/route.ts (Cloud Run Theia provisioning)
- Delete lib/cloud-run-workspace.ts and lib/coolify-workspace.ts
- Strip provisionTheiaWorkspace + theiaWorkspaceUrl/theiaAppUuid/
  theiaError from app/api/projects/create/route.ts response
- Remove Theia callbackUrl branch in app/auth/page.tsx
- Drop "Open in Theia" button + xterm/Theia PTY copy in build/page.tsx
- Drop theiaWorkspaceUrl from deployment/page.tsx Project type
- Strip Theia IDE line + theia-code-os from advisor + agent-chat
  context strings
- Scrub Theia mention from lib/auth/workspace-auth.ts comment

P5.1 (custom apex domains + DNS):
- lib/coolify.ts + lib/opensrs.ts: nameserver normalization, OpenSRS
  XML auth, Cloud DNS plumbing
- scripts/smoke-attach-e2e.ts: full prod GCP + sandbox OpenSRS +
  prod Coolify smoke covering register/zone/A/NS/PATCH/cleanup

In-progress (Justine onboarding/build, MVP setup, agent telemetry):
- New (justine)/stories, project (home) layouts, mvp-setup, run, tasks
  routes + supporting components
- Project shell + sidebar + nav refactor for the Stackless palette
- Agent session API hardening (sessions, events, stream, approve,
  retry, stop) + atlas-chat, advisor, design-surfaces refresh
- New scripts/sync-db-url-from-coolify.mjs +
  scripts/prisma-db-push.mjs + docker-compose.local-db.yml for
  local Prisma workflows
- lib/dev-bypass.ts, lib/chat-context-refs.ts, lib/prd-sections.ts
- Misc: stories CSS, debug/prisma route, modal-theme, BuildLivePlanPanel

Made-with: Cursor
2026-04-22 18:05:01 -07:00

103 lines
3.5 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { authSession } from "@/lib/auth/session-server";
import { query } from "@/lib/db-postgres";
async function assertOwnership(projectId: string, email: string): Promise<boolean> {
const rows = await query(
`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`,
[projectId, email]
);
return rows.length > 0;
}
// GET /api/projects/[projectId]/knowledge
export async function GET(
_req: NextRequest,
{ params }: { params: Promise<{ projectId: string }> }
) {
const session = await authSession();
if (!session?.user?.email) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { projectId } = await params;
if (!(await assertOwnership(projectId, session.user.email))) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const rows = await query<{ id: string; data: any; updated_at: string }>(
`SELECT id, data, updated_at FROM fs_knowledge_items WHERE project_id = $1 ORDER BY updated_at DESC`,
[projectId]
);
return NextResponse.json({
items: rows.map((r) => ({ id: r.id, ...r.data, updatedAt: r.updated_at })),
});
}
// POST /api/projects/[projectId]/knowledge — add or update an item
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ projectId: string }> }
) {
const session = await authSession();
if (!session?.user?.email) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { projectId } = await params;
if (!(await assertOwnership(projectId, session.user.email))) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const { key, type, value } = await req.json();
if (!key || !value) {
return NextResponse.json({ error: "key and value are required" }, { status: 400 });
}
const itemType = type ?? "note";
const data = JSON.stringify({ key, type: itemType, value, source: "user" });
// Upsert by key
const existing = await query<{ id: string }>(
`SELECT id FROM fs_knowledge_items WHERE project_id = $1 AND data->>'key' = $2 LIMIT 1`,
[projectId, key]
);
if (existing.length > 0) {
await query(
`UPDATE fs_knowledge_items SET data = $1::jsonb, updated_at = NOW() WHERE id = $2`,
[data, existing[0].id]
);
return NextResponse.json({ id: existing[0].id, key, type: itemType, value, updated: true });
} else {
const rows = await query<{ id: string }>(
`INSERT INTO fs_knowledge_items (project_id, data) VALUES ($1, $2::jsonb) RETURNING id`,
[projectId, data]
);
return NextResponse.json({ id: rows[0].id, key, type: itemType, value, created: true });
}
}
// DELETE /api/projects/[projectId]/knowledge?id=xxx
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ projectId: string }> }
) {
const session = await authSession();
if (!session?.user?.email) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { projectId } = await params;
if (!(await assertOwnership(projectId, session.user.email))) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const id = req.nextUrl.searchParams.get("id");
if (!id) return NextResponse.json({ error: "id is required" }, { status: 400 });
await query(
`DELETE FROM fs_knowledge_items WHERE id = $1 AND project_id = $2`,
[id, projectId]
);
return NextResponse.json({ deleted: true });
}