fix: early PDF validation, type-aware errors, exact job dedup
- Move PDF page count check before MinIO upload and DB record creation to prevent orphaned objects when >500 page PDFs are rejected - Use job type label (PDF page / Video) in media-worker error messages instead of hardcoded "Video processing failed" - Replace LIKE-based idempotency check with exact JSON match to prevent page 1 matching page 11/12/etc substring collisions
This commit is contained in:
+46
-27
@@ -131,33 +131,7 @@ router.post('/boards/:boardId/images', upload.single('image'), async (req, res)
|
||||
const { buffer, originalname, mimetype, size } = req.file;
|
||||
const mediaType = classifyMedia(mimetype);
|
||||
|
||||
const { assetKey, minioPath, width, height, isVideo } = await uploadMedia(board.id, imageId, buffer, mimetype);
|
||||
const publicUrl = getImageUrl(minioPath);
|
||||
|
||||
// Save image record first (media_jobs FK references images)
|
||||
const image = createImage({
|
||||
id: imageId,
|
||||
boardId: board.id,
|
||||
filename: originalname,
|
||||
mimeType: mimetype,
|
||||
fileSize: size,
|
||||
width,
|
||||
height,
|
||||
minioPath,
|
||||
publicUrl,
|
||||
uploadedBy: req.user.id,
|
||||
assetKey,
|
||||
mediaType,
|
||||
});
|
||||
|
||||
// Enqueue background processing after image record exists
|
||||
let jobId = null;
|
||||
if (isVideo) {
|
||||
jobId = uuidv4();
|
||||
createMediaJob({ id: jobId, imageId, boardId: board.id, type: 'poster' });
|
||||
}
|
||||
|
||||
// PDF: extract page info, create pdf_pages rows, queue thumbnail jobs
|
||||
// PDF: validate page count BEFORE uploading to MinIO or creating DB records
|
||||
if (mediaType === 'pdf') {
|
||||
const { tmpPath, cleanup } = bufferToTempFile(buffer, '.pdf');
|
||||
try {
|
||||
@@ -166,6 +140,25 @@ router.post('/boards/:boardId/images', upload.single('image'), async (req, res)
|
||||
return res.status(400).json({ error: 'PDF exceeds 500 page limit' });
|
||||
}
|
||||
|
||||
// Validation passed — now upload and create records
|
||||
const { assetKey, minioPath, width, height } = await uploadMedia(board.id, imageId, buffer, mimetype);
|
||||
const publicUrl = getImageUrl(minioPath);
|
||||
|
||||
const image = createImage({
|
||||
id: imageId,
|
||||
boardId: board.id,
|
||||
filename: originalname,
|
||||
mimeType: mimetype,
|
||||
fileSize: size,
|
||||
width,
|
||||
height,
|
||||
minioPath,
|
||||
publicUrl,
|
||||
uploadedBy: req.user.id,
|
||||
assetKey,
|
||||
mediaType,
|
||||
});
|
||||
|
||||
updateImagePageCount(imageId, info.pageCount);
|
||||
|
||||
// Create pdf_pages rows for all pages
|
||||
@@ -214,6 +207,32 @@ router.post('/boards/:boardId/images', upload.single('image'), async (req, res)
|
||||
}
|
||||
}
|
||||
|
||||
const { assetKey, minioPath, width, height, isVideo } = await uploadMedia(board.id, imageId, buffer, mimetype);
|
||||
const publicUrl = getImageUrl(minioPath);
|
||||
|
||||
// Save image record first (media_jobs FK references images)
|
||||
const image = createImage({
|
||||
id: imageId,
|
||||
boardId: board.id,
|
||||
filename: originalname,
|
||||
mimeType: mimetype,
|
||||
fileSize: size,
|
||||
width,
|
||||
height,
|
||||
minioPath,
|
||||
publicUrl,
|
||||
uploadedBy: req.user.id,
|
||||
assetKey,
|
||||
mediaType,
|
||||
});
|
||||
|
||||
// Enqueue background processing after image record exists
|
||||
let jobId = null;
|
||||
if (isVideo) {
|
||||
jobId = uuidv4();
|
||||
createMediaJob({ id: jobId, imageId, boardId: board.id, type: 'poster' });
|
||||
}
|
||||
|
||||
return res.status(201).json({
|
||||
id: image.id,
|
||||
url: publicUrl,
|
||||
|
||||
Reference in New Issue
Block a user