fdsafs
This commit is contained in:
+200
-75
@@ -77,7 +77,15 @@ export default (router, tpl) => {
|
||||
} else {
|
||||
const reason = user[0].ban_reason || 'none';
|
||||
const expires = user[0].ban_expires ? new Date(user[0].ban_expires).toISOString().replace('T', ' ').substring(0, 16) : 'never';
|
||||
return fail(`You are banned! reason: ${reason} expire: ${expires}`);
|
||||
if (req.headers['x-requested-with'] === 'XMLHttpRequest' || (req.headers.accept && req.headers.accept.includes('application/json'))) {
|
||||
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({
|
||||
success: false,
|
||||
banned: true,
|
||||
msg: `You are banned! reason: ${reason} expire: ${expires}`,
|
||||
redirect: '/banned'
|
||||
}));
|
||||
}
|
||||
return res.writeHead(302, { Location: '/banned' }).end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,6 +460,14 @@ export default (router, tpl) => {
|
||||
|
||||
await audit.log(req.session.id, 'ban_ip', 'ip', null, { ip, reason, duration });
|
||||
|
||||
// Broadcast ban event via SSE
|
||||
await db.notify('bans', JSON.stringify({
|
||||
ip,
|
||||
ipHash,
|
||||
reason: (reason || 'Banned by moderator').substring(0, 300),
|
||||
expires
|
||||
})).catch(() => {});
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message });
|
||||
@@ -682,6 +698,16 @@ export default (router, tpl) => {
|
||||
expires,
|
||||
banIps: true
|
||||
});
|
||||
} else {
|
||||
// Broadcast ban to registered user's active SSE sessions
|
||||
const userIps = await db`SELECT distinct ip FROM user_ips WHERE user_id = ${+user_id}`;
|
||||
const ips = userIps.map(r => r.ip).filter(Boolean);
|
||||
await db.notify('bans', JSON.stringify({
|
||||
userId: +user_id,
|
||||
reason: (reason || 'Violation of community rules').substring(0, 300),
|
||||
expires,
|
||||
ips
|
||||
})).catch(() => {});
|
||||
}
|
||||
|
||||
// Log it in audit
|
||||
@@ -1109,87 +1135,179 @@ export default (router, tpl) => {
|
||||
const page = Math.max(1, parseInt(req.url.qs?.page) || 1);
|
||||
const limit = 50;
|
||||
const offset = (page - 1) * limit;
|
||||
const rawStatus = (req.url.qs?.status || req.url.qs?.filter || '').toLowerCase().trim();
|
||||
const rawRole = (req.url.qs?.role || '').toLowerCase().trim();
|
||||
|
||||
const users = await db`
|
||||
WITH filtered_users AS (
|
||||
SELECT
|
||||
u.id, u.login, u.user, u.email, u.created_at, u.banned, u.is_moderator, u.admin, u.activated,
|
||||
uo.avatar_file, uo.display_name, uo.force_comment_display_mode, uo.comment_display_mode,
|
||||
(SELECT token FROM invite_tokens WHERE used_by = u.id ORDER BY created_at DESC LIMIT 1) as reg_method
|
||||
FROM "user" u
|
||||
LEFT JOIN user_options uo ON uo.user_id = u.id
|
||||
${q ? (exactMatch
|
||||
? db`WHERE lower(u.login) = lower(${q}) OR lower(u.user) = lower(${q}) OR lower(u.email) = lower(${q})`
|
||||
: db`WHERE u.login ILIKE ${'%' + lib.escapeLike(q) + '%'} OR u.user ILIKE ${'%' + lib.escapeLike(q) + '%'} OR u.email ILIKE ${'%' + lib.escapeLike(q) + '%'}`
|
||||
) : db``}
|
||||
),
|
||||
ghost_users AS (
|
||||
SELECT
|
||||
NULL::int as id, i.username as login, i.username as "user", 'Legacy Account' as email,
|
||||
to_timestamp(MIN(i.stamp)) as created_at, false as banned, false as is_moderator, false as admin, true as activated,
|
||||
NULL::text as avatar_file, NULL::varchar as display_name, 0 as force_comment_display_mode, 0 as comment_display_mode, 'Legacy' as reg_method
|
||||
const onlyLegacy = req.url.qs?.legacy === '1' || req.url.qs?.legacy === 'true' ||
|
||||
req.url.qs?.legacy_only === '1' || req.url.qs?.legacy_only === 'true' ||
|
||||
req.url.qs?.only_legacy === '1' || req.url.qs?.only_legacy === 'true' ||
|
||||
rawStatus === 'legacy';
|
||||
|
||||
const status = onlyLegacy ? '' : rawStatus;
|
||||
const role = onlyLegacy ? '' : rawRole;
|
||||
|
||||
let users;
|
||||
let total;
|
||||
|
||||
if (onlyLegacy) {
|
||||
users = await db`
|
||||
WITH ghost_users AS (
|
||||
SELECT
|
||||
NULL::int as id, i.username as login, i.username as "user", 'Legacy Account' as email,
|
||||
to_timestamp(MIN(i.stamp)) as created_at, false as banned, false as is_moderator, false as admin, true as activated,
|
||||
NULL::text as avatar_file, NULL::varchar as display_name, 0 as force_comment_display_mode, 0 as comment_display_mode, 'Legacy' as reg_method
|
||||
FROM items i
|
||||
WHERE i.username IS NOT NULL AND i.username != ''
|
||||
AND NOT EXISTS (SELECT 1 FROM "user" u WHERE u.login = i.username OR u.user = i.username)
|
||||
${q ? (exactMatch
|
||||
? db`AND lower(i.username) = lower(${q})`
|
||||
: db`AND (i.username ILIKE ${'%' + lib.escapeLike(q) + '%'})`
|
||||
) : db``}
|
||||
GROUP BY i.username
|
||||
),
|
||||
paginated_users AS (
|
||||
SELECT * FROM ghost_users
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
)
|
||||
SELECT
|
||||
pu.*,
|
||||
EXTRACT(DAY FROM (now() - pu.created_at)) as age_days,
|
||||
COALESCE(ic.upload_count, 0) as upload_count,
|
||||
0::bigint as comment_count,
|
||||
0::bigint as failed_attempts
|
||||
FROM paginated_users pu
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as upload_count
|
||||
FROM items
|
||||
WHERE (username = pu.login OR username = pu.user) AND is_deleted = false
|
||||
) ic ON true
|
||||
`;
|
||||
|
||||
const totalCountGhost = await db`
|
||||
SELECT COUNT(DISTINCT i.username) as c
|
||||
FROM items i
|
||||
WHERE NOT EXISTS (SELECT 1 FROM "user" u WHERE u.login = i.username OR u.user = i.username)
|
||||
WHERE i.username IS NOT NULL AND i.username != ''
|
||||
AND NOT EXISTS (SELECT 1 FROM "user" u WHERE u.login = i.username OR u.user = i.username)
|
||||
${q ? (exactMatch
|
||||
? db`AND lower(i.username) = lower(${q})`
|
||||
: db`AND (i.username ILIKE ${'%' + lib.escapeLike(q) + '%'})`
|
||||
) : db``}
|
||||
GROUP BY i.username
|
||||
),
|
||||
all_users AS (
|
||||
SELECT * FROM filtered_users
|
||||
UNION ALL
|
||||
SELECT * FROM ghost_users
|
||||
),
|
||||
paginated_users AS (
|
||||
SELECT * FROM all_users
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
)
|
||||
SELECT
|
||||
pu.*,
|
||||
EXTRACT(DAY FROM (now() - pu.created_at)) as age_days,
|
||||
COALESCE(ic.upload_count, 0) as upload_count,
|
||||
COALESCE(cc.comment_count, 0) as comment_count,
|
||||
COALESCE(la.failed_attempts, 0) as failed_attempts
|
||||
FROM paginated_users pu
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as upload_count
|
||||
FROM items
|
||||
WHERE (username = pu.login OR username = pu.user) AND is_deleted = false
|
||||
) ic ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as comment_count
|
||||
FROM comments
|
||||
WHERE user_id = pu.id AND is_deleted = false
|
||||
) cc ON pu.id IS NOT NULL
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as failed_attempts
|
||||
FROM login_attempts
|
||||
WHERE username = pu.login
|
||||
AND success = false
|
||||
AND type = 'login'
|
||||
AND attempted_at > now() - interval '10 hours'
|
||||
) la ON true
|
||||
`;
|
||||
`;
|
||||
total = parseInt(totalCountGhost[0].c);
|
||||
} else {
|
||||
let qCond = null;
|
||||
if (q) {
|
||||
if (exactMatch) {
|
||||
qCond = db`(lower(u.login) = lower(${q}) OR lower(u.user) = lower(${q}) OR lower(u.email) = lower(${q}))`;
|
||||
} else {
|
||||
const pattern = '%' + lib.escapeLike(q) + '%';
|
||||
qCond = db`(u.login ILIKE ${pattern} OR u.user ILIKE ${pattern} OR u.email ILIKE ${pattern})`;
|
||||
}
|
||||
}
|
||||
|
||||
const totalCountActual = await db`
|
||||
SELECT COUNT(*) as c FROM "user" u
|
||||
${q ? (exactMatch
|
||||
? db`WHERE lower(u.login) = lower(${q}) OR lower(u.user) = lower(${q}) OR lower(u.email) = lower(${q})`
|
||||
: db`WHERE u.login ILIKE ${'%' + lib.escapeLike(q) + '%'} OR u.user ILIKE ${'%' + lib.escapeLike(q) + '%'} OR u.email ILIKE ${'%' + lib.escapeLike(q) + '%'}`
|
||||
) : db``}
|
||||
`;
|
||||
const totalCountGhost = await db`
|
||||
SELECT COUNT(DISTINCT i.username) as c
|
||||
FROM items i
|
||||
WHERE NOT EXISTS (SELECT 1 FROM "user" u WHERE u.login = i.username OR u.user = i.username)
|
||||
${q ? (exactMatch
|
||||
? db`AND lower(i.username) = lower(${q})`
|
||||
: db`AND (i.username ILIKE ${'%' + lib.escapeLike(q) + '%'})`
|
||||
) : db``}
|
||||
`;
|
||||
const total = parseInt(totalCountActual[0].c) + parseInt(totalCountGhost[0].c);
|
||||
let statusCond = null;
|
||||
if (status === 'banned') {
|
||||
statusCond = db`u.banned = true`;
|
||||
} else if (status === 'active') {
|
||||
statusCond = db`(u.activated = true AND u.banned = false)`;
|
||||
} else if (status === 'pending') {
|
||||
statusCond = db`(u.activated = false AND u.banned = false)`;
|
||||
}
|
||||
|
||||
let roleCond = null;
|
||||
if (role === 'staff' || status === 'staff') {
|
||||
roleCond = db`(u.admin = true OR u.is_moderator = true)`;
|
||||
} else if (role === 'admin') {
|
||||
roleCond = db`u.admin = true`;
|
||||
} else if (role === 'mod') {
|
||||
roleCond = db`(u.is_moderator = true AND u.admin = false)`;
|
||||
} else if (role === 'user') {
|
||||
roleCond = db`(u.admin = false AND u.is_moderator = false)`;
|
||||
}
|
||||
|
||||
users = await db`
|
||||
WITH filtered_users AS (
|
||||
SELECT
|
||||
u.id, u.login, u.user, u.email, u.created_at, u.banned, u.is_moderator, u.admin, u.activated,
|
||||
uo.avatar_file, uo.display_name, uo.force_comment_display_mode, uo.comment_display_mode,
|
||||
(SELECT token FROM invite_tokens WHERE used_by = u.id ORDER BY created_at DESC LIMIT 1) as reg_method
|
||||
FROM "user" u
|
||||
LEFT JOIN user_options uo ON uo.user_id = u.id
|
||||
WHERE true
|
||||
${qCond ? db`AND ${qCond}` : db``}
|
||||
${statusCond ? db`AND ${statusCond}` : db``}
|
||||
${roleCond ? db`AND ${roleCond}` : db``}
|
||||
),
|
||||
paginated_users AS (
|
||||
SELECT * FROM filtered_users
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
)
|
||||
SELECT
|
||||
pu.*,
|
||||
EXTRACT(DAY FROM (now() - pu.created_at)) as age_days,
|
||||
COALESCE(ic.upload_count, 0) as upload_count,
|
||||
COALESCE(cc.comment_count, 0) as comment_count,
|
||||
COALESCE(la.failed_attempts, 0) as failed_attempts
|
||||
FROM paginated_users pu
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as upload_count
|
||||
FROM items
|
||||
WHERE (username = pu.login OR username = pu.user) AND is_deleted = false
|
||||
) ic ON true
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as comment_count
|
||||
FROM comments
|
||||
WHERE user_id = pu.id AND is_deleted = false
|
||||
) cc ON pu.id IS NOT NULL
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*) as failed_attempts
|
||||
FROM login_attempts
|
||||
WHERE username = pu.login
|
||||
AND success = false
|
||||
AND type = 'login'
|
||||
AND attempted_at > now() - interval '10 hours'
|
||||
) la ON true
|
||||
`;
|
||||
|
||||
const totalCountActual = await db`
|
||||
SELECT COUNT(*) as c FROM "user" u
|
||||
WHERE true
|
||||
${qCond ? db`AND ${qCond}` : db``}
|
||||
${statusCond ? db`AND ${statusCond}` : db``}
|
||||
${roleCond ? db`AND ${roleCond}` : db``}
|
||||
`;
|
||||
total = parseInt(totalCountActual[0].c);
|
||||
}
|
||||
|
||||
let totalLabel = 'registered members';
|
||||
let emptyMsg = 'No users matched your search.';
|
||||
if (onlyLegacy) {
|
||||
totalLabel = 'legacy accounts';
|
||||
emptyMsg = 'No legacy users matched your search.';
|
||||
} else if (status === 'banned') {
|
||||
totalLabel = 'banned members';
|
||||
emptyMsg = 'No banned users found.';
|
||||
} else if (status === 'pending') {
|
||||
totalLabel = 'pending members';
|
||||
emptyMsg = 'No pending users found.';
|
||||
} else if (status === 'active') {
|
||||
totalLabel = 'active members';
|
||||
emptyMsg = 'No active users found.';
|
||||
} else if (role === 'staff' || status === 'staff') {
|
||||
totalLabel = 'staff members';
|
||||
emptyMsg = 'No staff members found.';
|
||||
} else if (role === 'admin') {
|
||||
totalLabel = 'admin members';
|
||||
emptyMsg = 'No admin users found.';
|
||||
} else if (role === 'mod') {
|
||||
totalLabel = 'moderator members';
|
||||
emptyMsg = 'No moderator users found.';
|
||||
} else if (role === 'user') {
|
||||
totalLabel = 'regular users';
|
||||
emptyMsg = 'No regular users found.';
|
||||
}
|
||||
|
||||
const data = {
|
||||
session: req.session,
|
||||
@@ -1198,6 +1316,11 @@ export default (router, tpl) => {
|
||||
page,
|
||||
total,
|
||||
hasMore: users.length === limit,
|
||||
onlyLegacy,
|
||||
status,
|
||||
role,
|
||||
totalLabel,
|
||||
emptyMsg,
|
||||
totals: await lib.countf0cks(),
|
||||
log_user_ips: getLogUserIps(),
|
||||
tmp: null
|
||||
@@ -1205,6 +1328,8 @@ export default (router, tpl) => {
|
||||
|
||||
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
|
||||
res.setHeader('X-Total-Count', total.toString());
|
||||
res.setHeader('X-Total-Label', totalLabel);
|
||||
res.setHeader('X-Empty-Msg', emptyMsg);
|
||||
res.setHeader('X-Has-More', (users.length === limit).toString());
|
||||
return res.reply({
|
||||
body: tpl.render("admin/users_list", data, req)
|
||||
|
||||
Reference in New Issue
Block a user