migrate: replace Firebase with PostgreSQL across core routes

- chat-context.ts: session history now from fs_sessions
- /api/sessions: reads from fs_sessions (NextAuth session auth)
- /api/github/connect: NextAuth session + stores in fs_users.data
- /api/user/api-key: NextAuth session + stores in fs_users.data
- /api/projects/[id]/vision: PATCH to fs_projects JSONB
- /api/projects/[id]/knowledge/items: reads from fs_knowledge_items
- /api/projects/[id]/knowledge/import-ai-chat: uses pg createKnowledgeItem
- lib/server/knowledge.ts: fully rewritten to use PostgreSQL
- entrypoint.sh: add fs_knowledge_items and chat_conversations tables

Made-with: Cursor
This commit is contained in:
2026-02-27 13:25:38 -08:00
parent 3ce10dc45b
commit ef7a88e913
9 changed files with 267 additions and 360 deletions

View File

@@ -1,13 +1,11 @@
import { getAdminDb } from '@/lib/firebase/admin';
import { FieldValue } from 'firebase-admin/firestore';
import { query } from '@/lib/db-postgres';
import { randomUUID } from 'crypto';
import type {
KnowledgeItem,
KnowledgeSourceMeta,
KnowledgeSourceType,
} from '@/lib/types/knowledge';
const COLLECTION = 'knowledge_items';
interface CreateKnowledgeItemInput {
projectId: string;
sourceType: KnowledgeSourceType;
@@ -16,59 +14,62 @@ interface CreateKnowledgeItemInput {
sourceMeta?: KnowledgeSourceMeta;
}
function rowToItem(row: { id: string; project_id: string; data: any; created_at: string; updated_at: string }): KnowledgeItem {
const d = row.data ?? {};
return {
id: row.id,
projectId: row.project_id,
sourceType: d.sourceType,
title: d.title ?? null,
content: d.content,
sourceMeta: d.sourceMeta ?? null,
createdAt: row.created_at,
updatedAt: row.updated_at,
} as KnowledgeItem;
}
export async function createKnowledgeItem(
input: CreateKnowledgeItemInput,
): Promise<KnowledgeItem> {
const adminDb = getAdminDb();
const docRef = adminDb.collection(COLLECTION).doc();
const payload = {
id: docRef.id,
projectId: input.projectId,
const id = randomUUID();
const data = {
sourceType: input.sourceType,
title: input.title ?? null,
content: input.content,
sourceMeta: input.sourceMeta ?? null,
createdAt: FieldValue.serverTimestamp(),
updatedAt: FieldValue.serverTimestamp(),
};
await docRef.set(payload);
const snapshot = await docRef.get();
return snapshot.data() as KnowledgeItem;
await query(
`INSERT INTO fs_knowledge_items (id, project_id, data) VALUES ($1, $2, $3::jsonb)`,
[id, input.projectId, JSON.stringify(data)]
);
const rows = await query<any>(
`SELECT id, project_id, data, created_at, updated_at FROM fs_knowledge_items WHERE id = $1`,
[id]
);
return rowToItem(rows[0]);
}
export async function getKnowledgeItem(
projectId: string,
knowledgeItemId: string,
): Promise<KnowledgeItem | null> {
const adminDb = getAdminDb();
const docRef = adminDb.collection(COLLECTION).doc(knowledgeItemId);
const snapshot = await docRef.get();
if (!snapshot.exists) {
return null;
}
const data = snapshot.data() as KnowledgeItem;
if (data.projectId !== projectId) {
return null;
}
return data;
const rows = await query<any>(
`SELECT id, project_id, data, created_at, updated_at FROM fs_knowledge_items WHERE id = $1 AND project_id = $2`,
[knowledgeItemId, projectId]
);
if (rows.length === 0) return null;
return rowToItem(rows[0]);
}
export async function listKnowledgeItems(
projectId: string,
): Promise<KnowledgeItem[]> {
const adminDb = getAdminDb();
const querySnapshot = await adminDb
.collection(COLLECTION)
.where('projectId', '==', projectId)
.orderBy('createdAt', 'desc')
.get();
return querySnapshot.docs.map((doc) => doc.data() as KnowledgeItem);
const rows = await query<any>(
`SELECT id, project_id, data, created_at, updated_at FROM fs_knowledge_items WHERE project_id = $1 ORDER BY created_at DESC`,
[projectId]
);
return rows.map(rowToItem);
}