feat: rename keep-notes to memento-note, migrate to PostgreSQL, fix MCP bugs

- Rename directory keep-notes -> memento-note with all code references
- Prisma: SQLite -> PostgreSQL (both app and MCP server schemas)
- Sync MCP schema with main app (add missing fields, relations, indexes)
- Delete 17 SQLite migrations (clean slate for PostgreSQL)
- Remove SQLite dependencies (@libsql/client, better-sqlite3, etc.)
- Fix MCP server: hardcoded Windows DB paths -> DATABASE_URL env var
- Fix MCP server: .dockerignore excluded index-sse.js (SSE mode broken)
- MCP Dockerfile: node:20 -> node:22
- Docker Compose: add postgres service, remove SQLite volume
- Generate favicon.ico, icon-192.png, icon-512.png, apple-icon.png
- Update layout.tsx icons and manifest.json for PNG icons
- Update all .env files for PostgreSQL
- Rewrite README.md with updated sections
- Remove mcp-server/node_modules and prisma/client-generated from git tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Sepehr Ramezani
2026-04-20 20:58:04 +02:00
parent e2ddfa53d4
commit aa6a214f37
3548 changed files with 440 additions and 516800 deletions

View File

@@ -0,0 +1,100 @@
import { NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { auth } from '@/auth'
import { validateEmbedding } from '@/lib/utils'
/**
* Admin endpoint to validate all embeddings in the database
* Returns a list of notes with invalid embeddings
*/
export async function GET() {
try {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Check if user is admin
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { role: true }
})
if (!user || user.role !== 'ADMIN') {
return NextResponse.json({ error: 'Forbidden - Admin only' }, { status: 403 })
}
// Fetch all notes with embeddings
const allNotes = await prisma.note.findMany({
select: {
id: true,
title: true,
embedding: true
}
})
const invalidNotes: Array<{
id: string
title: string
issues: string[]
}> = []
let validCount = 0
let missingCount = 0
let invalidCount = 0
for (const note of allNotes) {
// Check if embedding is missing
if (!note.embedding) {
missingCount++
invalidNotes.push({
id: note.id,
title: note.title || 'Untitled',
issues: ['Missing embedding']
})
continue
}
// Parse and validate embedding
try {
const embedding = JSON.parse(note.embedding)
const validation = validateEmbedding(embedding)
if (!validation.valid) {
invalidCount++
invalidNotes.push({
id: note.id,
title: note.title || 'Untitled',
issues: validation.issues
})
} else {
validCount++
}
} catch (error) {
invalidCount++
invalidNotes.push({
id: note.id,
title: note.title || 'Untitled',
issues: [`Failed to parse embedding: ${error}`]
})
}
}
return NextResponse.json({
success: true,
summary: {
total: allNotes.length,
valid: validCount,
missing: missingCount,
invalid: invalidCount
},
invalidNotes
})
} catch (error) {
console.error('[EMBEDDING_VALIDATION] Error:', error)
return NextResponse.json(
{ success: false, error: String(error) },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,31 @@
import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';
import { LABEL_COLORS } from '@/lib/types';
export const dynamic = 'force-dynamic';
export async function GET() {
try {
const labels = await prisma.label.findMany();
const colors = Object.keys(LABEL_COLORS).filter(c => c !== 'gray'); // Exclude gray to force colors
const updates = labels.map((label: any) => {
const randomColor = colors[Math.floor(Math.random() * colors.length)];
return prisma.label.update({
where: { id: label.id },
data: { color: randomColor }
});
});
await prisma.$transaction(updates);
return NextResponse.json({
success: true,
updated: updates.length,
message: "All labels have been assigned a random non-gray color."
});
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 });
}
}

View File

@@ -0,0 +1,57 @@
import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';
export const dynamic = 'force-dynamic';
export async function GET() {
try {
// 1. Get all notes
const notes = await prisma.note.findMany({
select: { labels: true }
});
// 2. Extract all unique labels from JSON
const uniqueLabels = new Set<string>();
notes.forEach((note: any) => {
if (note.labels) {
try {
const parsed = JSON.parse(note.labels);
if (Array.isArray(parsed)) {
parsed.forEach((l: string) => uniqueLabels.add(l));
}
} catch (e) {
// ignore error
}
}
});
// 3. Get existing labels in DB
const existingDbLabels = await prisma.label.findMany();
const existingNames = new Set(existingDbLabels.map((l: any) => l.name));
// 4. Create missing labels
const created = [];
for (const name of uniqueLabels) {
if (!existingNames.has(name)) {
const newLabel = await prisma.label.create({
data: {
name,
color: 'gray' // Default color
}
});
created.push(newLabel);
}
}
return NextResponse.json({
success: true,
foundInNotes: uniqueLabels.size,
alreadyInDb: existingNames.size,
created: created.length,
createdLabels: created
});
} catch (error) {
return NextResponse.json({ error: String(error) }, { status: 500 });
}
}