const generateId = require("shortid"); const express = require('express'); const { Router } = require('express'); const path = require('path'); const UploadGroup = require('../models/uploadGroup'); const groupRepository = require('../repositories/GroupRepository'); const dbManager = require('../database/DatabaseManager'); const ImagePreviewService = require('../services/ImagePreviewService'); const router = Router(); /** * @swagger * /upload/batch: * post: * tags: [Upload] * summary: Batch upload multiple images and create a group * description: Uploads multiple images at once, creates previews, and stores them as a group with metadata and consent information * requestBody: * required: true * content: * multipart/form-data: * schema: * type: object * required: * - images * - consents * properties: * images: * type: array * items: * type: string * format: binary * description: Multiple image files to upload * metadata: * type: string * description: JSON string with group metadata (year, title, description, name) * example: '{"year":2024,"title":"Familie Mueller","description":"Weihnachtsfeier","name":"Mueller"}' * descriptions: * type: string * description: JSON array with image descriptions * example: '[{"index":0,"description":"Gruppenfoto"},{"index":1,"description":"Werkstatt"}]' * consents: * type: string * description: JSON object with consent flags (workshopConsent is required) * example: '{"workshopConsent":true,"socialMedia":{"facebook":false,"instagram":true}}' * responses: * 200: * description: Batch upload successful * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * example: true * groupId: * type: string * example: "cTV24Yn-a" * managementToken: * type: string * format: uuid * example: "550e8400-e29b-41d4-a716-446655440000" * filesProcessed: * type: integer * example: 5 * message: * type: string * example: "5 Bilder erfolgreich hochgeladen" * 400: * description: Bad request - missing files or workshop consent * content: * application/json: * schema: * type: object * properties: * error: * type: string * message: * type: string * 500: * description: Server error during batch upload */ // Batch-Upload für mehrere Bilder router.post('/upload/batch', async (req, res) => { /* #swagger.tags = ['Upload'] #swagger.summary = 'Batch upload multiple images' #swagger.description = 'Accepts multiple images + metadata/consents and creates a managed group with management token.' #swagger.consumes = ['multipart/form-data'] #swagger.responses[200] = { description: 'Batch upload successful (returns management token)' } #swagger.responses[400] = { description: 'Missing files or workshop consent' } #swagger.responses[500] = { description: 'Unexpected server error' } */ try { // Überprüfe ob Dateien hochgeladen wurden if (!req.files || !req.files.images) { return res.status(400).json({ error: 'No images uploaded', message: 'Keine Bilder wurden hochgeladen' }); } // Metadaten aus dem Request body let metadata = {}; let descriptions = []; let consents = {}; try { metadata = req.body.metadata ? JSON.parse(req.body.metadata) : {}; descriptions = req.body.descriptions ? JSON.parse(req.body.descriptions) : []; consents = req.body.consents ? JSON.parse(req.body.consents) : {}; } catch (e) { console.error('Error parsing metadata/descriptions/consents:', e); metadata = { description: req.body.description || "" }; descriptions = []; consents = {}; } // Validiere Workshop Consent (Pflichtfeld) if (!consents.workshopConsent) { return res.status(400).json({ error: 'Workshop consent required', message: 'Die Zustimmung zur Anzeige in der Werkstatt ist erforderlich' }); } // Erstelle neue Upload-Gruppe mit erweiterten Metadaten const group = new UploadGroup(metadata); // Handle sowohl einzelne Datei als auch Array von Dateien const files = Array.isArray(req.files.images) ? req.files.images : [req.files.images]; console.log(`Processing ${files.length} files for batch upload`); // Verarbeite alle Dateien const processedFiles = []; for (let i = 0; i < files.length; i++) { const file = files[i]; // Generiere eindeutigen Dateinamen const fileEnding = file.name.split(".").pop(); const fileName = generateId() + '.' + fileEnding; // Speichere Datei unter data/images const path = require('path'); const { UPLOAD_FS_DIR } = require('../constants'); const uploadPath = path.join(__dirname, '..', UPLOAD_FS_DIR, fileName); file.mv(uploadPath, (err) => { if (err) { console.error('Error saving file:', err); } }); // Füge Bild zur Gruppe hinzu group.addImage(fileName, file.name, i + 1); processedFiles.push({ fileName, originalName: file.name, size: file.size }); } // Generate previews for all uploaded images asynchronously const previewDir = path.join(__dirname, '..', require('../constants').PREVIEW_FS_DIR); const uploadDir = path.join(__dirname, '..', require('../constants').UPLOAD_FS_DIR); // Generate previews in background (don't wait) ImagePreviewService.generatePreviewsForGroup( processedFiles.map(f => ({ file_name: f.fileName, file_path: `/upload/${f.fileName}` })), uploadDir, previewDir ).then(results => { const successCount = results.filter(r => r.success).length; console.log(`Preview generation completed: ${successCount}/${results.length} successful`); // Update preview_path in database for successful previews results.forEach(async (result) => { if (result.success) { try { await dbManager.run(` UPDATE images SET preview_path = ? WHERE group_id = ? AND file_name = ? `, [result.previewPath, group.groupId, result.fileName]); } catch (err) { console.error(`Failed to update preview_path for ${result.fileName}:`, err); } } }); }).catch(err => { console.error('Preview generation failed:', err); }); // Speichere Gruppe mit Consents in SQLite const createResult = await groupRepository.createGroupWithConsent({ groupId: group.groupId, year: group.year, title: group.title, description: group.description, name: group.name, uploadDate: group.uploadDate, images: processedFiles.map((file, index) => { // Finde passende Beschreibung für dieses Bild (match by fileName or originalName) const descObj = descriptions.find(d => d.fileName === file.originalName || d.fileName === file.fileName ); const imageDescription = descObj ? descObj.description : null; // Validierung: Max 200 Zeichen if (imageDescription && imageDescription.length > 200) { console.warn(`Image description for ${file.originalName} exceeds 200 characters, truncating`); } return { fileName: file.fileName, originalName: file.originalName, filePath: `/upload/${file.fileName}`, uploadOrder: index + 1, fileSize: file.size, mimeType: files[index].mimetype, imageDescription: imageDescription ? imageDescription.slice(0, 200) : null }; }) }, consents.workshopConsent, consents.socialMediaConsents || [] ); console.log(`Successfully saved group ${group.groupId} with ${files.length} images to database`); // Erfolgreiche Antwort mit Management-Token res.json({ groupId: group.groupId, managementToken: createResult.managementToken, message: 'Batch upload successful', imageCount: files.length, year: group.year, title: group.title, description: group.description, name: group.name, uploadDate: group.uploadDate, files: processedFiles }); } catch (error) { console.error('Batch upload error:', error); res.status(500).json({ error: 'Internal server error', message: 'Ein Fehler ist beim Upload aufgetreten', details: error.message }); } }); module.exports = router;