Files
f0ckm/src/inc/routes/reports.mjs
2026-04-25 19:51:52 +02:00

168 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import db from "../sql.mjs";
import lib from "../lib.mjs";
import audit from "../audit.mjs";
export default (router, tpl) => {
// User: Submit a new report
router.post(/^\/api\/v2\/report\/?$/, lib.loggedin, async (req, res) => {
try {
const { item_id, comment_id, reported_user_id, reason } = req.post;
if (!reason || reason.trim().length === 0) {
return res.json({ success: false, msg: "Reason is required." }, 400);
}
// At least one target must be specified
if (!item_id && !comment_id && !reported_user_id) {
return res.json({ success: false, msg: "Must specify an item, comment, or user to report." }, 400);
}
const reportRes = await db`
INSERT INTO reports (reporter_id, item_id, comment_id, user_id, reason)
VALUES (
${req.session.id},
${item_id ? +item_id : null},
${comment_id ? +comment_id : null},
${reported_user_id ? +reported_user_id : null},
${reason.trim()}
)
RETURNING id
`;
const report_id = reportRes[0].id;
// Notify all mods and admins
try {
const mods = await db`SELECT id FROM "user" WHERE admin = true OR is_moderator = true`;
if (mods.length > 0) {
let resolved_item_id = item_id ? +item_id : null;
// If reporting a comment, resolve the item_id it belongs to
if (!resolved_item_id && comment_id) {
const comm = await db`SELECT item_id FROM comments WHERE id = ${comment_id}`;
if (comm.length > 0) resolved_item_id = comm[0].item_id;
}
const notificationsToAdd = mods.map(m => ({
user_id: m.id,
type: 'report',
reference_id: report_id,
item_id: resolved_item_id // Can be null now after migration
}));
await db`INSERT INTO notifications ${db(notificationsToAdd)}`;
}
} catch (notifyErr) {
console.error('[REPORT] Failed to send notifications:', notifyErr);
}
return res.json({ success: true, msg: "Report submitted successfully." });
} catch (err) {
return res.json({ success: false, msg: lib.logError(err) }, 500);
}
});
// Mod/Admin: Get pending/all reports
router.get(/^\/api\/v2\/mod\/reports\/?$/, lib.modAuth, async (req, res) => {
try {
const status = req.url.qs?.status || 'pending';
const page = +(req.url.qs?.page || 1);
const limit = 20;
const offset = (page - 1) * limit;
const reports = await db`
SELECT r.*,
rep.user AS reporter_name,
COALESCE(tgt_u.user, tgt_auth.user, comm_auth.user) AS reported_user_name,
COALESCE(NULLIF(r.user_id, 0), tgt_auth.id, comm_auth.id) AS reported_user_id,
COALESCE(tgt_u.admin, tgt_auth.admin, comm_auth.admin) AS reported_user_is_admin,
i.dest AS item_dest,
tgt_auth.id AS item_user_id,
tgt_auth.user AS item_user_name,
(SELECT coalesce(json_agg(json_build_object('id', t.id, 'tag', t.tag, 'normalized', t.normalized)), '[]')
FROM tags_assign ta
JOIN tags t ON ta.tag_id = t.id
WHERE ta.item_id = COALESCE(r.item_id, c.item_id)) as item_tags,
c.content AS comment_body,
COALESCE(r.item_id, c.item_id) AS resolved_item_id,
COALESCE(i.dest, ci.dest) AS resolved_item_dest,
COALESCE(i.mime, ci.mime) AS resolved_item_mime
FROM reports r
LEFT JOIN "user" rep ON r.reporter_id = rep.id
LEFT JOIN "user" tgt_u ON r.user_id = tgt_u.id
LEFT JOIN items i ON r.item_id = i.id
LEFT JOIN "user" tgt_auth ON i.username = tgt_auth.user
LEFT JOIN comments c ON r.comment_id = c.id
LEFT JOIN items ci ON c.item_id = ci.id
LEFT JOIN "user" comm_auth ON c.user_id = comm_auth.id
WHERE r.status = ${status}
ORDER BY r.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
// Compute badges for tags (sync with lib.getTags logic)
for (const r of reports) {
if (r.item_tags) {
for (const t of r.item_tags) {
if (t.tag.startsWith(">")) t.badge = "badge-greentext badge-light";
else if (t.normalized === "ukraine") t.badge = "badge-ukraine badge-light";
else if (/[а-яё]/.test(t.normalized) || t.normalized === "russia") t.badge = "badge-russia badge-light";
else if (t.normalized === "german") t.badge = "badge-german badge-light";
else if (t.normalized === "dutch") t.badge = "badge-dutch badge-light";
else if (t.normalized === "sfw") t.badge = "badge-success";
else if (t.normalized === "nsfw") t.badge = "badge-danger";
else t.badge = "badge-light";
}
}
}
const totalRes = await db`SELECT count(*) as c FROM reports WHERE status = ${status}`;
const total = totalRes[0].c;
const emojis = await db`SELECT name, url FROM custom_emojis`;
return res.json({
success: true,
reports,
emojis,
page,
pages: Math.ceil(total / limit),
total
});
} catch (err) {
return res.json({ success: false, msg: lib.logError(err) }, 500);
}
});
// Mod/Admin: Resolve a report
router.post(/^\/api\/v2\/mod\/reports\/(?<id>\d+)\/resolve\/?$/, lib.modAuth, async (req, res) => {
try {
const id = +req.params.id;
const { action } = req.post; // 'resolved' or 'rejected'
if (!['resolved', 'rejected'].includes(action)) {
return res.json({ success: false, msg: "Invalid action. Must be 'resolved' or 'rejected'." }, 400);
}
const result = await db`
UPDATE reports
SET status = ${action}, resolved_by = ${req.session.id}
WHERE id = ${id}
RETURNING id
`;
if (result.length === 0) {
return res.json({ success: false, msg: "Report not found." }, 404);
}
await audit.log(req.session.id, 'resolve_report', 'report', id, { status: action });
return res.json({ success: true, msg: `Report marked as ${action}.` });
} catch (err) {
return res.json({ success: false, msg: lib.logError(err) }, 500);
}
});
return router;
};