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
This commit is contained in:
Matthias Lotz 2025-11-08 13:18:44 +01:00
parent 3a2efd97c3
commit 861d4813d1
3 changed files with 406 additions and 0 deletions

View File

@ -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;

View File

@ -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();

99
test-cleanup.sh Executable file
View File

@ -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