feat: enhance user management by adding content scrubbing on hard delete and updating dashboard display
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 29s

This commit is contained in:
2026-03-20 14:46:05 -04:00
parent 64764b652d
commit de21d8b9ee
2 changed files with 22 additions and 3 deletions

View File

@@ -1,6 +1,10 @@
const db = require('../config/database');
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const uploadsDir = process.env.UPLOADS_DIR || './data/uploads';
// ─── Helpers ──────────────────────────────────────────────
function generateCode(length = 5) {
@@ -150,6 +154,8 @@ const Users = {
// get username from userId before scrambling
const user = this.findById(userId);
const scrambled = `_deleted_${user.username}_${Date.now()}`;
// Delete uploaded images from disk and clear from DB
this._scrubUserContent(userId);
db.prepare('UPDATE users SET username = ?, display_name = ?, password_hash = ?, is_admin = 0, is_organizer = 0 WHERE id = ?')
.run(scrambled, '[deleted]', '', userId);
db.prepare('UPDATE password_reset_tokens SET used = 1 WHERE user_id = ?').run(userId);
@@ -157,12 +163,25 @@ const Users = {
},
hardDeleteUser(userId) {
this._scrubUserContent(userId);
db.prepare('DELETE FROM scans WHERE user_id = ?').run(userId);
db.prepare('UPDATE packages SET first_scanned_by = NULL, first_scan_image = NULL WHERE first_scanned_by = ?').run(userId);
db.prepare('UPDATE packages SET last_scanned_by = NULL, last_scan_hint = NULL WHERE last_scanned_by = ?').run(userId);
db.prepare('UPDATE packages SET first_scanned_by = NULL WHERE first_scanned_by = ?').run(userId);
db.prepare('UPDATE packages SET last_scanned_by = NULL WHERE last_scanned_by = ?').run(userId);
db.prepare('DELETE FROM password_reset_tokens WHERE user_id = ?').run(userId);
db.prepare("DELETE FROM sessions WHERE sess LIKE ?").run('%"userId":' + userId + '%');
db.prepare('DELETE FROM users WHERE id = ?').run(userId);
},
_scrubUserContent(userId) {
// Delete images uploaded by this user
const images = db.prepare('SELECT first_scan_image FROM packages WHERE first_scanned_by = ? AND first_scan_image IS NOT NULL').all(userId);
for (const row of images) {
const filePath = path.resolve(uploadsDir, path.basename(row.first_scan_image));
try { fs.unlinkSync(filePath); } catch (_) { /* file may already be gone */ }
}
db.prepare('UPDATE packages SET first_scan_image = NULL WHERE first_scanned_by = ?').run(userId);
// Clear hints left by this user
db.prepare('UPDATE packages SET last_scan_hint = NULL WHERE last_scanned_by = ?').run(userId);
}
};

View File

@@ -74,7 +74,7 @@
<tbody>
<% if (typeof users !== 'undefined' && users) { users.filter(u => !u.is_admin).forEach(u => { const deleted = u.password_hash === ''; %>
<tr<%= deleted ? ' style="opacity: 0.5;"' : '' %>>
<td><% if (!deleted) { %><a href="/player/<%= u.username %>"><%= u.display_name %></a><% } else { %><span style="color: var(--muted);"><%= u.display_name %></span><% } %></td>
<td><% if (!deleted) { %><a href="/player/<%= u.username %>"><%= u.display_name %></a><% } else { %><span style="color: var(--muted);"><%= u.display_name %></span><% } %> <span style="color: var(--muted); font-size: 0.8rem;">(<%= u.username %>)</span></td>
<td><%= deleted ? 'Deleted' : u.is_organizer ? 'Organizer' : 'Player' %></td>
<td>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">