- Add slides.tool.ts with support for title, bullets, chart, stats, table, cards, timeline, quote, comparison, equation, image, summary slide types - Chart types: bar, horizontal-bar, line, donut, radar - Integrate with agent executor and canvas system - Add multilingual support (en/fr) - Various UI improvements and bug fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
225 lines
6.0 KiB
TypeScript
225 lines
6.0 KiB
TypeScript
'use server'
|
|
|
|
import { revalidatePath } from 'next/cache'
|
|
import prisma from '@/lib/prisma'
|
|
import { auth } from '@/auth'
|
|
import { parseNote } from '@/lib/utils'
|
|
import { parseImageUrls, cleanupNoteImages, deleteImageFileSafely } from '@/lib/image-cleanup'
|
|
import { NOTE_LIST_SELECT } from '@/lib/note-select'
|
|
|
|
// Soft-delete a note (move to trash)
|
|
export async function deleteNote(id: string, options?: { skipRevalidation?: boolean }) {
|
|
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() }
|
|
})
|
|
if (!options?.skipRevalidation) {
|
|
revalidatePath('/home')
|
|
}
|
|
return { success: true }
|
|
} catch (error) {
|
|
console.error('Error deleting note:', error)
|
|
throw new Error('Failed to delete note')
|
|
}
|
|
}
|
|
|
|
export async function trashNote(id: string, options?: { skipRevalidation?: boolean }) {
|
|
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() }
|
|
})
|
|
if (!options?.skipRevalidation) {
|
|
revalidatePath('/home')
|
|
}
|
|
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('/home')
|
|
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 {
|
|
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 } })
|
|
|
|
if (imageUrls.length > 0) {
|
|
await cleanupNoteImages(id, imageUrls)
|
|
}
|
|
|
|
revalidatePath('/trash')
|
|
revalidatePath('/home')
|
|
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 {
|
|
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 } }
|
|
})
|
|
|
|
for (const note of trashedNotes) {
|
|
const imageUrls = parseImageUrls(note.images)
|
|
if (imageUrls.length > 0) {
|
|
await cleanupNoteImages(note.id, imageUrls)
|
|
}
|
|
}
|
|
|
|
await prisma.notebook.deleteMany({
|
|
where: { userId: session.user.id, trashedAt: { not: null } }
|
|
})
|
|
|
|
revalidatePath('/trash')
|
|
revalidatePath('/home')
|
|
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 },
|
|
})
|
|
|
|
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 {
|
|
const [noteCount, notebookCount] = await Promise.all([
|
|
prisma.note.count({
|
|
where: { userId: session.user.id, trashedAt: { not: null } }
|
|
}),
|
|
prisma.notebook.count({
|
|
where: { userId: session.user.id, trashedAt: { not: null } }
|
|
})
|
|
])
|
|
return noteCount + notebookCount
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
export async function getTrashedNotebooks() {
|
|
const session = await auth()
|
|
if (!session?.user?.id) return []
|
|
|
|
try {
|
|
return await prisma.notebook.findMany({
|
|
where: { userId: session.user.id, trashedAt: { not: null } },
|
|
include: { _count: { select: { notes: true } } },
|
|
orderBy: { trashedAt: 'desc' }
|
|
})
|
|
} catch (error) {
|
|
console.error('Error fetching trashed notebooks:', error)
|
|
return []
|
|
}
|
|
}
|