feat: add reminders page, BMad skills upgrade, MCP server refactor

- Add reminders page with navigation support
- Upgrade BMad builder module to skills-based architecture
- Refactor MCP server: extract tools and auth into separate modules
- Add connections cache, custom AI provider support
- Update prisma schema and generated client
- Various UI/UX improvements and i18n updates
- Add service worker for PWA support

Made-with: Cursor
This commit is contained in:
Sepehr Ramezani
2026-04-13 21:02:53 +02:00
parent 18ed116e0d
commit fa7e166f3e
3099 changed files with 397228 additions and 14584 deletions

View File

@@ -49,6 +49,7 @@ export async function GET(
data: label
})
} catch (error) {
console.error('Error fetching label:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch label' },
{ status: 500 }
@@ -148,6 +149,7 @@ export async function PUT(
data: label
})
} catch (error) {
console.error('Error updating label:', error)
return NextResponse.json(
{ success: false, error: 'Failed to update label' },
{ status: 500 }
@@ -239,6 +241,7 @@ export async function DELETE(
message: `Label "${label.name}" deleted successfully`
})
} catch (error) {
console.error('Error deleting label:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete label' },
{ status: 500 }

View File

@@ -47,6 +47,7 @@ export async function GET(request: NextRequest) {
data: labels
})
} catch (error) {
console.error('Error fetching labels:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch labels' },
{ status: 500 }
@@ -121,6 +122,7 @@ export async function POST(request: NextRequest) {
data: label
}, { status: 201 })
} catch (error) {
console.error('Error creating label:', error)
return NextResponse.json(
{ success: false, error: 'Failed to create label' },
{ status: 500 }

View File

@@ -65,6 +65,7 @@ export async function PATCH(
notesCount: notebook._count.notes
})
} catch (error) {
console.error('Error updating notebook:', error)
return NextResponse.json(
{ success: false, error: 'Failed to update notebook' },
{ status: 500 }
@@ -125,6 +126,7 @@ export async function DELETE(
labelsCount: notebook._count.labels
})
} catch (error) {
console.error('Error deleting notebook:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete notebook' },
{ status: 500 }

View File

@@ -35,6 +35,7 @@ export async function GET(request: NextRequest) {
}))
})
} catch (error) {
console.error('Error fetching notebooks:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch notebooks' },
{ status: 500 }
@@ -94,6 +95,7 @@ export async function POST(request: NextRequest) {
notesCount: notebook._count.notes
}, { status: 201 })
} catch (error) {
console.error('Error creating notebook:', error)
return NextResponse.json(
{ success: false, error: 'Failed to create notebook' },
{ status: 500 }

View File

@@ -1,20 +1,21 @@
import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
// Helper to parse JSON fields
function parseNote(dbNote: any) {
return {
...dbNote,
checkItems: dbNote.checkItems ? JSON.parse(dbNote.checkItems) : null,
labels: dbNote.labels ? JSON.parse(dbNote.labels) : null,
}
}
import { auth } from '@/auth'
import { parseNote } from '@/lib/utils'
// GET /api/notes/[id] - Get a single note
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const { id } = await params
const note = await prisma.note.findUnique({
@@ -28,11 +29,19 @@ export async function GET(
)
}
if (note.userId !== session.user.id) {
return NextResponse.json(
{ success: false, error: 'Forbidden' },
{ status: 403 }
)
}
return NextResponse.json({
success: true,
data: parseNote(note)
})
} catch (error) {
console.error('Error fetching note:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch note' },
{ status: 500 }
@@ -45,12 +54,38 @@ export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const { id } = await params
const existingNote = await prisma.note.findUnique({
where: { id }
})
if (!existingNote) {
return NextResponse.json(
{ success: false, error: 'Note not found' },
{ status: 404 }
)
}
if (existingNote.userId !== session.user.id) {
return NextResponse.json(
{ success: false, error: 'Forbidden' },
{ status: 403 }
)
}
const body = await request.json()
const updateData: any = { ...body }
// Stringify JSON fields if they exist
if ('checkItems' in body) {
updateData.checkItems = body.checkItems ? JSON.stringify(body.checkItems) : null
}
@@ -69,6 +104,7 @@ export async function PUT(
data: parseNote(note)
})
} catch (error) {
console.error('Error updating note:', error)
return NextResponse.json(
{ success: false, error: 'Failed to update note' },
{ status: 500 }
@@ -81,8 +117,35 @@ export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const { id } = await params
const existingNote = await prisma.note.findUnique({
where: { id }
})
if (!existingNote) {
return NextResponse.json(
{ success: false, error: 'Note not found' },
{ status: 404 }
)
}
if (existingNote.userId !== session.user.id) {
return NextResponse.json(
{ success: false, error: 'Forbidden' },
{ status: 403 }
)
}
await prisma.note.delete({
where: { id }
})
@@ -92,6 +155,7 @@ export async function DELETE(
message: 'Note deleted successfully'
})
} catch (error) {
console.error('Error deleting note:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete note' },
{ status: 500 }

View File

@@ -19,7 +19,7 @@ export async function GET(req: NextRequest) {
userId: session.user.id
},
include: {
labels: {
labelRelations: {
select: {
id: true,
name: true
@@ -84,7 +84,6 @@ export async function GET(req: NextRequest) {
notebooks: notebooks.map(notebook => ({
id: notebook.id,
name: notebook.name,
description: notebook.description,
noteCount: notebook.notes.length
})),
notes: notes.map(note => ({
@@ -95,7 +94,7 @@ export async function GET(req: NextRequest) {
updatedAt: note.updatedAt,
isPinned: note.isPinned,
notebookId: note.notebookId,
labels: note.labels.map(label => ({
labelRelations: note.labelRelations.map(label => ({
id: label.id,
name: label.name
}))

View File

@@ -92,8 +92,7 @@ export async function POST(req: NextRequest) {
data: {
userId: session.user.id,
name: notebook.name,
description: notebook.description || null,
position: 0
order: 0
}
})
newNotebookId = created.id
@@ -129,7 +128,7 @@ export async function POST(req: NextRequest) {
content: note.content,
isPinned: note.isPinned || false,
notebookId: mappedNotebookId,
labels: {
labelRelations: {
connect: labels.map(label => ({ id: label.id }))
}
}

View File

@@ -1,25 +1,26 @@
import { NextRequest, NextResponse } from 'next/server'
import prisma from '@/lib/prisma'
import { CheckItem } from '@/lib/types'
// Helper to parse JSON fields
function parseNote(dbNote: any) {
return {
...dbNote,
checkItems: dbNote.checkItems ? JSON.parse(dbNote.checkItems) : null,
labels: dbNote.labels ? JSON.parse(dbNote.labels) : null,
images: dbNote.images ? JSON.parse(dbNote.images) : null,
}
}
import { auth } from '@/auth'
import { parseNote } from '@/lib/utils'
// GET /api/notes - Get all notes
export async function GET(request: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const searchParams = request.nextUrl.searchParams
const includeArchived = searchParams.get('archived') === 'true'
const search = searchParams.get('search')
let where: any = {}
let where: any = {
userId: session.user.id
}
if (!includeArchived) {
where.isArchived = false
@@ -46,6 +47,7 @@ export async function GET(request: NextRequest) {
data: notes.map(parseNote)
})
} catch (error) {
console.error('Error fetching notes:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch notes' },
{ status: 500 }
@@ -55,6 +57,14 @@ export async function GET(request: NextRequest) {
// POST /api/notes - Create a new note
export async function POST(request: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const body = await request.json()
const { title, content, color, type, checkItems, labels, images } = body
@@ -68,6 +78,7 @@ export async function POST(request: NextRequest) {
const note = await prisma.note.create({
data: {
userId: session.user.id,
title: title || null,
content: content || '',
color: color || 'default',
@@ -83,6 +94,7 @@ export async function POST(request: NextRequest) {
data: parseNote(note)
}, { status: 201 })
} catch (error) {
console.error('Error creating note:', error)
return NextResponse.json(
{ success: false, error: 'Failed to create note' },
{ status: 500 }
@@ -92,6 +104,14 @@ export async function POST(request: NextRequest) {
// PUT /api/notes - Update an existing note
export async function PUT(request: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const body = await request.json()
const { id, title, content, color, type, checkItems, labels, isPinned, isArchived, images } = body
@@ -103,6 +123,24 @@ export async function PUT(request: NextRequest) {
)
}
const existingNote = await prisma.note.findUnique({
where: { id }
})
if (!existingNote) {
return NextResponse.json(
{ success: false, error: 'Note not found' },
{ status: 404 }
)
}
if (existingNote.userId !== session.user.id) {
return NextResponse.json(
{ success: false, error: 'Forbidden' },
{ status: 403 }
)
}
const updateData: any = {}
if (title !== undefined) updateData.title = title
@@ -125,6 +163,7 @@ export async function PUT(request: NextRequest) {
data: parseNote(note)
})
} catch (error) {
console.error('Error updating note:', error)
return NextResponse.json(
{ success: false, error: 'Failed to update note' },
{ status: 500 }
@@ -134,6 +173,14 @@ export async function PUT(request: NextRequest) {
// DELETE /api/notes?id=xxx - Delete a note
export async function DELETE(request: NextRequest) {
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json(
{ success: false, error: 'Unauthorized' },
{ status: 401 }
)
}
try {
const searchParams = request.nextUrl.searchParams
const id = searchParams.get('id')
@@ -145,6 +192,24 @@ export async function DELETE(request: NextRequest) {
)
}
const existingNote = await prisma.note.findUnique({
where: { id }
})
if (!existingNote) {
return NextResponse.json(
{ success: false, error: 'Note not found' },
{ status: 404 }
)
}
if (existingNote.userId !== session.user.id) {
return NextResponse.json(
{ success: false, error: 'Forbidden' },
{ status: 403 }
)
}
await prisma.note.delete({
where: { id }
})
@@ -154,6 +219,7 @@ export async function DELETE(request: NextRequest) {
message: 'Note deleted successfully'
})
} catch (error) {
console.error('Error deleting note:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete note' },
{ status: 500 }