feat: accept PDF uploads — pdfinfo extraction, pdf_pages rows, thumbnail job queuing
This commit is contained in:
@@ -26,6 +26,7 @@ const MIME_TO_EXT = {
|
|||||||
'video/mp4': '.mp4',
|
'video/mp4': '.mp4',
|
||||||
'video/webm': '.webm',
|
'video/webm': '.webm',
|
||||||
'video/quicktime': '.mov',
|
'video/quicktime': '.mov',
|
||||||
|
'application/pdf': '.pdf',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ const https = require('https');
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
const { authMiddleware } = require('../auth');
|
const { authMiddleware } = require('../auth');
|
||||||
const { getBoard, getCollectionMember, createImage, createMediaJob } = require('../db');
|
const { getBoard, getCollectionMember, createImage, createMediaJob, createPdfPage, updateImagePageCount } = require('../db');
|
||||||
const { putBuffer, getImageUrl, MIME_TO_EXT, MAX_FILE_SIZE } = require('../minio');
|
const { putBuffer, getImageUrl, MIME_TO_EXT, MAX_FILE_SIZE } = require('../minio');
|
||||||
|
const { pdfInfo, bufferToTempFile } = require('../pdf-utils');
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -25,7 +26,9 @@ const VIDEO_MIME_TYPES = [
|
|||||||
'video/quicktime',
|
'video/quicktime',
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALLOWED_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES];
|
const PDF_MIME_TYPES = ['application/pdf'];
|
||||||
|
|
||||||
|
const ALLOWED_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES, ...PDF_MIME_TYPES];
|
||||||
|
|
||||||
const MAX_FILE_SIZE_LABEL = `${MAX_FILE_SIZE / 1024 / 1024}MB`;
|
const MAX_FILE_SIZE_LABEL = `${MAX_FILE_SIZE / 1024 / 1024}MB`;
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ async function getImageDimensions(buffer, mimeType) {
|
|||||||
*/
|
*/
|
||||||
function classifyMedia(mimeType) {
|
function classifyMedia(mimeType) {
|
||||||
if (VIDEO_MIME_TYPES.includes(mimeType)) return 'video';
|
if (VIDEO_MIME_TYPES.includes(mimeType)) return 'video';
|
||||||
|
if (PDF_MIME_TYPES.includes(mimeType)) return 'pdf';
|
||||||
return 'image';
|
return 'image';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +157,63 @@ router.post('/boards/:boardId/images', upload.single('image'), async (req, res)
|
|||||||
createMediaJob({ id: jobId, imageId, boardId: board.id, type: 'poster' });
|
createMediaJob({ id: jobId, imageId, boardId: board.id, type: 'poster' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PDF: extract page info, create pdf_pages rows, queue thumbnail jobs
|
||||||
|
if (mediaType === 'pdf') {
|
||||||
|
const { tmpPath, cleanup } = bufferToTempFile(buffer, '.pdf');
|
||||||
|
try {
|
||||||
|
const info = await pdfInfo(tmpPath);
|
||||||
|
if (info.pageCount > 500) {
|
||||||
|
return res.status(400).json({ error: 'PDF exceeds 500 page limit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
updateImagePageCount(imageId, info.pageCount);
|
||||||
|
|
||||||
|
// Create pdf_pages rows for all pages
|
||||||
|
for (let i = 0; i < info.pageCount; i++) {
|
||||||
|
const dim = info.dimensions[i] || { w: null, h: null };
|
||||||
|
createPdfPage(uuidv4(), imageId, i + 1, dim.w, dim.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue thumbnail jobs for first 20 pages
|
||||||
|
const thumbLimit = Math.min(info.pageCount, 20);
|
||||||
|
for (let i = 1; i <= thumbLimit; i++) {
|
||||||
|
createMediaJob({
|
||||||
|
id: uuidv4(),
|
||||||
|
imageId,
|
||||||
|
boardId: board.id,
|
||||||
|
type: 'pdf-thumbnail',
|
||||||
|
priority: 0,
|
||||||
|
metaJson: JSON.stringify({ pageNumber: i }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// For single-page PDFs, also queue hires
|
||||||
|
if (info.pageCount === 1) {
|
||||||
|
createMediaJob({
|
||||||
|
id: uuidv4(),
|
||||||
|
imageId,
|
||||||
|
boardId: board.id,
|
||||||
|
type: 'pdf-hires',
|
||||||
|
priority: 10,
|
||||||
|
metaJson: JSON.stringify({ pageNumber: 1 }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
id: image.id,
|
||||||
|
media_type: 'pdf',
|
||||||
|
page_count: info.pageCount,
|
||||||
|
dimensions: info.dimensions,
|
||||||
|
asset_key: image.asset_key,
|
||||||
|
width: info.dimensions[0]?.w || null,
|
||||||
|
height: info.dimensions[0]?.h || null,
|
||||||
|
single_page: info.pageCount === 1,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
id: image.id,
|
id: image.id,
|
||||||
url: publicUrl,
|
url: publicUrl,
|
||||||
|
|||||||
Reference in New Issue
Block a user