168 lines
7.2 KiB
JavaScript
168 lines
7.2 KiB
JavaScript
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;
|
||
};
|