From 7138f86427ad48eb9a9f043697a7b59376fc1404 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 27 Apr 2026 17:34:30 -0700 Subject: [PATCH] Auto-create chat tables on first request (IF NOT EXISTS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/api/chat/route.ts | 30 ++++++++++++++++++++++++++++++ app/api/chat/threads/route.ts | 30 ++++++++++++++++++++++++++++++ scripts/migrate-fs-tables.sql | 29 +++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 3012a9b5..80ed9916 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -21,6 +21,34 @@ import type { ChatMessage, ToolCall } from '@/lib/ai/gemini-chat'; const MAX_TOOL_ROUNDS = 6; +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; +} + function buildSystemPrompt(projects: any[], workspace: string): string { const projectsText = projects.length ? projects @@ -55,6 +83,8 @@ You can take real actions by calling tools: } export async function POST(request: Request) { + await ensureChatTables(); + const session = await authSession(); if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/app/api/chat/threads/route.ts b/app/api/chat/threads/route.ts index bfd5a769..273e45f8 100644 --- a/app/api/chat/threads/route.ts +++ b/app/api/chat/threads/route.ts @@ -6,7 +6,36 @@ 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 }); @@ -33,6 +62,7 @@ export async function GET(request: Request) { } export async function POST(request: Request) { + await ensureChatTables(); const session = await authSession(); if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); diff --git a/scripts/migrate-fs-tables.sql b/scripts/migrate-fs-tables.sql index 7e01bb0c..c02664b0 100644 --- a/scripts/migrate-fs-tables.sql +++ b/scripts/migrate-fs-tables.sql @@ -155,5 +155,34 @@ CREATE TABLE IF NOT EXISTS verification_tokens ( UNIQUE (identifier, token) ); +-- --------------------------------------------------------------------------- +-- fs_chat_threads (Vibn AI chat conversation threads) +-- --------------------------------------------------------------------------- +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); + +-- --------------------------------------------------------------------------- +-- fs_chat_messages (individual messages within a thread) +-- --------------------------------------------------------------------------- +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); + -- Done SELECT 'Migration complete' AS status;