|
|
|
|
@@ -86,7 +86,7 @@ router.post('/', authMiddleware, adminMiddleware, async (req, res) => {
|
|
|
|
|
if (!name || !email) return res.status(400).json({ error: 'Name and email required' });
|
|
|
|
|
if (!isValidEmail(email)) return res.status(400).json({ error: 'Invalid email address' });
|
|
|
|
|
try {
|
|
|
|
|
const exists = await queryOne(req.schema, 'SELECT id FROM users WHERE email = $1', [email]);
|
|
|
|
|
const exists = await queryOne(req.schema, "SELECT id FROM users WHERE email = $1 AND status != 'deleted'", [email]);
|
|
|
|
|
if (exists) return res.status(400).json({ error: 'Email already in use' });
|
|
|
|
|
const resolvedName = await resolveUniqueName(req.schema, name.trim());
|
|
|
|
|
const pw = (password || '').trim() || process.env.USER_PASS || 'user@1234';
|
|
|
|
|
@@ -120,7 +120,7 @@ router.post('/bulk', authMiddleware, adminMiddleware, async (req, res) => {
|
|
|
|
|
if (!isValidEmail(email)) { results.skipped.push({ email, reason: 'Invalid email address' }); continue; }
|
|
|
|
|
if (seenEmails.has(email)) { results.skipped.push({ email, reason: 'Duplicate email in CSV' }); continue; }
|
|
|
|
|
seenEmails.add(email);
|
|
|
|
|
const exists = await queryOne(req.schema, 'SELECT id FROM users WHERE email=$1', [email]);
|
|
|
|
|
const exists = await queryOne(req.schema, "SELECT id FROM users WHERE email=$1 AND status != 'deleted'", [email]);
|
|
|
|
|
if (exists) { results.skipped.push({ email, reason: 'Email already exists' }); continue; }
|
|
|
|
|
try {
|
|
|
|
|
const resolvedName = await resolveUniqueName(req.schema, name);
|
|
|
|
|
@@ -208,22 +208,48 @@ router.delete('/:id', authMiddleware, adminMiddleware, async (req, res)
|
|
|
|
|
if (!t) return res.status(404).json({ error: 'User not found' });
|
|
|
|
|
if (t.is_default_admin) return res.status(403).json({ error: 'Cannot delete default admin' });
|
|
|
|
|
|
|
|
|
|
// Mark deleted
|
|
|
|
|
await exec(req.schema, "UPDATE users SET status='deleted', updated_at=NOW() WHERE id=$1", [t.id]);
|
|
|
|
|
// ── 1. Anonymise the user record ─────────────────────────────────────────
|
|
|
|
|
// Scrub the email immediately so the address is free for re-use.
|
|
|
|
|
// Replace name/display_name/avatar/about_me so no PII is retained.
|
|
|
|
|
await exec(req.schema, `
|
|
|
|
|
UPDATE users SET
|
|
|
|
|
status = 'deleted',
|
|
|
|
|
email = $1,
|
|
|
|
|
name = 'Deleted User',
|
|
|
|
|
display_name = NULL,
|
|
|
|
|
avatar = NULL,
|
|
|
|
|
about_me = NULL,
|
|
|
|
|
password = '',
|
|
|
|
|
updated_at = NOW()
|
|
|
|
|
WHERE id = $2
|
|
|
|
|
`, [`deleted_${t.id}@deleted`, t.id]);
|
|
|
|
|
|
|
|
|
|
// Remove from all chat group memberships
|
|
|
|
|
await exec(req.schema, 'DELETE FROM group_members WHERE user_id=$1', [t.id]);
|
|
|
|
|
// ── 2. Anonymise their messages ───────────────────────────────────────────
|
|
|
|
|
// Mark all their messages as deleted so they render as "This message was
|
|
|
|
|
// deleted" in conversation history — no content holes for other members.
|
|
|
|
|
await exec(req.schema,
|
|
|
|
|
'UPDATE messages SET is_deleted=TRUE, content=NULL, image_url=NULL WHERE user_id=$1 AND is_deleted=FALSE',
|
|
|
|
|
[t.id]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Remove from all user groups (managed groups)
|
|
|
|
|
// ── 3. Freeze any DMs that only had this user + one other person ──────────
|
|
|
|
|
// The surviving peer still has their DM visible but it becomes read-only
|
|
|
|
|
// (frozen) since the other party is gone. Group chats (3+ people) are
|
|
|
|
|
// left intact — the other members' history and ongoing chat is unaffected.
|
|
|
|
|
await exec(req.schema, `
|
|
|
|
|
UPDATE groups SET is_readonly=TRUE, updated_at=NOW()
|
|
|
|
|
WHERE is_direct=TRUE
|
|
|
|
|
AND (direct_peer1_id=$1 OR direct_peer2_id=$1)
|
|
|
|
|
`, [t.id]);
|
|
|
|
|
|
|
|
|
|
// ── 4. Remove memberships ────────────────────────────────────────────────
|
|
|
|
|
await exec(req.schema, 'DELETE FROM group_members WHERE user_id=$1', [t.id]);
|
|
|
|
|
await exec(req.schema, 'DELETE FROM user_group_members WHERE user_id=$1', [t.id]);
|
|
|
|
|
|
|
|
|
|
// Clear all active sessions so they cannot log in
|
|
|
|
|
await exec(req.schema, 'DELETE FROM active_sessions WHERE user_id=$1', [t.id]);
|
|
|
|
|
|
|
|
|
|
// Remove push subscriptions
|
|
|
|
|
// ── 5. Purge sessions, push subscriptions, notifications ─────────────────
|
|
|
|
|
await exec(req.schema, 'DELETE FROM active_sessions WHERE user_id=$1', [t.id]);
|
|
|
|
|
await exec(req.schema, 'DELETE FROM push_subscriptions WHERE user_id=$1', [t.id]);
|
|
|
|
|
|
|
|
|
|
// Remove event availability responses
|
|
|
|
|
await exec(req.schema, 'DELETE FROM notifications WHERE user_id=$1', [t.id]);
|
|
|
|
|
await exec(req.schema, 'DELETE FROM event_availability WHERE user_id=$1', [t.id]);
|
|
|
|
|
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|