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:
parent
3a2efd97c3
commit
861d4813d1
|
|
@ -1,6 +1,9 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const DeletionLogRepository = require('../repositories/DeletionLogRepository');
|
const DeletionLogRepository = require('../repositories/DeletionLogRepository');
|
||||||
|
const GroupCleanupService = require('../services/GroupCleanupService');
|
||||||
|
|
||||||
|
const cleanupService = new GroupCleanupService();
|
||||||
|
|
||||||
// Hole Deletion Log (mit Limit)
|
// Hole Deletion Log (mit Limit)
|
||||||
router.get('/deletion-log', async (req, res) => {
|
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;
|
module.exports = router;
|
||||||
|
|
|
||||||
255
backend/src/scripts/test-cleanup.js
Executable file
255
backend/src/scripts/test-cleanup.js
Executable 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
99
test-cleanup.sh
Executable 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
|
||||||
Loading…
Reference in New Issue
Block a user