fix: label management - transaction safety, deletion sync, error handling
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 42s

- Use prisma.$transaction in auto-label-creation.service with tx client
- Fix DELETE /api/labels to properly JSON.parse + disconnect labelRelations
- Fix PUT /api/labels rename to handle JSON labels
- Graceful error handling in /api/ai/tags and /api/ai/auto-labels
- Client-side label-deleted event in home-client, notes-tabs-view, label-management-dialog

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 00:09:31 +02:00
parent 0a4aa47690
commit 6ff8088cc2
7 changed files with 116 additions and 59 deletions

View File

@@ -114,7 +114,10 @@ export async function PUT(
for (const note of allNotes) {
if (note.labels) {
try {
const noteLabels: string[] = Array.isArray(note.labels) ? (note.labels as string[]) : []
const parsed = typeof note.labels === 'string' ? JSON.parse(note.labels) : note.labels
const noteLabels: string[] = Array.isArray(parsed)
? parsed.filter((n): n is string => typeof n === 'string')
: []
const updatedLabels = noteLabels.map(l =>
l.toLowerCase() === currentLabel.name.toLowerCase() ? newName : l
)
@@ -123,8 +126,8 @@ export async function PUT(
await prisma.note.update({
where: { id: note.id },
data: {
labels: updatedLabels as any
}
labels: updatedLabels.length > 0 ? JSON.stringify(updatedLabels) : null,
},
})
}
} catch (e) {
@@ -197,36 +200,50 @@ export async function DELETE(
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
// For backward compatibility, remove from old label field in notes
// Remove label from all notes that have it (JSON + labelRelations)
const targetUserIdDel = label.userId || label.notebook?.userId || session.user.id;
if (targetUserIdDel) {
const allNotes = await prisma.note.findMany({
where: {
userId: targetUserIdDel,
labels: { not: null }
OR: [
{ labels: { not: null } },
{ labelRelations: { some: { id: label.id } } },
],
},
select: { id: true, labels: true }
})
for (const note of allNotes) {
let noteLabels: string[] = []
if (note.labels) {
try {
const noteLabels: string[] = Array.isArray(note.labels) ? (note.labels as string[]) : []
const filteredLabels = noteLabels.filter(
l => l.toLowerCase() !== label.name.toLowerCase()
)
if (filteredLabels.length !== noteLabels.length) {
await prisma.note.update({
where: { id: note.id },
data: {
labels: (filteredLabels.length > 0 ? filteredLabels : null) as any
}
})
}
} catch (e) {
const parsed = typeof note.labels === 'string' ? JSON.parse(note.labels) : note.labels
noteLabels = Array.isArray(parsed)
? parsed.filter((n): n is string => typeof n === 'string')
: []
} catch {
noteLabels = []
}
}
const filteredLabels = noteLabels.filter(
l => l.toLowerCase() !== label.name.toLowerCase()
)
const labelChanged = filteredLabels.length !== noteLabels.length
if (labelChanged) {
await prisma.note.update({
where: { id: note.id },
data: {
labels: filteredLabels.length > 0 ? JSON.stringify(filteredLabels) : null,
labelRelations: {
disconnect: { id: label.id },
},
},
})
}
}
}