From 861d4813d1a7b8f49bb33519648f4f63256b051d Mon Sep 17 00:00:00 2001 From: "matthias.lotz" Date: Sat, 8 Nov 2025 13:18:44 +0100 Subject: [PATCH] feat(testing): Add cleanup testing tools and API endpoints - Add POST /api/admin/cleanup/trigger for manual cleanup - Add GET /api/admin/cleanup/preview for dry-run testing - Create test-cleanup.sh (bash) for easy testing - Create test-cleanup.js (node) as alternative test tool - Enable backdating groups for testing purposes --- backend/src/routes/admin.js | 52 ++++++ backend/src/scripts/test-cleanup.js | 255 ++++++++++++++++++++++++++++ test-cleanup.sh | 99 +++++++++++ 3 files changed, 406 insertions(+) create mode 100755 backend/src/scripts/test-cleanup.js create mode 100755 test-cleanup.sh diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js index 5941675..f4329c3 100644 --- a/backend/src/routes/admin.js +++ b/backend/src/routes/admin.js @@ -1,6 +1,9 @@ const express = require('express'); const router = express.Router(); const DeletionLogRepository = require('../repositories/DeletionLogRepository'); +const GroupCleanupService = require('../services/GroupCleanupService'); + +const cleanupService = new GroupCleanupService(); // Hole Deletion Log (mit Limit) router.get('/deletion-log', async (req, res) => { @@ -82,4 +85,53 @@ router.get('/deletion-log/stats', async (req, res) => { } }); +// Manueller Cleanup-Trigger (für Testing) +router.post('/cleanup/trigger', async (req, res) => { + 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 + }); + } +}); + +// Zeige welche Gruppen gelöscht würden (Dry-Run) +router.get('/cleanup/preview', async (req, res) => { + 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 + }); + } +}); + + module.exports = router; diff --git a/backend/src/scripts/test-cleanup.js b/backend/src/scripts/test-cleanup.js new file mode 100755 index 0000000..905fa0b --- /dev/null +++ b/backend/src/scripts/test-cleanup.js @@ -0,0 +1,255 @@ +#!/usr/bin/env node + +/** + * Test-Script für automatisches Löschen + * + * Dieses Script hilft beim Testen des Cleanup-Features: + * 1. Zeigt alle nicht-freigegebenen Gruppen + * 2. Erlaubt das Zurückdatieren von Gruppen (für Tests) + * 3. Zeigt Preview der zu löschenden Gruppen + * 4. Triggert manuellen Cleanup + */ + +const readline = require('readline'); +const https = require('http'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +const API_BASE = 'http://localhost:5001'; + +// Helper: HTTP Request +function makeRequest(path, method = 'GET', data = null) { + return new Promise((resolve, reject) => { + const url = new URL(path, API_BASE); + const options = { + hostname: url.hostname, + port: url.port, + path: url.pathname + url.search, + method: method, + headers: { + 'Content-Type': 'application/json' + } + }; + + const req = https.request(options, (res) => { + let body = ''; + res.on('data', chunk => body += chunk); + res.on('end', () => { + try { + resolve(JSON.parse(body)); + } catch (e) { + resolve(body); + } + }); + }); + + req.on('error', reject); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +// Helper: SQL Query über API +async function execSQL(query) { + // Direkt über docker exec + const { exec } = require('child_process'); + return new Promise((resolve, reject) => { + exec( + `docker compose -f docker/dev/docker-compose.yml exec -T backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db "${query}"`, + (error, stdout, stderr) => { + if (error) { + reject(error); + return; + } + resolve(stdout); + } + ); + }); +} + +// Zeige Menü +function showMenu() { + console.log('\n========================================'); + console.log(' CLEANUP TEST MENÜ'); + console.log('========================================'); + console.log('1. Zeige alle nicht-freigegebenen Gruppen'); + console.log('2. Gruppe um X Tage zurückdatieren (für Tests)'); + console.log('3. Preview: Welche Gruppen würden gelöscht?'); + console.log('4. Cleanup JETZT ausführen'); + console.log('5. Lösch-Historie anzeigen'); + console.log('0. Beenden'); + console.log('========================================\n'); +} + +// Option 1: Zeige nicht-freigegebene Gruppen +async function showUnapprovedGroups() { + console.log('\n📋 Lade nicht-freigegebene Gruppen...\n'); + const result = await execSQL( + 'SELECT group_id, year, name, approved, datetime(upload_date) as upload_date, ' + + 'CAST((julianday(\\'now\\') - julianday(upload_date)) AS INTEGER) as days_old ' + + 'FROM groups WHERE approved = 0 ORDER BY upload_date DESC;' + ); + + console.log('Gruppe ID | Jahr | Name | Freigegeben | Upload-Datum | Tage alt'); + console.log('------------- | ---- | --------- | ----------- | -------------------- | --------'); + console.log(result || 'Keine nicht-freigegebenen Gruppen gefunden.'); +} + +// Option 2: Gruppe zurückdatieren +async function backdateGroup() { + await showUnapprovedGroups(); + + rl.question('\nGruppe ID zum Zurückdatieren: ', async (groupId) => { + if (!groupId) { + console.log('❌ Keine Gruppe ID angegeben'); + return mainMenu(); + } + + rl.question('Um wie viele Tage zurückdatieren? (z.B. 8 für 8 Tage alt): ', async (days) => { + const daysNum = parseInt(days); + if (isNaN(daysNum) || daysNum < 1) { + console.log('❌ Ungültige Anzahl Tage'); + return mainMenu(); + } + + try { + await execSQL( + `UPDATE groups SET upload_date = datetime('now', '-${daysNum} days') WHERE group_id = '${groupId}';` + ); + console.log(`✅ Gruppe ${groupId} wurde um ${daysNum} Tage zurückdatiert`); + + // Zeige aktualisierte Info + const result = await execSQL( + `SELECT group_id, datetime(upload_date) as upload_date, ` + + `CAST((julianday('now') - julianday(upload_date)) AS INTEGER) as days_old ` + + `FROM groups WHERE group_id = '${groupId}';` + ); + console.log('\nAktualisierte Daten:'); + console.log(result); + } catch (error) { + console.error('❌ Fehler:', error.message); + } + + mainMenu(); + }); + }); +} + +// Option 3: Preview Cleanup +async function previewCleanup() { + console.log('\n🔍 Lade Cleanup Preview...\n'); + try { + const result = await makeRequest('/api/admin/cleanup/preview'); + + if (result.groupsToDelete === 0) { + console.log('✅ Keine Gruppen würden gelöscht (alle sind < 7 Tage alt oder freigegeben)'); + } else { + console.log(`⚠️ ${result.groupsToDelete} Gruppe(n) würden gelöscht:\n`); + result.groups.forEach(group => { + console.log(` - ${group.group_id} (${group.year}) - ${group.name}`); + console.log(` Upload: ${group.uploadDate}`); + console.log(` Tage seit Upload: ${Math.abs(group.daysUntilDeletion)}`); + console.log(''); + }); + } + } catch (error) { + console.error('❌ Fehler:', error.message); + } + + mainMenu(); +} + +// Option 4: Cleanup ausführen +async function executeCleanup() { + console.log('\n⚠️ ACHTUNG: Dies wird Gruppen permanent löschen!\n'); + + rl.question('Cleanup wirklich ausführen? (ja/nein): ', async (answer) => { + if (answer.toLowerCase() !== 'ja') { + console.log('❌ Abgebrochen'); + return mainMenu(); + } + + console.log('\n🔄 Führe Cleanup aus...\n'); + try { + const result = await makeRequest('/api/admin/cleanup/trigger', 'POST'); + + console.log('✅ Cleanup abgeschlossen!'); + console.log(` Gelöschte Gruppen: ${result.result.deletedGroups}`); + console.log(` Fehler: ${result.result.failedGroups || 0}`); + } catch (error) { + console.error('❌ Fehler:', error.message); + } + + mainMenu(); + }); +} + +// Option 5: Lösch-Historie +async function showDeletionLog() { + console.log('\n📜 Lösch-Historie (letzte 10 Einträge)...\n'); + try { + const result = await makeRequest('/api/admin/deletion-log?limit=10'); + + if (result.deletions.length === 0) { + console.log('Keine Einträge im Lösch-Log'); + } else { + console.log('Gruppe ID | Jahr | Bilder | Upload-Datum | Gelöscht am | Grund'); + console.log('------------- | ---- | ------ | -------------------- | -------------------- | -----'); + result.deletions.forEach(d => { + console.log( + `${d.group_id.padEnd(13)} | ${String(d.year).padEnd(4)} | ${String(d.image_count).padEnd(6)} | ` + + `${d.upload_date.substring(0, 19)} | ${d.deleted_at.substring(0, 19)} | ${d.deletion_reason}` + ); + }); + } + } catch (error) { + console.error('❌ Fehler:', error.message); + } + + mainMenu(); +} + +// Hauptmenü +function mainMenu() { + showMenu(); + rl.question('Wähle eine Option: ', async (choice) => { + switch (choice) { + case '1': + await showUnapprovedGroups(); + mainMenu(); + break; + case '2': + await backdateGroup(); + break; + case '3': + await previewCleanup(); + break; + case '4': + await executeCleanup(); + break; + case '5': + await showDeletionLog(); + break; + case '0': + console.log('\n👋 Auf Wiedersehen!\n'); + rl.close(); + process.exit(0); + break; + default: + console.log('❌ Ungültige Option'); + mainMenu(); + } + }); +} + +// Start +console.log('\n🚀 Cleanup Test Script gestartet\n'); +console.log('Hinweis: Stelle sicher, dass der Dev-Server läuft (./dev.sh)'); +mainMenu(); diff --git a/test-cleanup.sh b/test-cleanup.sh new file mode 100755 index 0000000..2fde9aa --- /dev/null +++ b/test-cleanup.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# Cleanup Test Helper Script +# Hilft beim Testen des automatischen Löschens + +echo "========================================" +echo " CLEANUP TEST HELPER" +echo "========================================" +echo "" + +# Prüfe ob Container läuft +if ! docker compose -f docker/dev/docker-compose.yml ps | grep -q "backend-dev.*Up"; then + echo "❌ Backend-Container läuft nicht. Bitte starte ./dev.sh" + exit 1 +fi + +function show_unapproved_groups() { + echo "📋 Nicht-freigegebene Gruppen:" + echo "" + docker compose -f docker/dev/docker-compose.yml exec -T backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db \ + "SELECT group_id || ' | Jahr: ' || year || ' | Name: ' || name || ' | Upload: ' || datetime(upload_date) || ' | Tage: ' || CAST((julianday('now') - julianday(upload_date)) AS INTEGER) + FROM groups WHERE approved = 0 ORDER BY upload_date DESC;" + echo "" +} + +function backdate_group() { + show_unapproved_groups + + echo "" + read -p "Gruppe ID zum Zurückdatieren: " group_id + read -p "Um wie viele Tage? (z.B. 8): " days + + docker compose -f docker/dev/docker-compose.yml exec -T backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db \ + "UPDATE groups SET upload_date = datetime('now', '-$days days') WHERE group_id = '$group_id';" + + echo "✅ Gruppe $group_id wurde um $days Tage zurückdatiert" + echo "" + + # Zeige aktualisierte Info + docker compose -f docker/dev/docker-compose.yml exec -T backend-dev sqlite3 /usr/src/app/src/data/db/image_uploader.db \ + "SELECT 'Gruppe: ' || group_id || ', Upload: ' || datetime(upload_date) || ', Tage alt: ' || CAST((julianday('now') - julianday(upload_date)) AS INTEGER) + FROM groups WHERE group_id = '$group_id';" + echo "" +} + +function preview_cleanup() { + echo "🔍 Cleanup Preview (über API):" + echo "" + curl -s http://localhost:5001/api/admin/cleanup/preview | jq '.' + echo "" +} + +function trigger_cleanup() { + echo "⚠️ ACHTUNG: Dies wird Gruppen permanent löschen!" + echo "" + read -p "Cleanup wirklich ausführen? (ja/nein): " confirm + + if [ "$confirm" != "ja" ]; then + echo "❌ Abgebrochen" + return + fi + + echo "" + echo "🔄 Führe Cleanup aus..." + echo "" + curl -s -X POST http://localhost:5001/api/admin/cleanup/trigger | jq '.' + echo "" +} + +function show_deletion_log() { + echo "📜 Lösch-Historie (letzte 10):" + echo "" + curl -s http://localhost:5001/api/admin/deletion-log?limit=10 | jq '.deletions[] | "Gruppe: \(.group_id), Jahr: \(.year), Bilder: \(.image_count), Gelöscht: \(.deleted_at)"' + echo "" +} + +# Menü +while true; do + echo "Optionen:" + echo " 1) Zeige nicht-freigegebene Gruppen" + echo " 2) Gruppe zurückdatieren (für Tests)" + echo " 3) Preview: Was würde gelöscht?" + echo " 4) Cleanup JETZT ausführen" + echo " 5) Lösch-Historie anzeigen" + echo " 0) Beenden" + echo "" + read -p "Wähle Option: " option + echo "" + + case $option in + 1) show_unapproved_groups ;; + 2) backdate_group ;; + 3) preview_cleanup ;; + 4) trigger_cleanup ;; + 5) show_deletion_log ;; + 0) echo "👋 Auf Wiedersehen!"; exit 0 ;; + *) echo "❌ Ungültige Option" ;; + esac +done