refactor(ux): consolidate BMAD skills, update design system, and clean up Prisma generated client
This commit is contained in:
@@ -13,17 +13,25 @@ async function checkAdmin() {
|
||||
return session
|
||||
}
|
||||
|
||||
export async function testSMTP() {
|
||||
export async function testEmail(provider: 'resend' | 'smtp' = 'smtp') {
|
||||
const session = await checkAdmin()
|
||||
const email = session.user?.email
|
||||
|
||||
if (!email) throw new Error("No admin email found")
|
||||
|
||||
const subject = provider === 'resend'
|
||||
? "Memento Resend Test"
|
||||
: "Memento SMTP Test"
|
||||
|
||||
const html = provider === 'resend'
|
||||
? "<p>This is a test email from your Memento instance. <strong>Resend is working!</strong></p>"
|
||||
: "<p>This is a test email from your Memento instance. <strong>SMTP is working!</strong></p>"
|
||||
|
||||
const result = await sendEmail({
|
||||
to: email,
|
||||
subject: "Memento SMTP Test",
|
||||
html: "<p>This is a test email from your Memento instance. <strong>SMTP is working!</strong></p>"
|
||||
})
|
||||
subject,
|
||||
html,
|
||||
}, provider)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { executeAgent } from '@/lib/ai/services/agent-executor.service'
|
||||
|
||||
// --- CRUD ---
|
||||
|
||||
@@ -21,6 +20,10 @@ export async function createAgent(data: {
|
||||
sourceNotebookId?: string
|
||||
targetNotebookId?: string
|
||||
frequency?: string
|
||||
tools?: string[]
|
||||
maxSteps?: number
|
||||
notifyEmail?: boolean
|
||||
includeImages?: boolean
|
||||
}) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
@@ -38,6 +41,10 @@ export async function createAgent(data: {
|
||||
sourceNotebookId: data.sourceNotebookId || null,
|
||||
targetNotebookId: data.targetNotebookId || null,
|
||||
frequency: data.frequency || 'manual',
|
||||
tools: data.tools ? JSON.stringify(data.tools) : '[]',
|
||||
maxSteps: data.maxSteps || 10,
|
||||
notifyEmail: data.notifyEmail || false,
|
||||
includeImages: data.includeImages || false,
|
||||
userId: session.user.id,
|
||||
}
|
||||
})
|
||||
@@ -60,6 +67,10 @@ export async function updateAgent(id: string, data: {
|
||||
targetNotebookId?: string | null
|
||||
frequency?: string
|
||||
isEnabled?: boolean
|
||||
tools?: string[]
|
||||
maxSteps?: number
|
||||
notifyEmail?: boolean
|
||||
includeImages?: boolean
|
||||
}) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
@@ -82,6 +93,10 @@ export async function updateAgent(id: string, data: {
|
||||
if (data.targetNotebookId !== undefined) updateData.targetNotebookId = data.targetNotebookId
|
||||
if (data.frequency !== undefined) updateData.frequency = data.frequency
|
||||
if (data.isEnabled !== undefined) updateData.isEnabled = data.isEnabled
|
||||
if (data.tools !== undefined) updateData.tools = JSON.stringify(data.tools)
|
||||
if (data.maxSteps !== undefined) updateData.maxSteps = data.maxSteps
|
||||
if (data.notifyEmail !== undefined) updateData.notifyEmail = data.notifyEmail
|
||||
if (data.includeImages !== undefined) updateData.includeImages = data.includeImages
|
||||
|
||||
const agent = await prisma.agent.update({
|
||||
where: { id },
|
||||
@@ -155,6 +170,7 @@ export async function runAgent(id: string) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { executeAgent } = await import('@/lib/ai/services/agent-executor.service')
|
||||
const result = await executeAgent(id, session.user.id)
|
||||
revalidatePath('/agents')
|
||||
revalidatePath('/')
|
||||
@@ -182,6 +198,16 @@ export async function getAgentActions(agentId: string) {
|
||||
where: { agentId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 20,
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
result: true,
|
||||
log: true,
|
||||
input: true,
|
||||
toolLog: true,
|
||||
tokensUsed: true,
|
||||
createdAt: true,
|
||||
}
|
||||
})
|
||||
return actions
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import prisma from '@/lib/prisma'
|
||||
import { sendEmail } from '@/lib/mail'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { getEmailTemplate } from '@/lib/email-template'
|
||||
|
||||
@@ -42,11 +43,14 @@ export async function forgotPassword(email: string) {
|
||||
"Reset Password"
|
||||
);
|
||||
|
||||
const sysConfig = await getSystemConfig()
|
||||
const emailProvider = (sysConfig.EMAIL_PROVIDER || 'auto') as 'resend' | 'smtp' | 'auto'
|
||||
|
||||
await sendEmail({
|
||||
to: user.email,
|
||||
subject: "Reset your Memento password",
|
||||
html
|
||||
});
|
||||
}, emailProvider);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
|
||||
111
keep-notes/app/actions/canvas-actions.ts
Normal file
111
keep-notes/app/actions/canvas-actions.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function saveCanvas(id: string | null, name: string, data: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
if (id) {
|
||||
const canvas = await prisma.canvas.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { name, data }
|
||||
})
|
||||
revalidatePath('/lab')
|
||||
return { success: true, canvas }
|
||||
} else {
|
||||
const canvas = await prisma.canvas.create({
|
||||
data: {
|
||||
name,
|
||||
data,
|
||||
userId: session.user.id
|
||||
}
|
||||
})
|
||||
revalidatePath('/lab')
|
||||
return { success: true, canvas }
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCanvases() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return []
|
||||
|
||||
return prisma.canvas.findMany({
|
||||
where: { userId: session.user.id },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCanvasDetails(id: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return null
|
||||
|
||||
return prisma.canvas.findUnique({
|
||||
where: { id, userId: session.user.id }
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteCanvas(id: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
await prisma.canvas.delete({
|
||||
where: { id, userId: session.user.id }
|
||||
})
|
||||
|
||||
revalidatePath('/lab')
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function renameCanvas(id: string, name: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
await prisma.canvas.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { name }
|
||||
})
|
||||
|
||||
revalidatePath('/lab')
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function createCanvas(lang?: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
const count = await prisma.canvas.count({
|
||||
where: { userId: session.user.id }
|
||||
})
|
||||
|
||||
const defaultNames: Record<string, string> = {
|
||||
en: `Space ${count + 1}`,
|
||||
fr: `Espace ${count + 1}`,
|
||||
fa: `فضای ${count + 1}`,
|
||||
es: `Espacio ${count + 1}`,
|
||||
de: `Bereich ${count + 1}`,
|
||||
it: `Spazio ${count + 1}`,
|
||||
pt: `Espaço ${count + 1}`,
|
||||
ru: `Пространство ${count + 1}`,
|
||||
ja: `スペース ${count + 1}`,
|
||||
ko: `공간 ${count + 1}`,
|
||||
zh: `空间 ${count + 1}`,
|
||||
ar: `مساحة ${count + 1}`,
|
||||
hi: `स्थान ${count + 1}`,
|
||||
nl: `Ruimte ${count + 1}`,
|
||||
pl: `Przestrzeń ${count + 1}`,
|
||||
}
|
||||
|
||||
const newCanvas = await prisma.canvas.create({
|
||||
data: {
|
||||
name: defaultNames[lang || 'en'] || defaultNames.en,
|
||||
data: JSON.stringify({}),
|
||||
userId: session.user.id
|
||||
}
|
||||
})
|
||||
|
||||
revalidatePath('/lab')
|
||||
return newCanvas
|
||||
}
|
||||
74
keep-notes/app/actions/chat-actions.ts
Normal file
74
keep-notes/app/actions/chat-actions.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
'use server'
|
||||
|
||||
import { chatService } from '@/lib/ai/services'
|
||||
import { auth } from '@/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
/**
|
||||
* Create a new empty conversation and return its id.
|
||||
* Called before streaming so the client knows the conversationId upfront.
|
||||
*/
|
||||
export async function createConversation(title: string, notebookId?: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
const conversation = await prisma.conversation.create({
|
||||
data: {
|
||||
userId: session.user.id,
|
||||
notebookId: notebookId || null,
|
||||
title: title.substring(0, 80) + (title.length > 80 ? '...' : ''),
|
||||
},
|
||||
})
|
||||
|
||||
revalidatePath('/chat')
|
||||
return { id: conversation.id, title: conversation.title }
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the streaming API route /api/chat instead.
|
||||
* Kept for backward compatibility with the debug route.
|
||||
*/
|
||||
export async function sendChatMessage(
|
||||
message: string,
|
||||
conversationId?: string,
|
||||
notebookId?: string
|
||||
) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const result = await chatService.chat(message, conversationId, notebookId)
|
||||
revalidatePath('/chat')
|
||||
return { success: true, ...result }
|
||||
} catch (error: any) {
|
||||
console.error('[ChatAction] Error:', error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
}
|
||||
|
||||
export async function getConversations() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return []
|
||||
|
||||
return chatService.listConversations(session.user.id)
|
||||
}
|
||||
|
||||
export async function getConversationDetails(id: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return null
|
||||
|
||||
return chatService.getHistory(id)
|
||||
}
|
||||
|
||||
export async function deleteConversation(id: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
await prisma.conversation.delete({
|
||||
where: { id, userId: session.user.id }
|
||||
})
|
||||
|
||||
revalidatePath('/chat')
|
||||
return { success: true }
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { getAIProvider } from '@/lib/ai/factory'
|
||||
import { parseNote as parseNoteUtil, cosineSimilarity, validateEmbedding, calculateRRFK, detectQueryType, getSearchWeights } from '@/lib/utils'
|
||||
import { getSystemConfig, getConfigNumber, getConfigBoolean, SEARCH_DEFAULTS } from '@/lib/config'
|
||||
import { contextualAutoTagService } from '@/lib/ai/services/contextual-auto-tag.service'
|
||||
import { cleanupNoteImages, parseImageUrls, deleteImageFileSafely } from '@/lib/image-cleanup'
|
||||
|
||||
/**
|
||||
* Champs sélectionnés pour les listes de notes (sans embedding pour économiser ~6KB/note).
|
||||
@@ -20,6 +21,7 @@ const NOTE_LIST_SELECT = {
|
||||
color: true,
|
||||
isPinned: true,
|
||||
isArchived: true,
|
||||
trashedAt: true,
|
||||
type: true,
|
||||
dismissedFromRecent: true,
|
||||
checkItems: true,
|
||||
@@ -219,6 +221,7 @@ export async function getNotes(includeArchived = false) {
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: null,
|
||||
...(includeArchived ? {} : { isArchived: false }),
|
||||
},
|
||||
select: NOTE_LIST_SELECT,
|
||||
@@ -245,6 +248,7 @@ export async function getNotesWithReminders() {
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: null,
|
||||
isArchived: false,
|
||||
reminder: { not: null }
|
||||
},
|
||||
@@ -286,7 +290,8 @@ export async function getArchivedNotes() {
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
isArchived: true
|
||||
isArchived: true,
|
||||
trashedAt: null
|
||||
},
|
||||
select: NOTE_LIST_SELECT,
|
||||
orderBy: { updatedAt: 'desc' }
|
||||
@@ -321,6 +326,7 @@ export async function searchNotes(query: string, useSemantic: boolean = false, n
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
isArchived: false,
|
||||
trashedAt: null,
|
||||
OR: [
|
||||
{ title: { contains: query } },
|
||||
{ content: { contains: query } },
|
||||
@@ -349,6 +355,7 @@ async function semanticSearch(query: string, userId: string, notebookId?: string
|
||||
where: {
|
||||
userId: userId,
|
||||
isArchived: false,
|
||||
trashedAt: null,
|
||||
...(notebookId !== undefined ? { notebookId } : {})
|
||||
},
|
||||
include: { noteEmbedding: true }
|
||||
@@ -650,17 +657,16 @@ export async function updateNote(id: string, data: {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a note
|
||||
// Soft-delete a note (move to trash)
|
||||
export async function deleteNote(id: string) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
|
||||
try {
|
||||
await prisma.note.delete({ where: { id, userId: session.user.id } })
|
||||
|
||||
// Sync labels with empty array to trigger cleanup of any orphans
|
||||
// The syncLabels function will scan all remaining notes and clean up unused labels
|
||||
await syncLabels(session.user.id, [])
|
||||
await prisma.note.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { trashedAt: new Date() }
|
||||
})
|
||||
|
||||
revalidatePath('/')
|
||||
return { success: true }
|
||||
@@ -670,6 +676,192 @@ export async function deleteNote(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Trash actions
|
||||
export async function trashNote(id: string) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
|
||||
try {
|
||||
await prisma.note.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { trashedAt: new Date() }
|
||||
})
|
||||
revalidatePath('/')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error trashing note:', error)
|
||||
throw new Error('Failed to trash note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreNote(id: string) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
|
||||
try {
|
||||
await prisma.note.update({
|
||||
where: { id, userId: session.user.id },
|
||||
data: { trashedAt: null }
|
||||
})
|
||||
revalidatePath('/')
|
||||
revalidatePath('/trash')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error restoring note:', error)
|
||||
throw new Error('Failed to restore note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrashedNotes() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) return [];
|
||||
|
||||
try {
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: { not: null }
|
||||
},
|
||||
select: NOTE_LIST_SELECT,
|
||||
orderBy: { trashedAt: 'desc' }
|
||||
})
|
||||
|
||||
return notes.map(parseNote)
|
||||
} catch (error) {
|
||||
console.error('Error fetching trashed notes:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export async function permanentDeleteNote(id: string) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
|
||||
try {
|
||||
// Fetch images before deleting so we can clean up files
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id, userId: session.user.id },
|
||||
select: { images: true }
|
||||
})
|
||||
const imageUrls = parseImageUrls(note?.images ?? null)
|
||||
|
||||
await prisma.note.delete({ where: { id, userId: session.user.id } })
|
||||
|
||||
// Clean up orphaned image files (safe: skips if referenced by other notes)
|
||||
if (imageUrls.length > 0) {
|
||||
await cleanupNoteImages(id, imageUrls)
|
||||
}
|
||||
|
||||
await syncLabels(session.user.id, [])
|
||||
revalidatePath('/trash')
|
||||
revalidatePath('/')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error permanently deleting note:', error)
|
||||
throw new Error('Failed to permanently delete note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function emptyTrash() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) throw new Error('Unauthorized');
|
||||
|
||||
try {
|
||||
// Fetch trashed notes with images before deleting
|
||||
const trashedNotes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: { not: null }
|
||||
},
|
||||
select: { id: true, images: true }
|
||||
})
|
||||
|
||||
await prisma.note.deleteMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: { not: null }
|
||||
}
|
||||
})
|
||||
|
||||
// Clean up image files for all deleted notes
|
||||
for (const note of trashedNotes) {
|
||||
const imageUrls = parseImageUrls(note.images)
|
||||
if (imageUrls.length > 0) {
|
||||
await cleanupNoteImages(note.id, imageUrls)
|
||||
}
|
||||
}
|
||||
|
||||
await syncLabels(session.user.id, [])
|
||||
revalidatePath('/trash')
|
||||
revalidatePath('/')
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error emptying trash:', error)
|
||||
throw new Error('Failed to empty trash')
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeImageFromNote(noteId: string, imageIndex: number) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
try {
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id: noteId, userId: session.user.id },
|
||||
select: { images: true },
|
||||
})
|
||||
if (!note) throw new Error('Note not found')
|
||||
|
||||
const imageUrls = parseImageUrls(note.images)
|
||||
if (imageIndex < 0 || imageIndex >= imageUrls.length) throw new Error('Invalid image index')
|
||||
|
||||
const removedUrl = imageUrls[imageIndex]
|
||||
const newImages = imageUrls.filter((_, i) => i !== imageIndex)
|
||||
|
||||
await prisma.note.update({
|
||||
where: { id: noteId },
|
||||
data: { images: newImages.length > 0 ? JSON.stringify(newImages) : null },
|
||||
})
|
||||
|
||||
// Clean up file if no other note references it
|
||||
await deleteImageFileSafely(removedUrl, noteId)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Error removing image:', error)
|
||||
throw new Error('Failed to remove image')
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanupOrphanedImages(imageUrls: string[], noteId: string) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return
|
||||
|
||||
try {
|
||||
for (const url of imageUrls) {
|
||||
await deleteImageFileSafely(url, noteId)
|
||||
}
|
||||
} catch {
|
||||
// Silent — best-effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrashCount() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) return 0;
|
||||
|
||||
try {
|
||||
return await prisma.note.count({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
trashedAt: { not: null }
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle functions
|
||||
export async function togglePin(id: string, isPinned: boolean) { return updateNote(id, { isPinned }) }
|
||||
export async function toggleArchive(id: string, isArchived: boolean) { return updateNote(id, { isArchived }) }
|
||||
@@ -710,7 +902,7 @@ export async function reorderNotes(draggedId: string, targetId: string) {
|
||||
const targetNote = await prisma.note.findUnique({ where: { id: targetId, userId: session.user.id } })
|
||||
if (!draggedNote || !targetNote) throw new Error('Notes not found')
|
||||
const allNotes = await prisma.note.findMany({
|
||||
where: { userId: session.user.id, isPinned: draggedNote.isPinned, isArchived: false },
|
||||
where: { userId: session.user.id, isPinned: draggedNote.isPinned, isArchived: false, trashedAt: null },
|
||||
orderBy: { order: 'asc' }
|
||||
})
|
||||
const reorderedNotes = allNotes.filter((n: any) => n.id !== draggedId)
|
||||
@@ -865,11 +1057,12 @@ export async function syncAllEmbeddings() {
|
||||
const userId = session.user.id;
|
||||
let updatedCount = 0;
|
||||
try {
|
||||
const notesToSync = await prisma.note.findMany({
|
||||
where: {
|
||||
userId,
|
||||
const notesToSync = await prisma.note.findMany({
|
||||
where: {
|
||||
userId,
|
||||
trashedAt: null,
|
||||
noteEmbedding: { is: null }
|
||||
}
|
||||
}
|
||||
})
|
||||
const provider = getAIProvider(await getSystemConfig());
|
||||
for (const note of notesToSync) {
|
||||
@@ -905,6 +1098,7 @@ export async function getAllNotes(includeArchived = false) {
|
||||
prisma.note.findMany({
|
||||
where: {
|
||||
userId,
|
||||
trashedAt: null,
|
||||
...(includeArchived ? {} : { isArchived: false }),
|
||||
},
|
||||
select: NOTE_LIST_SELECT,
|
||||
@@ -923,6 +1117,7 @@ export async function getAllNotes(includeArchived = false) {
|
||||
const sharedNotes = acceptedShares
|
||||
.map(share => share.note)
|
||||
.filter(note => includeArchived || !note.isArchived)
|
||||
.map(note => ({ ...note, _isShared: true }))
|
||||
|
||||
return [...ownNotes.map(parseNote), ...sharedNotes.map(parseNote)]
|
||||
} catch (error) {
|
||||
@@ -944,6 +1139,7 @@ export async function getPinnedNotes(notebookId?: string) {
|
||||
userId: userId,
|
||||
isPinned: true,
|
||||
isArchived: false,
|
||||
trashedAt: null,
|
||||
...(notebookId !== undefined ? { notebookId } : {})
|
||||
},
|
||||
orderBy: [
|
||||
@@ -977,6 +1173,7 @@ export async function getRecentNotes(limit: number = 3) {
|
||||
userId: userId,
|
||||
contentUpdatedAt: { gte: sevenDaysAgo },
|
||||
isArchived: false,
|
||||
trashedAt: null,
|
||||
dismissedFromRecent: false // Filter out dismissed notes
|
||||
},
|
||||
orderBy: { contentUpdatedAt: 'desc' },
|
||||
@@ -1118,8 +1315,20 @@ export async function getNoteCollaborators(noteId: string) {
|
||||
throw new Error('Note not found')
|
||||
}
|
||||
|
||||
// Owner can always see collaborators
|
||||
// Shared users can also see collaborators if they have accepted access
|
||||
if (note.userId !== session.user.id) {
|
||||
throw new Error('You do not have access to this note')
|
||||
const share = await prisma.noteShare.findUnique({
|
||||
where: {
|
||||
noteId_userId: {
|
||||
noteId,
|
||||
userId: session.user.id
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!share || share.status !== 'accepted') {
|
||||
throw new Error('You do not have access to this note')
|
||||
}
|
||||
}
|
||||
|
||||
// Get all users who have been shared this note (any status)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { revalidatePath, updateTag } from 'next/cache'
|
||||
|
||||
export type UserSettingsData = {
|
||||
theme?: 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
|
||||
cardSizeMode?: 'variable' | 'uniform'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,11 +49,12 @@ const getCachedUserSettings = unstable_cache(
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { theme: true }
|
||||
select: { theme: true, cardSizeMode: true }
|
||||
})
|
||||
|
||||
return {
|
||||
theme: (user?.theme || 'light') as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
|
||||
theme: (user?.theme || 'light') as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue',
|
||||
cardSizeMode: (user?.cardSizeMode || 'variable') as 'variable' | 'uniform'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting user settings:', error)
|
||||
@@ -75,7 +77,8 @@ export async function getUserSettings(userId?: string) {
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
theme: 'light' as const
|
||||
theme: 'light' as const,
|
||||
cardSizeMode: 'variable' as const
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user