fix: clean up orphan labels when syncing note labels
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 43s

After syncing a note's labels, detect and delete labels in the same
notebook that are no longer referenced by any note (neither via JSON
nor labelRelations). Prevents phantom labels from accumulating.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 00:24:12 +02:00
parent 295bc29786
commit 2d10e5527a

View File

@@ -134,7 +134,8 @@ async function syncLabels(userId: string, noteLabels: string[] = [], notebookId?
}
}
/** Sync both Note.labels (JSON) AND labelRelations for a single note. */
/** Sync both Note.labels (JSON) AND labelRelations for a single note.
* Also cleans up orphan labels in the same notebook scope. */
async function syncNoteLabels(noteId: string, labelNames: string[], notebookId: string | null, userId: string) {
const uniqueNames = [...new Set(labelNames.map(n => n.trim()).filter(Boolean))]
const labelRows = await syncLabels(userId, uniqueNames, notebookId)
@@ -146,6 +147,46 @@ async function syncNoteLabels(noteId: string, labelNames: string[], notebookId:
labelRelations: { set: labelIds.map(id => ({ id })) },
},
})
// Clean up orphan labels: labels in this notebook scope that are no longer
// referenced by any note (neither via JSON nor via labelRelations)
if (notebookId !== null) {
const allLabels = await prisma.label.findMany({
where: { notebookId, userId },
select: { id: true, name: true },
})
if (allLabels.length > 0) {
const labelIdsSet = new Set(allLabels.map(l => l.id))
// Find notes in this notebook that have any label relation
const notesWithLabels = await prisma.note.findMany({
where: {
notebookId,
userId,
labelRelations: { some: { id: { in: Array.from(labelIdsSet) } } },
},
select: { labels: true },
})
// Collect all label names still in use via JSON
const namesInUse = new Set<string>()
for (const n of notesWithLabels) {
if (n.labels) {
try {
const parsed = JSON.parse(n.labels as string)
if (Array.isArray(parsed)) {
parsed.filter((x: any) => typeof x === 'string').forEach((x: string) => namesInUse.add(x.toLowerCase()))
}
} catch {}
}
}
// Delete labels not in use
const orphans = allLabels.filter(l => !namesInUse.has(l.name.toLowerCase()))
if (orphans.length > 0) {
await prisma.label.deleteMany({
where: { id: { in: orphans.map(l => l.id) } },
})
}
}
}
}
/** Après déplacement via API : rattacher les étiquettes de la note au bon carnet */