AccessibilityCheckerBackend / api /batch-upload.js
accessibilitychecker's picture
Upload folder using huggingface_hub
bbfde3f verified
const Busboy = require('busboy');
const JSZip = require('jszip');
const fs = require('fs').promises;
const path = require('path');
const sessionManager = require('../lib/session-manager');
const { applyCorsHeaders, handleCorsPreflight } = require('../lib/cors-middleware');
// Helper function to send JSON with proper headers
function sendJson(res, status, data) {
res.setHeader('Content-Type', 'application/json');
res.status(status).end(JSON.stringify(data));
}
module.exports = async (req, res) => {
if (handleCorsPreflight(req, res, { allowedMethods: 'POST, OPTIONS' })) {
return;
}
applyCorsHeaders(req, res, { allowedMethods: 'POST, OPTIONS' });
if (req.method !== 'POST') {
sendJson(res, 405, { error: 'Method not allowed' });
return;
}
try {
const busboy = Busboy({ headers: req.headers });
const uploadedFiles = []; // Store multiple files
const MAX_FILES = 10; // Allow up to 10 files per batch
let fileCount = 0;
busboy.on('file', (fieldname, file, info) => {
fileCount++;
if (fileCount > MAX_FILES) {
file.resume(); // Drain the file stream
return;
}
const filename = info.filename;
const chunks = [];
file.on('data', (chunk) => {
chunks.push(chunk);
});
file.on('end', () => {
const fileData = Buffer.concat(chunks);
uploadedFiles.push({
filename: filename,
data: fileData,
size: fileData.length
});
});
});
busboy.on('finish', async () => {
if (uploadedFiles.length === 0) {
res.status(400).json({ error: 'No valid files uploaded' });
return;
}
if (fileCount > MAX_FILES) {
res.status(400).json({
error: `Too many files. Maximum ${MAX_FILES} files allowed per batch.`,
received: fileCount
});
return;
}
// Get or create session
const sessionId = req.headers['x-session-id'] || req.query.sessionId;
const session = sessionManager.getOrCreateSession(sessionId);
// Process each file and generate individual reports
const batchResults = {
batchId: Date.now(),
sessionId: session.sessionId,
timestamp: new Date().toISOString(),
totalFiles: uploadedFiles.length,
results: []
};
for (let i = 0; i < uploadedFiles.length; i++) {
const fileInfo = uploadedFiles[i];
try {
console.log(`Processing file ${i + 1}/${uploadedFiles.length}: ${fileInfo.filename}`);
// Process individual file (reuse existing logic)
const fileResult = await processSingleFile(fileInfo, session.directory);
// Add file to session
sessionManager.addFileToSession(session.sessionId, {
filename: fileInfo.filename,
reportId: fileResult.reportId,
originalPath: fileResult.originalFilePath,
reportPath: fileResult.reportPath,
processedAt: new Date().toISOString()
});
batchResults.results.push({
fileIndex: i + 1,
filename: fileInfo.filename,
fileSize: fileInfo.size,
success: true,
reportId: fileResult.reportId,
...fileResult.report
});
} catch (error) {
console.error(`Error processing ${fileInfo.filename}:`, error);
batchResults.results.push({
fileIndex: i + 1,
filename: fileInfo.filename,
fileSize: fileInfo.size,
success: false,
error: error.message
});
}
}
// Save batch summary to session directory
const batchReportPath = `${session.directory}/batch-${batchResults.batchId}-summary.json`;
await fs.writeFile(batchReportPath, JSON.stringify(batchResults, null, 2));
// Add batch to session
sessionManager.addBatchToSession(session.sessionId, {
batchId: batchResults.batchId,
timestamp: batchResults.timestamp,
totalFiles: batchResults.totalFiles,
successful: batchResults.results.filter(r => r.success).length,
failed: batchResults.results.filter(r => !r.success).length,
reportPath: batchReportPath
});
// Return batch summary with session info
res.json({
message: `Successfully processed batch of ${uploadedFiles.length} files`,
sessionId: session.sessionId,
batchId: batchResults.batchId,
summary: {
totalFiles: batchResults.totalFiles,
successful: batchResults.results.filter(r => r.success).length,
failed: batchResults.results.filter(r => !r.success).length
},
results: batchResults.results,
expiresIn: '1 hour'
});
});
req.pipe(busboy);
} catch (error) {
console.error('Batch upload error:', error);
res.status(500).json({ error: 'Internal server error during batch processing' });
}
};
// Extract single file processing logic (from existing upload-document.js)
async function processSingleFile(fileInfo, sessionDirectory) {
const { filename, data } = fileInfo;
// Validate DOCX file
if (!filename.toLowerCase().endsWith('.docx')) {
throw new Error(`Invalid file type: ${filename}. Only .docx files are supported.`);
}
let zip;
try {
zip = await JSZip.loadAsync(data);
} catch (error) {
throw new Error(`Invalid DOCX file: ${filename}. Unable to read as ZIP archive.`);
}
// Generate unique report ID for this file
const reportId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// Initialize report structure
const report = {
filename: filename,
reportId: reportId,
timestamp: new Date().toISOString(),
summary: {
flagged: 0,
fixed: 0
},
details: {
hasProtection: false,
removedProtection: false,
languageDefaultFixed: null,
titleNeedsFixing: false,
textShadowsRemoved: false,
fontsNormalized: false,
fontSizesNormalized: false
}
};
// Run all analysis functions (copied from existing logic)
await analyzeDocumentStructure(zip, report);
await analyzeProtection(zip, report);
const shadowFontResults = await analyzeShadowsAndFonts(zip);
// Update report with shadow/font analysis
if (shadowFontResults.hasShadows) {
report.details.textShadowsRemoved = false; // Will be true after remediation
report.summary.flagged++;
}
if (shadowFontResults.hasSerifFonts) {
report.details.fontsNormalized = false; // Will be true after remediation
report.summary.flagged++;
}
if (shadowFontResults.hasSmallFonts) {
report.details.fontSizesNormalized = false; // Will be true after remediation
report.summary.flagged++;
}
// Save original file and report to session directory (not permanent storage)
const originalFilePath = `${sessionDirectory}/original-${reportId}.docx`;
const reportPath = `${sessionDirectory}/${reportId}-accessibility-report.json`;
await fs.writeFile(originalFilePath, data);
await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
return {
reportId: reportId,
report: report,
reportPath: reportPath,
originalFilePath: originalFilePath
};
}
// Copy existing analysis functions (you'll need to import these)
async function analyzeDocumentStructure(zip, report) {
// Implementation from existing upload-document.js
// ... existing logic ...
}
async function analyzeProtection(zip, report) {
// Implementation from existing upload-document.js
// ... existing logic ...
}
async function analyzeShadowsAndFonts(zip) {
// Implementation from existing upload-document.js
// ... existing logic ...
}