Project-Image-Uploader/backend/src/routes/admin.js
matthias.lotz 6332b82c6a Feature Request: admin session security
- replace bearer auth with session+CSRF flow and add admin user directory

- update frontend moderation flow, force password change gate, and new CLI

- refresh changelog/docs/feature plan + ensure swagger dev experience
2025-11-23 21:18:42 +01:00

1051 lines
35 KiB
JavaScript

const express = require('express');
const router = express.Router();
const DeletionLogRepository = require('../repositories/DeletionLogRepository');
const ManagementAuditLogRepository = require('../repositories/ManagementAuditLogRepository');
const GroupRepository = require('../repositories/GroupRepository');
const GroupCleanupService = require('../services/GroupCleanupService');
const AdminAuthService = require('../services/AdminAuthService');
const { getStatistics: getRateLimiterStats } = require('../middlewares/rateLimiter');
const { requireAdminAuth } = require('../middlewares/auth');
const { requireCsrf } = require('../middlewares/csrf');
// GroupCleanupService ist bereits eine Instanz, keine Klasse
const cleanupService = GroupCleanupService;
// Apply admin authentication to ALL routes in this router
router.use(requireAdminAuth);
router.use(requireCsrf);
router.post('/users', async (req, res) => {
/*
#swagger.tags = ['Admin - Users']
#swagger.summary = 'Create a new admin user'
#swagger.description = 'Adds an additional admin (or auditor) via API'
#swagger.requestBody = {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
required: ['username', 'password'],
properties: {
username: { type: 'string', example: 'admin2' },
password: { type: 'string', example: 'SehrSicher123!' },
role: { type: 'string', example: 'admin' },
requirePasswordChange: { type: 'boolean', example: true }
}
}
}
}
}
#swagger.responses[201] = {
description: 'Admin user created',
schema: {
success: true,
user: {
id: 5,
username: 'admin2',
role: 'admin',
requiresPasswordChange: false
}
}
}
*/
try {
const { username, password, role, requirePasswordChange } = req.body || {};
const user = await AdminAuthService.createAdminUser({
username,
password,
role,
requiresPasswordChange: Boolean(requirePasswordChange)
});
res.status(201).json({
success: true,
user
});
} catch (error) {
console.error('[Admin API] create user failed:', error.message);
if (['USERNAME_REQUIRED', 'PASSWORD_TOO_WEAK'].includes(error.message)) {
return res.status(400).json({ error: error.message });
}
if (error.message === 'USERNAME_IN_USE') {
return res.status(409).json({ error: 'USERNAME_IN_USE' });
}
res.status(500).json({ error: 'CREATE_ADMIN_FAILED' });
}
});
router.get('/deletion-log', async (req, res) => {
/*
#swagger.tags = ['Admin - Deletion Log']
#swagger.summary = 'Get recent deletion log entries'
#swagger.description = 'Returns recent deletion log entries with optional limit'
#swagger.parameters['limit'] = {
in: 'query',
type: 'integer',
description: 'Number of entries to return (1-1000)',
example: 10
}
#swagger.responses[200] = {
description: 'Deletion log entries',
schema: {
success: true,
deletions: [],
total: 2,
limit: 10
}
}
#swagger.responses[400] = {
description: 'Invalid limit parameter'
}
*/
try {
const limit = parseInt(req.query.limit) || 10;
if (limit < 1 || limit > 1000) {
return res.status(400).json({
error: 'Invalid limit',
message: 'Limit must be between 1 and 1000'
});
}
const deletions = await DeletionLogRepository.getRecentDeletions(limit);
const total = deletions.length;
res.json({
success: true,
deletions: deletions,
total: total,
limit: limit
});
} catch (error) {
console.error('Error fetching deletion log:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/deletion-log/all', async (req, res) => {
/*
#swagger.tags = ['Admin - Deletion Log']
#swagger.summary = 'Get all deletion log entries'
#swagger.description = 'Returns complete deletion log without pagination'
#swagger.responses[200] = {
description: 'All deletion log entries',
schema: {
success: true,
deletions: [],
total: 50
}
}
*/
try {
const deletions = await DeletionLogRepository.getAllDeletions();
res.json({
success: true,
deletions: deletions,
total: deletions.length
});
} catch (error) {
console.error('Error fetching all deletion logs:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/deletion-log/stats', async (req, res) => {
/*
#swagger.tags = ['Admin - Deletion Log']
#swagger.summary = 'Get deletion statistics'
#swagger.description = 'Returns aggregated statistics about deleted images'
#swagger.responses[200] = {
description: 'Deletion statistics',
schema: {
success: true,
totalDeleted: 12,
totalImages: 348,
totalSize: '19.38 MB',
totalSizeBytes: 20324352,
lastCleanup: '2025-11-15T10:30:00Z'
}
}
*/
try {
const stats = await DeletionLogRepository.getDeletionStatistics();
// Format file size
const formatBytes = (bytes) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
res.json({
success: true,
totalDeleted: stats.totalDeleted,
totalImages: stats.totalImages,
totalSize: formatBytes(stats.totalSize),
totalSizeBytes: stats.totalSize,
lastCleanup: stats.lastCleanup
});
} catch (error) {
console.error('Error fetching deletion statistics:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.post('/cleanup/trigger', async (req, res) => {
/*
#swagger.tags = ['Admin - Cleanup']
#swagger.summary = 'Manually trigger cleanup of unapproved groups'
#swagger.description = 'Deletes groups that have not been approved within retention period'
#swagger.responses[200] = {
description: 'Cleanup completed',
schema: {
success: true,
deletedGroups: 3,
message: '3 alte unbestätigte Gruppen gelöscht'
}
}
*/
try {
console.log('[Admin API] Manual cleanup triggered');
const result = await cleanupService.performScheduledCleanup();
res.json({
success: true,
result: result,
message: `Cleanup completed: ${result.deletedGroups} groups deleted`
});
} catch (error) {
console.error('[Admin API] Error triggering cleanup:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/cleanup/preview', async (req, res) => {
/*
#swagger.tags = ['Admin - Cleanup']
#swagger.summary = 'Preview groups that would be deleted'
#swagger.description = 'Dry-run showing which unapproved groups are eligible for deletion'
#swagger.responses[200] = {
description: 'Preview of groups to delete',
schema: {
success: true,
groupsToDelete: 2,
groups: [{
id: 'abc123',
groupName: 'Familie_Mueller',
uploadDate: '2025-10-01T12:00:00Z',
daysUntilDeletion: -5,
imageCount: 8
}],
message: '2 groups would be deleted'
}
}
*/
try {
const groups = await cleanupService.findGroupsForDeletion();
// Berechne Tage bis zur Löschung für jede Gruppe
const groupsWithDays = groups.map(group => ({
...group,
daysUntilDeletion: cleanupService.getDaysUntilDeletion(group.uploadDate)
}));
res.json({
success: true,
groupsToDelete: groupsWithDays.length,
groups: groupsWithDays,
message: groupsWithDays.length === 0
? 'No groups would be deleted'
: `${groupsWithDays.length} groups would be deleted`
});
} catch (error) {
console.error('[Admin API] Error previewing cleanup:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/rate-limiter/stats', async (req, res) => {
/*
#swagger.tags = ['Admin - Monitoring']
#swagger.summary = 'Get rate limiter statistics'
#swagger.description = 'Returns statistics about rate limiting (blocked requests, active limits)'
#swagger.responses[200] = {
description: 'Rate limiter statistics',
schema: {
success: true,
totalRequests: 1523,
blockedRequests: 12,
activeClients: 45
}
}
*/
try {
const stats = getRateLimiterStats();
res.json({
success: true,
...stats
});
} catch (error) {
console.error('[Admin API] Error fetching rate-limiter stats:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/management-audit', async (req, res) => {
/*
#swagger.tags = ['Admin - Monitoring']
#swagger.summary = 'Get management audit log entries'
#swagger.description = 'Returns recent management portal activity logs'
#swagger.parameters['limit'] = {
in: 'query',
type: 'integer',
description: 'Number of entries to return (1-1000)',
example: 100
}
#swagger.responses[200] = {
description: 'Audit log entries',
schema: {
success: true,
logs: [],
total: 15,
limit: 100
}
}
#swagger.responses[400] = {
description: 'Invalid limit parameter'
}
*/
try {
const limit = parseInt(req.query.limit) || 100;
if (limit < 1 || limit > 1000) {
return res.status(400).json({
error: 'Invalid limit',
message: 'Limit must be between 1 and 1000'
});
}
const logs = await ManagementAuditLogRepository.getRecentLogs(limit);
res.json({
success: true,
logs: logs,
total: logs.length,
limit: limit
});
} catch (error) {
console.error('[Admin API] Error fetching management audit log:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/management-audit/stats', async (req, res) => {
/*
#swagger.tags = ['Admin - Monitoring']
#swagger.summary = 'Get management audit log statistics'
#swagger.description = 'Returns aggregated statistics about management portal activity'
#swagger.responses[200] = {
description: 'Audit log statistics',
schema: {
success: true,
totalActions: 523,
actionsByType: {
'update': 312,
'delete': 45,
'approve': 166
},
lastAction: '2025-11-15T14:30:00Z'
}
}
*/
try {
const stats = await ManagementAuditLogRepository.getStatistics();
res.json({
success: true,
...stats
});
} catch (error) {
console.error('[Admin API] Error fetching audit log stats:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
router.get('/management-audit/group/:groupId', async (req, res) => {
/*
#swagger.tags = ['Admin - Monitoring']
#swagger.summary = 'Get audit log for specific group'
#swagger.description = 'Returns all management actions performed on a specific group'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.responses[200] = {
description: 'Audit log for group',
schema: {
success: true,
groupId: 'abc123def456',
logs: [],
total: 8
}
}
*/
try {
const { groupId } = req.params;
const logs = await ManagementAuditLogRepository.getLogsByGroupId(groupId);
res.json({
success: true,
groupId: groupId,
logs: logs,
total: logs.length
});
} catch (error) {
console.error('[Admin API] Error fetching audit log for group:', error);
res.status(500).json({
error: 'Internal server error',
message: error.message
});
}
});
// ============================================================================
// GRUPPEN-MODERATION (verschoben von groups.js)
// ============================================================================
router.get('/groups', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Get all groups for moderation'
#swagger.description = 'Returns all groups including unapproved ones with moderation info and consent data'
#swagger.parameters['workshopOnly'] = {
in: 'query',
type: 'boolean',
description: 'Filter by workshop consent',
example: false
}
#swagger.parameters['platform'] = {
in: 'query',
type: 'string',
description: 'Filter by social media platform',
example: 'instagram'
}
#swagger.responses[200] = {
description: 'All groups with moderation info',
schema: {
success: true,
groups: [{
groupId: 'abc123',
groupName: 'Familie_Mueller',
isApproved: false,
uploadDate: '2025-11-01T10:00:00Z',
imageCount: 12,
socialMediaConsents: []
}]
}
}
*/
try {
const { workshopOnly, platform, consents } = req.query;
// Hole alle Gruppen mit vollständigen Infos (inkl. Bilder)
let allGroups = await GroupRepository.getAllGroupsWithModerationInfo();
// Füge Consent-Daten für jede Gruppe hinzu
const groupsWithConsents = await Promise.all(
allGroups.map(async (group) => {
const consentData = await GroupRepository.getSocialMediaConsentsForGroup(group.groupId);
return {
...group,
socialMediaConsents: consentData
};
})
);
// Jetzt filtern wir basierend auf den Query-Parametern
let filteredGroups = groupsWithConsents;
// Neuer Multi-Checkbox Filter
if (consents) {
const selectedConsents = consents.split(','); // z.B. ['workshop', 'facebook', 'instagram']
filteredGroups = groupsWithConsents.filter(group => {
// Gruppe muss mindestens einen der ausgewählten Consents haben
return selectedConsents.some(consentType => {
if (consentType === 'workshop') {
return group.display_in_workshop === 1 || group.display_in_workshop === true;
} else {
// Social Media Platform (facebook, instagram, tiktok)
return group.socialMediaConsents &&
group.socialMediaConsents.some(consent =>
consent.platform_name === consentType &&
(consent.consented === 1 || consent.consented === true) &&
(consent.revoked !== 1 && consent.revoked !== true)
);
}
});
});
} else if (workshopOnly === 'true') {
// Filter: Nur Gruppen MIT Werkstatt-Consent aber OHNE zugestimmte Social Media Consents
filteredGroups = groupsWithConsents.filter(group => {
// Muss Werkstatt-Consent haben
if (!group.display_in_workshop) return false;
// Darf KEINE zugestimmten Social Media Consents haben
const hasConsentedSocialMedia = group.socialMediaConsents &&
group.socialMediaConsents.some(consent => consent.consented === 1 || consent.consented === true);
return !hasConsentedSocialMedia;
});
} else if (platform) {
// Filter: Gruppen mit bestimmter Social Media Platform (unabhängig vom Werkstatt-Consent)
filteredGroups = groupsWithConsents.filter(group =>
group.socialMediaConsents &&
group.socialMediaConsents.some(consent =>
consent.platform_name === platform && (consent.consented === 1 || consent.consented === true)
)
);
}
// else: Kein Filter - zeige ALLE Gruppen (nicht filtern)
res.json({
groups: filteredGroups,
totalCount: filteredGroups.length,
pendingCount: filteredGroups.filter(g => !g.approved).length,
approvedCount: filteredGroups.filter(g => g.approved).length
});
} catch (error) {
console.error('Error fetching moderation groups:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Laden der Moderations-Gruppen',
details: error.message
});
}
});
router.get('/groups/:groupId', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Get single group for moderation'
#swagger.description = 'Returns detailed info for a specific group including unapproved ones'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.responses[200] = {
description: 'Group details with images',
schema: {
groupId: 'abc123',
groupName: 'Familie_Mueller',
isApproved: true,
images: []
}
}
#swagger.responses[404] = {
description: 'Group not found'
}
*/
try {
const { groupId } = req.params;
const group = await GroupRepository.getGroupForModeration(groupId);
if (!group) {
return res.status(404).json({
error: 'Group not found',
message: `Gruppe mit ID ${groupId} wurde nicht gefunden`
});
}
res.json(group);
} catch (error) {
console.error('Error fetching group for moderation:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Laden der Gruppe für Moderation',
details: error.message
});
}
});
router.patch('/groups/:groupId/approve', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Approve a group'
#swagger.description = 'Marks a group as approved, making it publicly visible'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.parameters['body'] = {
in: 'body',
required: false,
schema: {
approved: true
}
}
#swagger.responses[200] = {
description: 'Group approved successfully',
schema: {
success: true,
message: 'Gruppe erfolgreich freigegeben'
}
}
#swagger.responses[404] = {
description: 'Group not found'
}
*/
try {
const { groupId } = req.params;
const { approved } = req.body;
// Validierung
if (typeof approved !== 'boolean') {
return res.status(400).json({
error: 'Invalid request',
message: 'approved muss ein boolean Wert sein'
});
}
const updated = await GroupRepository.updateGroupApproval(groupId, approved);
if (!updated) {
return res.status(404).json({
error: 'Group not found',
message: `Gruppe mit ID ${groupId} wurde nicht gefunden`
});
}
res.json({
success: true,
message: approved ? 'Gruppe freigegeben' : 'Gruppe gesperrt',
groupId: groupId,
approved: approved
});
} catch (error) {
console.error('Error updating group approval:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Aktualisieren der Freigabe'
});
}
});
router.patch('/groups/:groupId', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Update group metadata'
#swagger.description = 'Updates group metadata fields (year, title, description, name)'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.parameters['body'] = {
in: 'body',
required: true,
schema: {
year: 2025,
title: 'Sommercamp',
description: 'Tolle Veranstaltung',
name: 'Familie_Mueller'
}
}
#swagger.responses[200] = {
description: 'Group updated successfully',
schema: {
success: true,
message: 'Gruppe aktualisiert',
updatedFields: ['year', 'title']
}
}
#swagger.responses[404] = {
description: 'Group not found'
}
*/
try {
const { groupId } = req.params;
// Erlaubte Felder zum Aktualisieren
const allowed = ['year', 'title', 'description', 'name'];
const updates = {};
for (const field of allowed) {
if (req.body[field] !== undefined) {
updates[field] = req.body[field];
}
}
if (Object.keys(updates).length === 0) {
return res.status(400).json({
error: 'Invalid request',
message: 'Keine gültigen Felder zum Aktualisieren angegeben'
});
}
const updated = await GroupRepository.updateGroup(groupId, updates);
if (!updated) {
return res.status(404).json({
error: 'Group not found',
message: `Gruppe mit ID ${groupId} wurde nicht gefunden`
});
}
res.json({
success: true,
message: 'Gruppe erfolgreich aktualisiert',
groupId: groupId,
updates: updates
});
} catch (error) {
console.error('Error updating group:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Aktualisieren der Gruppe',
details: error.message
});
}
});
router.delete('/groups/:groupId/images/:imageId', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Delete a single image'
#swagger.description = 'Deletes a specific image from a group'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.parameters['imageId'] = {
in: 'path',
required: true,
type: 'integer',
description: 'Image ID',
example: 42
}
#swagger.responses[200] = {
description: 'Image deleted successfully',
schema: {
success: true,
message: 'Bild erfolgreich gelöscht',
groupId: 'abc123def456',
imageId: 42
}
}
#swagger.responses[404] = {
description: 'Image not found'
}
*/
try {
const { groupId, imageId } = req.params;
const deleted = await GroupRepository.deleteImage(groupId, parseInt(imageId));
if (!deleted) {
return res.status(404).json({
error: 'Image not found',
message: `Bild mit ID ${imageId} in Gruppe ${groupId} wurde nicht gefunden`
});
}
res.json({
success: true,
message: 'Bild erfolgreich gelöscht',
groupId: groupId,
imageId: parseInt(imageId)
});
} catch (error) {
console.error('Error deleting image:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Löschen des Bildes'
});
}
});
router.patch('/groups/:groupId/images/batch-description', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Batch update image descriptions'
#swagger.description = 'Updates descriptions for multiple images in a group at once'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.parameters['body'] = {
in: 'body',
required: true,
schema: {
descriptions: [
{ imageId: 1, description: 'Sonnenuntergang am Strand' },
{ imageId: 2, description: 'Gruppenfoto beim Lagerfeuer' }
]
}
}
#swagger.responses[200] = {
description: 'Descriptions updated',
schema: {
success: true,
updatedCount: 2,
message: '2 Bildbeschreibungen aktualisiert'
}
}
#swagger.responses[400] = {
description: 'Invalid request format'
}
*/
try {
const { groupId } = req.params;
const { descriptions } = req.body;
// Validierung
if (!Array.isArray(descriptions) || descriptions.length === 0) {
return res.status(400).json({
error: 'Invalid request',
message: 'descriptions muss ein nicht-leeres Array sein'
});
}
// Validiere jede Beschreibung
for (const desc of descriptions) {
if (!desc.imageId || typeof desc.imageId !== 'number') {
return res.status(400).json({
error: 'Invalid request',
message: 'Jede Beschreibung muss eine gültige imageId enthalten'
});
}
if (desc.description && desc.description.length > 200) {
return res.status(400).json({
error: 'Invalid request',
message: `Bildbeschreibung für Bild ${desc.imageId} darf maximal 200 Zeichen lang sein`
});
}
}
const result = await GroupRepository.updateBatchImageDescriptions(groupId, descriptions);
res.json({
success: true,
message: `${result.updatedImages} Bildbeschreibungen erfolgreich aktualisiert`,
groupId: groupId,
updatedImages: result.updatedImages
});
} catch (error) {
console.error('Error batch updating image descriptions:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Aktualisieren der Bildbeschreibungen',
details: error.message
});
}
});
router.patch('/groups/:groupId/images/:imageId', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Update single image description'
#swagger.description = 'Updates description for a specific image (max 200 characters)'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.parameters['imageId'] = {
in: 'path',
required: true,
type: 'integer',
description: 'Image ID',
example: 42
}
#swagger.parameters['body'] = {
in: 'body',
required: true,
schema: {
image_description: 'Sonnenuntergang am Strand'
}
}
#swagger.responses[200] = {
description: 'Description updated',
schema: {
success: true,
message: 'Bildbeschreibung erfolgreich aktualisiert',
groupId: 'abc123def456',
imageId: 42,
imageDescription: 'Sonnenuntergang am Strand'
}
}
#swagger.responses[400] = {
description: 'Description too long (max 200 chars)'
}
#swagger.responses[404] = {
description: 'Image not found'
}
*/
try {
const { groupId, imageId } = req.params;
const { image_description } = req.body;
// Validierung: Max 200 Zeichen
if (image_description && image_description.length > 200) {
return res.status(400).json({
error: 'Invalid request',
message: 'Bildbeschreibung darf maximal 200 Zeichen lang sein'
});
}
const updated = await GroupRepository.updateImageDescription(
parseInt(imageId),
groupId,
image_description
);
if (!updated) {
return res.status(404).json({
error: 'Image not found',
message: `Bild mit ID ${imageId} in Gruppe ${groupId} wurde nicht gefunden`
});
}
res.json({
success: true,
message: 'Bildbeschreibung erfolgreich aktualisiert',
groupId: groupId,
imageId: parseInt(imageId),
imageDescription: image_description
});
} catch (error) {
console.error('Error updating image description:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Aktualisieren der Bildbeschreibung',
details: error.message
});
}
});
router.delete('/groups/:groupId', async (req, res) => {
/*
#swagger.tags = ['Admin - Groups Moderation']
#swagger.summary = 'Delete a group'
#swagger.description = 'Deletes a complete group including all images and metadata'
#swagger.parameters['groupId'] = {
in: 'path',
required: true,
type: 'string',
description: 'Group ID',
example: 'abc123def456'
}
#swagger.responses[200] = {
description: 'Group deleted successfully',
schema: {
success: true,
message: 'Gruppe erfolgreich gelöscht',
groupId: 'abc123def456'
}
}
#swagger.responses[404] = {
description: 'Group not found'
}
*/
try {
const { groupId } = req.params;
// Get group data before deletion for logging
const groupData = await GroupRepository.getGroupById(groupId);
if (!groupData) {
return res.status(404).json({
error: 'Group not found',
message: `Gruppe mit ID ${groupId} wurde nicht gefunden`
});
}
const imageCount = groupData.images ? groupData.images.length : 0;
const totalFileSize = groupData.images ? groupData.images.reduce((sum, img) => sum + (img.fileSize || 0), 0) : 0;
// Create deletion_log entry BEFORE deleting
await DeletionLogRepository.createDeletionEntry({
groupId: groupId,
year: groupData.year,
imageCount: imageCount,
uploadDate: groupData.uploadDate,
deletionReason: 'admin_moderation_deletion',
totalFileSize: totalFileSize
});
// Delete the group
await GroupRepository.deleteGroup(groupId);
res.json({
success: true,
message: 'Gruppe erfolgreich gelöscht',
groupId: groupId
});
} catch (error) {
console.error('Error deleting group:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Fehler beim Löschen der Gruppe'
});
}
});
module.exports = router;