| 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');
|
|
|
|
|
| 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 = [];
|
| const MAX_FILES = 10;
|
| let fileCount = 0;
|
|
|
| busboy.on('file', (fieldname, file, info) => {
|
| fileCount++;
|
|
|
| if (fileCount > MAX_FILES) {
|
| file.resume();
|
| 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;
|
| }
|
|
|
|
|
| const sessionId = req.headers['x-session-id'] || req.query.sessionId;
|
| const session = sessionManager.getOrCreateSession(sessionId);
|
|
|
|
|
| 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}`);
|
|
|
|
|
| const fileResult = await processSingleFile(fileInfo, session.directory);
|
|
|
|
|
| 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
|
| });
|
| }
|
| }
|
|
|
|
|
| const batchReportPath = `${session.directory}/batch-${batchResults.batchId}-summary.json`;
|
| await fs.writeFile(batchReportPath, JSON.stringify(batchResults, null, 2));
|
|
|
|
|
| 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
|
| });
|
|
|
|
|
| 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' });
|
| }
|
| };
|
|
|
|
|
| async function processSingleFile(fileInfo, sessionDirectory) {
|
| const { filename, data } = fileInfo;
|
|
|
|
|
| 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.`);
|
| }
|
|
|
|
|
| const reportId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
|
|
|
| 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
|
| }
|
| };
|
|
|
|
|
| await analyzeDocumentStructure(zip, report);
|
| await analyzeProtection(zip, report);
|
| const shadowFontResults = await analyzeShadowsAndFonts(zip);
|
|
|
|
|
| if (shadowFontResults.hasShadows) {
|
| report.details.textShadowsRemoved = false;
|
| report.summary.flagged++;
|
| }
|
|
|
| if (shadowFontResults.hasSerifFonts) {
|
| report.details.fontsNormalized = false;
|
| report.summary.flagged++;
|
| }
|
|
|
| if (shadowFontResults.hasSmallFonts) {
|
| report.details.fontSizesNormalized = false;
|
| report.summary.flagged++;
|
| }
|
|
|
|
|
| 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
|
| };
|
| }
|
|
|
|
|
| async function analyzeDocumentStructure(zip, report) {
|
|
|
|
|
| }
|
|
|
| async function analyzeProtection(zip, report) {
|
|
|
|
|
| }
|
|
|
| async function analyzeShadowsAndFonts(zip) {
|
|
|
|
|
| } |