Files
Momento/memento-note/lib/image-cleanup.ts
Antigravity 45297da333 fix: images, couverture, slash, agents, wizard, Stripe et admin
- Collage/accès images: ownership path userId + meta legacy, 403 non caché
- Couverture note: explicite (choix/aucune), plus d’auto depuis le corps
- Éditeur: suppression image TipTap, sync immédiate, toolbar réordonnée
- Slash: listes par titre (plus d’index), keywords nettoyés
- Agents: nextRun à la réactivation, cron sans stampede null
- Wizard: titres courts + mode Expert plus robuste
- Stripe checkout hosted fiable; admin métriques live; dark mode IA/settings
- Indexation notes courtes + test unit aligné
2026-07-16 16:58:07 +00:00

70 lines
2.1 KiB
TypeScript

/**
* Image Cleanup Utility
* Safely deletes orphaned image files from disk.
* Checks database references before deleting to avoid breaking shared images.
*/
import { promises as fs } from 'fs'
import path from 'path'
import { prisma } from '@/lib/prisma'
const UPLOADS_DIR = 'data/uploads/notes'
/**
* Delete an image file from disk only if no other note references it.
* @param imageUrl - The relative URL path (e.g. "/uploads/notes/abc.jpg")
* @param excludeNoteId - Note ID to exclude from reference check (the note being deleted)
*/
export async function deleteImageFileSafely(imageUrl: string, excludeNoteId?: string): Promise<void> {
if (!imageUrl || !imageUrl.startsWith('/uploads/notes/')) return
try {
const notes = await prisma.note.findMany({
where: {
OR: [
{ images: { contains: imageUrl } },
{ content: { contains: imageUrl } },
],
},
select: { id: true },
})
const otherRefs = notes.filter(n => n.id !== excludeNoteId)
if (otherRefs.length > 0) return // File still referenced elsewhere
// imageUrl = /uploads/notes/... → data/uploads/notes/...
const filePath = path.join(process.cwd(), 'data', imageUrl.replace(/^\//, ''))
await fs.unlink(filePath)
// Remove ownership sidecar if present
try {
await fs.unlink(filePath + '.meta.json')
} catch {
/* no meta */
}
} catch {
// File already gone or unreadable -- silently skip
}
}
/**
* Delete all image files associated with a note.
* Checks that each image is not referenced by any other note before deleting.
*/
export async function cleanupNoteImages(noteId: string, imageUrls: string[]): Promise<void> {
for (const url of imageUrls) {
await deleteImageFileSafely(url, noteId)
}
}
/**
* Parse the images JSON field from a note record.
*/
export function parseImageUrls(imagesJson: string | null): string[] {
if (!imagesJson) return []
try {
const parsed = JSON.parse(imagesJson)
return Array.isArray(parsed) ? parsed.filter((u: unknown) => typeof u === 'string') : []
} catch {
return []
}
}