import type { RequestHandler } from '@sveltejs/kit'; import { getSession } from '$lib/server/session'; import { Permissions } from '$lib/permissions'; import { Admin, Report, StrikeReason, User } from '$lib/server/database'; import type { Attributes } from 'sequelize'; import { Op } from 'sequelize'; import { env } from '$env/dynamic/private'; import crypto from 'crypto'; import { webhookUserReported } from '$lib/server/webhook'; export const POST = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.ReportRead] }) == null) { return new Response(null, { status: 401 }); } const data: { limit: number | null; from: number | null; draft: boolean | null; status: 'none' | 'review' | 'reviewed' | null; reporter: string | null; reported: string | null; hash: string | null; } = await request.json(); let reportFindOptions: Attributes = {}; if (data.draft != null) reportFindOptions.draft = data.draft; reportFindOptions.status = data.status == null ? ['none', 'review'] : data.status; if (data.reporter != null) { const reporter_ids = await User.findAll({ attributes: ['id'], where: { username: { [Op.like]: `%${data.reporter}%` } } }); reportFindOptions.reporter_id = reporter_ids.map((u) => u.id); } if (data.reported != null) { const reported_ids = await User.findAll({ attributes: ['id'], where: { username: { [Op.like]: `%${data.reported}%` } } }); reportFindOptions.reported_id = reported_ids.map((u) => u.id); } if (data.hash != null) { reportFindOptions = { url_hash: data.hash }; data.from = 0; data.limit = 1; } let reports = await Report.findAll({ where: reportFindOptions, include: [ { model: User, as: 'reporter' }, { model: User, as: 'reported' }, { model: Admin, as: 'auditor' }, { model: StrikeReason, as: 'strike_reason' } ], order: ['created_at'], offset: data.from || 0, limit: data.limit || 100 }); reports = reports.map((r) => { if (r.auditor_id === null && r.status != 'none') { // if the report was edited by the admin account set by the env variable, it has no relation to the admin // table in the database, so it gets manually created here. we just assume that the auditor_id is never null // when not edited by the env admin account r.auditor_id = -1; r.dataValues.auditor = { id: -1, username: env.ADMIN_USER, permissions: new Permissions(Permissions.allPermissions()), createdAt: 0, updatedAt: 0 }; } else if (r.auditor) { delete r.dataValues.auditor.password; } if (!r.strike_reason) { r.strike_reason_id = -1; } return r; }); return new Response( JSON.stringify({ reports: reports, count: await Report.count({ where: reportFindOptions }) }) ); }) satisfies RequestHandler; export const PATCH = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.ReportWrite] }) == null) { return new Response(null, { status: 401 }); } const data: { id: number; reported: string | null; auditor: number; notice: string | null; statement: string | null; status: 'none' | 'review' | 'reviewed' | null; strike_reason: number | null; } = await request.json(); if (data.id === null || data.auditor === null) return new Response(null, { status: 400 }); const report = await Report.findOne({ where: { id: data.id } }); const admin = await Admin.findOne({ where: { id: data.auditor } }); const reported = data.reported ? await User.findOne({ where: { uuid: data.reported } }) : undefined; if (report === null || (admin === null && data.auditor != -1) || reported === null) return new Response(null, { status: 400 }); const webhookTriggerUsers: string[] = []; if (report.reported_id != reported?.id) { const oldReportUser = await User.findByPk(report.reported_id); if (oldReportUser) webhookTriggerUsers.push(oldReportUser.uuid); if (reported) webhookTriggerUsers.push(reported.uuid); } else if ( reported && report.reported_id != null && report.strike_reason_id != data.strike_reason ) { webhookTriggerUsers.push(reported.uuid); } report.reported_id = reported?.id ?? null; if (data.notice != null) report.notice = data.notice; if (data.statement != null) report.statement = data.statement; if (data.status != null) report.status = data.status; if (data.strike_reason != null) { if (data.status !== 'reviewed') { if (data.strike_reason == -1) { report.strike_reason_id = null; } else { const strike_reason = await StrikeReason.findByPk(data.strike_reason); if (strike_reason == null) return new Response(null, { status: 400 }); report.strike_reason_id = strike_reason.id; } } else if (data.strike_reason == -1 && report.strike_reason_id != null) { report.strike_reason_id = null; report.striked_at = null; } else if (data.strike_reason != -1 && data.strike_reason != report.strike_reason_id) { if (!report.reported_id) return new Response(null, { status: 400 }); const strike_reason = await StrikeReason.findByPk(data.strike_reason); if (strike_reason == null) return new Response(null, { status: 400 }); report.strike_reason_id = strike_reason.id; report.striked_at = new Date(Date.now()); } } if (admin != null) report.auditor_id = admin.id; await report.save(); if (webhookTriggerUsers.length > 0 && data.status == 'reviewed' && env.REPORTED_WEBHOOK) { for (const webhookTriggerUser of webhookTriggerUsers) { // no `await` to avoid blocking webhookUserReported(env.REPORTED_WEBHOOK, webhookTriggerUser); } } return new Response(); }) satisfies RequestHandler; export const PUT = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.ReportWrite] }) == null) { return new Response(null, { status: 401 }); } const data: { reporter: string; reported: string | null; reason: string; body: string | null; } = await request.json(); if (data.reporter == null || data.reason == null || data.body === undefined) return new Response(null, { status: 400 }); const reporter = await User.findOne({ where: { uuid: data.reporter } }); const reported = data.reported ? await User.findOne({ where: { uuid: data.reported } }) : null; if (reporter == null) return new Response(null, { status: 400 }); const report = await Report.create({ subject: data.reason, body: data.body, draft: data.body === null, status: 'none', url_hash: crypto.randomBytes(18).toString('hex'), completed: false, reporter_id: reporter.id, reported_id: reported?.id || null }); report.dataValues.reporter = await User.findOne({ where: { id: report.reporter_id } }); report.dataValues.reported = report.reported_id ? await User.findOne({ where: { id: report.reported_id } }) : null; report.dataValues.auditor = null; return new Response(JSON.stringify(report), { status: 201 }); }) satisfies RequestHandler;