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
All checks were successful
Build Images and Deploy / Update-PROD-Stack (push) Successful in 29s
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;">
|
||||
|
||||
Reference in New Issue
Block a user