Files
vibn-frontend/app/api/chat/threads/route.ts
Mark Henderson 7138f86427 Auto-create chat tables on first request (IF NOT EXISTS)
fs_chat_threads and fs_chat_messages were referenced in code but
never added to the migration script. Added ensureChatTables() called
at startup of both /api/chat and /api/chat/threads routes — safe,
idempotent, and runs once per process lifetime. Also backfilled the
SQL migration file for documentation.

Made-with: Cursor
2026-04-27 17:34:30 -07:00

86 lines
2.9 KiB
TypeScript

/**
* GET /api/chat/threads — list user's threads
* POST /api/chat/threads — create a new thread
*/
import { NextResponse } from 'next/server';
import { authSession } from '@/lib/auth/session-server';
import { query } from '@/lib/db-postgres';
let chatTablesReady = false;
async function ensureChatTables() {
if (chatTablesReady) return;
await query(`
CREATE TABLE IF NOT EXISTS fs_chat_threads (
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
user_id TEXT NOT NULL,
workspace TEXT NOT NULL DEFAULT '',
data JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS fs_chat_threads_user_ws_idx
ON fs_chat_threads (user_id, workspace, updated_at DESC);
CREATE TABLE IF NOT EXISTS fs_chat_messages (
id BIGSERIAL PRIMARY KEY,
thread_id TEXT NOT NULL REFERENCES fs_chat_threads(id) ON DELETE CASCADE,
user_id TEXT NOT NULL,
data JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS fs_chat_messages_thread_idx
ON fs_chat_messages (thread_id, created_at ASC);
`, []);
chatTablesReady = true;
}
export async function GET(request: Request) {
await ensureChatTables();
const session = await authSession();
if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { searchParams } = new URL(request.url);
const workspace = searchParams.get('workspace') || '';
const rows = await query<any>(
`SELECT id, data, created_at, updated_at
FROM fs_chat_threads
WHERE user_id = $1 AND workspace = $2
ORDER BY updated_at DESC
LIMIT 50`,
[session.user.email, workspace],
);
return NextResponse.json({
threads: rows.map((r: any) => ({
id: r.id,
title: r.data?.title || 'New conversation',
updatedAt: r.updated_at,
createdAt: r.created_at,
})),
});
}
export async function POST(request: Request) {
await ensureChatTables();
const session = await authSession();
if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { workspace, title } = await request.json().catch(() => ({}));
if (!workspace) return NextResponse.json({ error: 'workspace required' }, { status: 400 });
const rows = await query<any>(
`INSERT INTO fs_chat_threads (user_id, workspace, data)
VALUES ($1, $2, $3)
RETURNING id, data, created_at, updated_at`,
[
session.user.email,
workspace,
JSON.stringify({ title: title || 'New conversation', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }),
],
);
const r = rows[0];
return NextResponse.json({ thread: { id: r.id, title: r.data?.title || 'New conversation', createdAt: r.created_at, updatedAt: r.updated_at } });
}