/bewerbung: Tabs Unterlagen/E-Mail + Admin-Nutzerdetailseite

- Bewerbungsdetail: Toggle zwischen Bewerbungsunterlagen und E-Mail-Korrespondenz
  (hash-basiert), weniger Scroll auf der Seite.
- Admin: Klick auf einen Nutzer öffnet neue Verwaltungsseite
  /admin/users/:id (Passwort, Anmelden als, Jobsuche, Admin-Status, Löschen).
- Admin-Tabelle: Löschen und Jobsuche aus den Aktionen entfernt
  (jetzt auf der Nutzerdetailseite).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-20 08:37:54 +02:00
co-authored by Claude
parent ada69eb482
commit fa07dbf90a
4 changed files with 296 additions and 20 deletions
+47
View File
@@ -4284,6 +4284,53 @@ initializeDatabase().then(async () => {
}
});
// Einzelnen Benutzer verwalten (Detailansicht von „Bestehende Benutzer").
app.get('/admin/users/:id', requireAdmin, async (req, res) => {
try {
const id = Number(req.params.id);
const user = await dbGet('SELECT id, username, is_admin, created_at FROM users WHERE id = ?', [id]);
if (!user) return res.status(404).send('Benutzer nicht gefunden.');
const anzahl = await dbGet('SELECT COUNT(*) AS c FROM bewerbungen WHERE user_id = ?', [id]);
const bewerbungen = await dbAll(
'SELECT id, firma, stelle, status, datum FROM bewerbungen WHERE user_id = ? ORDER BY date(datum) DESC, created_at DESC LIMIT 10',
[id]
);
const adminCount = await dbGet('SELECT COUNT(*) AS c FROM users WHERE is_admin = 1');
res.render('admin-user', {
user,
currentUserId: req.user.id,
anzahlBewerbungen: anzahl ? anzahl.c : 0,
bewerbungen: bewerbungen || [],
adminCount: adminCount ? adminCount.c : 0,
hideSettings: false,
});
} catch (error) {
console.error('Admin user detail error:', error);
res.status(500).send('Serverfehler');
}
});
// Admin-Status eines Benutzers umschalten. Der letzte Admin darf nicht
// abgesetzt werden (sonst sperrt man sich aus).
app.post('/admin/users/:id/toggle-admin', requireAdmin, async (req, res) => {
try {
const id = Number(req.params.id);
const target = await dbGet('SELECT id, is_admin FROM users WHERE id = ?', [id]);
if (!target) return res.status(404).send('Benutzer nicht gefunden.');
if (target.is_admin) {
const adminCount = await dbGet('SELECT COUNT(*) AS c FROM users WHERE is_admin = 1');
if (adminCount && adminCount.c <= 1) return res.status(400).send('Der letzte Admin darf nicht abgesetzt werden.');
}
const neu = target.is_admin ? 0 : 1;
await dbRun('UPDATE users SET is_admin = ? WHERE id = ?', [neu, id]);
await auditLog(req.user.id, id, `admin_toggle → ${neu ? 'admin' : 'user'}`);
res.redirect('/admin/users/' + id);
} catch (error) {
console.error('Admin toggle-admin error:', error);
res.status(500).send('Serverfehler');
}
});
// --- Impersonation: admin "logs in as" a user --------------------------
// Best practice (Django/Flask-impersonate style): the admin's own session
// keeps its cookie token, but its user_id switches to the target and the