const dbManager = require('../database/DatabaseManager'); class GroupRepository { // Erstelle neue Gruppe mit Bildern (Transaction) async createGroup(groupData) { return await dbManager.transaction(async (db) => { // Füge Gruppe hinzu const groupResult = await db.run(` INSERT INTO groups (group_id, year, title, description, name, upload_date) VALUES (?, ?, ?, ?, ?, ?) `, [ groupData.groupId, groupData.year, groupData.title, groupData.description || null, groupData.name || null, groupData.uploadDate ]); // Füge Bilder hinzu if (groupData.images && groupData.images.length > 0) { for (const image of groupData.images) { await db.run(` INSERT INTO images (group_id, file_name, original_name, file_path, upload_order, file_size, mime_type, preview_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, [ groupData.groupId, image.fileName, image.originalName, image.filePath, image.uploadOrder, image.fileSize || null, image.mimeType || null, image.previewPath || null ]); } } return groupResult.id; }); } // Hole Gruppe mit Bildern nach Group-ID async getGroupById(groupId) { const group = await dbManager.get(` SELECT * FROM groups WHERE group_id = ? `, [groupId]); if (!group) { return null; } const images = await dbManager.all(` SELECT * FROM images WHERE group_id = ? ORDER BY upload_order ASC `, [groupId]); return { groupId: group.group_id, year: group.year, title: group.title, description: group.description, name: group.name, uploadDate: group.upload_date, images: images.map(img => ({ fileName: img.file_name, originalName: img.original_name, filePath: img.file_path, previewPath: img.preview_path, uploadOrder: img.upload_order, fileSize: img.file_size, mimeType: img.mime_type })), imageCount: images.length }; } // Hole alle Gruppen (mit Paginierung) async getAllGroups(limit = null, offset = 0) { let sql = ` SELECT g.*, COUNT(i.id) as image_count FROM groups g LEFT JOIN images i ON g.group_id = i.group_id GROUP BY g.group_id ORDER BY g.upload_date DESC `; const params = []; if (limit) { sql += ` LIMIT ? OFFSET ?`; params.push(limit, offset); } const groups = await dbManager.all(sql, params); return { groups: groups.map(group => ({ groupId: group.group_id, year: group.year, title: group.title, description: group.description, name: group.name, uploadDate: group.upload_date, imageCount: group.image_count })), total: groups.length }; } // Hole alle Gruppen mit Bildern für Slideshow (nur freigegebene) async getAllGroupsWithImages() { const groupFormatter = require('../utils/groupFormatter'); const groups = await dbManager.all(` SELECT * FROM groups WHERE approved = TRUE ORDER BY upload_date DESC `); const result = []; for (const group of groups) { const images = await dbManager.all(` SELECT * FROM images WHERE group_id = ? ORDER BY upload_order ASC `, [group.group_id]); result.push(groupFormatter.formatGroupDetail(group, images)); } return result; } // Lösche Gruppe und alle Bilder async deleteGroup(groupId) { return await dbManager.transaction(async (db) => { // Erst alle Bilddateien physisch löschen const images = await db.all(` SELECT * FROM images WHERE group_id = ? `, [groupId]); const fs = require('fs').promises; const path = require('path'); for (const image of images) { try { const absolutePath = path.join(__dirname, '..', image.file_path); await fs.unlink(absolutePath); console.log(`✓ Bilddatei gelöscht: ${absolutePath}`); } catch (error) { console.warn(`⚠️ Konnte Bilddatei nicht löschen: ${image.file_path}`, error.message); } } // Dann Gruppe aus Datenbank löschen (Bilder werden durch CASCADE gelöscht) const result = await db.run(` DELETE FROM groups WHERE group_id = ? `, [groupId]); console.log(`✓ Gruppe gelöscht: ${groupId} (${images.length} Bilder)`); return result.changes > 0; }); } // Update Gruppe async updateGroup(groupId, updates) { const setClause = []; const params = []; if (updates.year !== undefined) { setClause.push('year = ?'); params.push(updates.year); } if (updates.title !== undefined) { setClause.push('title = ?'); params.push(updates.title); } if (updates.description !== undefined) { setClause.push('description = ?'); params.push(updates.description); } if (updates.name !== undefined) { setClause.push('name = ?'); params.push(updates.name); } if (setClause.length === 0) { return false; } params.push(groupId); const result = await dbManager.run(` UPDATE groups SET ${setClause.join(', ')} WHERE group_id = ? `, params); return result.changes > 0; } // Gruppe Freigabe-Status aktualisieren async updateGroupApproval(groupId, approved) { const result = await dbManager.run(` UPDATE groups SET approved = ? WHERE group_id = ? `, [approved, groupId]); return result.changes > 0; } // Einzelnes Bild löschen async deleteImage(groupId, imageId) { return await dbManager.transaction(async (db) => { // Prüfe ob Bild existiert const image = await db.get(` SELECT * FROM images WHERE id = ? AND group_id = ? `, [imageId, groupId]); if (!image) { return false; } // Lösche Datei vom Dateisystem const fs = require('fs').promises; const path = require('path'); try { // Konvertiere relativen Pfad zu absolutem Pfad im Container // image.file_path ist "/upload/dateiname.ext", wir brauchen "/usr/src/app/src/upload/dateiname.ext" const absolutePath = path.join(__dirname, '..', image.file_path); await fs.unlink(absolutePath); console.log(`✓ Bilddatei gelöscht: ${absolutePath}`); } catch (error) { console.warn(`⚠️ Konnte Bilddatei nicht löschen: ${image.file_path}`, error.message); // Datei-Löschfehler sollen nicht das Löschen aus der Datenbank verhindern } // Lösche aus Datenbank const result = await db.run(` DELETE FROM images WHERE id = ? AND group_id = ? `, [imageId, groupId]); // Aktualisiere upload_order der verbleibenden Bilder await db.run(` UPDATE images SET upload_order = upload_order - 1 WHERE group_id = ? AND upload_order > ? `, [groupId, image.upload_order]); return result.changes > 0; }); } // Alle Gruppen für Moderation (mit Freigabestatus und Bildanzahl) async getAllGroupsWithModerationInfo() { const groupFormatter = require('../utils/groupFormatter'); const groups = await dbManager.all(` SELECT * FROM groups ORDER BY approved ASC, upload_date DESC `); const result = []; for (const group of groups) { const images = await dbManager.all(` SELECT * FROM images WHERE group_id = ? ORDER BY upload_order ASC `, [group.group_id]); result.push(groupFormatter.formatGroupDetail(group, images)); } return result; } // Hole Gruppe für Moderation (inkl. nicht-freigegebene) async getGroupForModeration(groupId) { const group = await dbManager.get(` SELECT * FROM groups WHERE group_id = ? `, [groupId]); if (!group) { return null; } const images = await dbManager.all(` SELECT * FROM images WHERE group_id = ? ORDER BY upload_order ASC `, [groupId]); const groupFormatter = require('../utils/groupFormatter'); return groupFormatter.formatGroupDetail(group, images); } // Statistiken (erweitert um Freigabe-Status) async getStats() { const groupCount = await dbManager.get('SELECT COUNT(*) as count FROM groups'); const imageCount = await dbManager.get('SELECT COUNT(*) as count FROM images'); const approvedGroups = await dbManager.get('SELECT COUNT(*) as count FROM groups WHERE approved = TRUE'); const pendingGroups = await dbManager.get('SELECT COUNT(*) as count FROM groups WHERE approved = FALSE'); const latestGroup = await dbManager.get(` SELECT upload_date FROM groups ORDER BY upload_date DESC LIMIT 1 `); return { totalGroups: groupCount.count, totalImages: imageCount.count, approvedGroups: approvedGroups.count, pendingGroups: pendingGroups.count, latestUpload: latestGroup ? latestGroup.upload_date : null }; } // Aktualisiere die Reihenfolge der Bilder in einer Gruppe async updateImageOrder(groupId, imageIds) { if (!groupId) { throw new Error('Group ID is required'); } if (!Array.isArray(imageIds) || imageIds.length === 0) { throw new Error('Image IDs array is required and cannot be empty'); } return await dbManager.transaction(async (db) => { // Zunächst prüfen, ob die Gruppe existiert const group = await db.get('SELECT group_id FROM groups WHERE group_id = ?', [groupId]); if (!group) { throw new Error(`Group with ID ${groupId} not found`); } // Alle Bilder der Gruppe laden und prüfen ob alle IDs gültig sind const existingImages = await db.all( 'SELECT id, file_name FROM images WHERE group_id = ? ORDER BY upload_order ASC', [groupId] ); const existingImageIds = existingImages.map(img => img.id); // Prüfen ob alle übergebenen IDs zur Gruppe gehören const invalidIds = imageIds.filter(id => !existingImageIds.includes(id)); if (invalidIds.length > 0) { throw new Error(`Invalid image IDs found: ${invalidIds.join(', ')}. These images do not belong to group ${groupId}`); } // Prüfen ob alle Bilder der Gruppe in der Liste enthalten sind const missingIds = existingImageIds.filter(id => !imageIds.includes(id)); if (missingIds.length > 0) { throw new Error(`Missing image IDs: ${missingIds.join(', ')}. All images of the group must be included in the reorder operation`); } // Batch-Update der upload_order Werte let updateCount = 0; for (let i = 0; i < imageIds.length; i++) { const imageId = imageIds[i]; const newOrder = i + 1; // upload_order beginnt bei 1 const result = await db.run( 'UPDATE images SET upload_order = ? WHERE id = ? AND group_id = ?', [newOrder, imageId, groupId] ); if (result.changes === 0) { throw new Error(`Failed to update image with ID ${imageId}`); } updateCount += result.changes; } return { groupId: groupId, updatedImages: updateCount, newOrder: imageIds }; }); } } module.exports = new GroupRepository();