fix: add race condition guard and null check in group queries

- createOrFindPackageGroup: catch unique constraint violation from
  concurrent creates and fall back to findFirst
- createManualGroup: guard against empty package results before
  accessing first element

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 22:45:29 +01:00
parent d50c68f67c
commit a4156b2ac6
2 changed files with 24 additions and 10 deletions

View File

@@ -609,6 +609,9 @@ export async function createManualGroup(name: string, packageIds: string[]) {
where: { id: { in: packageIds } }, where: { id: { in: packageIds } },
select: { sourceChannelId: true }, select: { sourceChannelId: true },
}); });
if (pkgs.length === 0) {
throw new Error("No matching packages found");
}
const channelIds = new Set(pkgs.map((p) => p.sourceChannelId)); const channelIds = new Set(pkgs.map((p) => p.sourceChannelId));
if (channelIds.size > 1) { if (channelIds.size > 1) {
throw new Error("Cannot group packages from different channels"); throw new Error("Cannot group packages from different channels");

View File

@@ -553,16 +553,27 @@ export async function createOrFindPackageGroup(input: {
if (existing) return existing.id; if (existing) return existing.id;
const group = await db.packageGroup.create({ try {
data: { const group = await db.packageGroup.create({
mediaAlbumId: input.mediaAlbumId, data: {
sourceChannelId: input.sourceChannelId, mediaAlbumId: input.mediaAlbumId,
name: input.name, sourceChannelId: input.sourceChannelId,
previewData: input.previewData ? new Uint8Array(input.previewData) : undefined, name: input.name,
}, previewData: input.previewData ? new Uint8Array(input.previewData) : undefined,
}); },
});
return group.id; return group.id;
} catch (err) {
// Handle race condition: another process created the group between our findFirst and create
if (err instanceof Error && err.message.includes("Unique constraint")) {
const raced = await db.packageGroup.findFirst({
where: { mediaAlbumId: input.mediaAlbumId, sourceChannelId: input.sourceChannelId },
select: { id: true },
});
if (raced) return raced.id;
}
throw err;
}
} }
export async function linkPackagesToGroup( export async function linkPackagesToGroup(