All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m12s
Replace JSON-string embeddings with native pgvector(1536) storage and add PostgreSQL full-text search (tsvector/GIN) with Reciprocal Rank Fusion for hybrid keyword + semantic ranking. Changes: - NoteEmbedding.embedding: String → vector(1536) via pgvector - NoteEmbedding: added updatedAt for reindex tracking - Note: added tsv (tsvector) with auto-update trigger for FTS - semantic-search.service: hybrid FTS + vector search with RRF fusion - embedding.service: toVectorString() for pgvector SQL literals - Removed JS-side cosine similarity loops (now DB-side via <=>) - Added HNSW index on NoteEmbedding.embedding (cosine distance) - Added GIN index on Note.tsv for FTS queries Schema migration in: prisma/migrations/20260512120000_pgvector_and_fts_search/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { auth } from '@/auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const session = await auth()
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
const userId = session.user.id
|
|
|
|
// 1. Find and delete labels that have no notes and belong to this user
|
|
// We only delete labels that are not part of a notebook (global labels)
|
|
const orphanedLabels = await prisma.label.findMany({
|
|
where: {
|
|
userId,
|
|
notebookId: null,
|
|
notes: { none: {} }
|
|
}
|
|
})
|
|
|
|
await prisma.label.deleteMany({
|
|
where: {
|
|
id: { in: orphanedLabels.map(l => l.id) }
|
|
}
|
|
})
|
|
|
|
// 2. Clean up NoteEmbeddings that don't have a corresponding Note
|
|
const orphanedEmbeddings: Array<{ id: string }> = await prisma.$queryRawUnsafe(
|
|
`SELECT e.id FROM "NoteEmbedding" e
|
|
LEFT JOIN "Note" n ON n.id = e."noteId"
|
|
WHERE n.id IS NULL`
|
|
)
|
|
|
|
if (orphanedEmbeddings.length > 0) {
|
|
await prisma.$executeRawUnsafe(
|
|
`DELETE FROM "NoteEmbedding" WHERE id = ANY(${`ARRAY['${orphanedEmbeddings.map(e => e.id).join("','")}']`}::text[])`
|
|
)
|
|
}
|
|
|
|
// 3. Remove note history entries for notes that were deleted (cascade should handle this, but let's be safe)
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
deletedLabels: orphanedLabels.length
|
|
})
|
|
} catch (error) {
|
|
console.error('Cleanup error:', error)
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Failed to cleanup data' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|