238 lines
6.7 KiB
TypeScript
238 lines
6.7 KiB
TypeScript
import { ActionError, defineAction } from 'astro:actions';
|
|
import { Session } from '@util/session.ts';
|
|
import { Permissions } from '@util/permissions.ts';
|
|
import { db } from '@db/database.ts';
|
|
import { z } from 'astro:schema';
|
|
import { UPLOAD_PATH } from 'astro:env/server';
|
|
import fs from 'node:fs';
|
|
import crypto from 'node:crypto';
|
|
import path from 'node:path';
|
|
import { sendWebhook, WebhookAction } from '@util/webhook.ts';
|
|
|
|
export const report = {
|
|
submitReport: defineAction({
|
|
input: z.object({
|
|
urlHash: z.string(),
|
|
reason: z.string(),
|
|
body: z.string(),
|
|
files: z.array(z.instanceof(File)).nullable()
|
|
}),
|
|
handler: async (input) => {
|
|
const report = await db.getReportByUrlHash({ urlHash: input.urlHash });
|
|
if (!report) {
|
|
throw new ActionError({
|
|
code: 'NOT_FOUND'
|
|
});
|
|
}
|
|
|
|
if (!UPLOAD_PATH) {
|
|
throw new ActionError({
|
|
code: 'FORBIDDEN',
|
|
message: 'Es dürfen keine Anhänge hochgeladen werden'
|
|
});
|
|
}
|
|
|
|
const filePaths = [] as string[];
|
|
try {
|
|
await db.transaction(async (tx) => {
|
|
for (const file of input.files ?? []) {
|
|
const uuid = crypto.randomUUID();
|
|
const tmpFilePath = path.join(UPLOAD_PATH!, uuid);
|
|
const tmpFileStream = fs.createWriteStream(tmpFilePath);
|
|
|
|
filePaths.push(tmpFilePath);
|
|
|
|
const md5Hash = crypto.createHash('md5');
|
|
|
|
for await (const chunk of file.stream()) {
|
|
md5Hash.update(chunk);
|
|
tmpFileStream.write(chunk);
|
|
}
|
|
|
|
const hash = md5Hash.digest('hex');
|
|
const filePath = path.join(UPLOAD_PATH!, hash);
|
|
|
|
await tx.addReportAttachment({
|
|
type: 'video',
|
|
hash: hash,
|
|
reportId: report.id
|
|
});
|
|
|
|
fs.renameSync(tmpFilePath, filePath);
|
|
filePaths.pop();
|
|
filePaths.push(filePath);
|
|
}
|
|
|
|
await tx.submitReport({
|
|
urlHash: input.urlHash,
|
|
reason: input.reason,
|
|
body: input.body
|
|
});
|
|
});
|
|
} catch (e) {
|
|
for (const filePath of filePaths) {
|
|
fs.rmSync(filePath);
|
|
}
|
|
|
|
throw e;
|
|
}
|
|
},
|
|
accept: 'form'
|
|
}),
|
|
addReport: defineAction({
|
|
input: z.object({
|
|
reason: z.string(),
|
|
body: z.string().nullable(),
|
|
createdAt: z.string().datetime().nullable(),
|
|
reporter: z.number(),
|
|
reported: z.number().nullable()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
|
|
|
const { id } = await db.addReport({
|
|
reason: input.reason,
|
|
body: input.body,
|
|
createdAt: input.createdAt ? new Date(input.createdAt) : null,
|
|
reporterTeamId: input.reporter,
|
|
reportedTeamId: input.reported
|
|
});
|
|
|
|
return {
|
|
id: id
|
|
};
|
|
}
|
|
}),
|
|
reportStatus: defineAction({
|
|
input: z.object({
|
|
reportId: z.number()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
|
|
|
return {
|
|
reportStatus: await db.getReportStatus(input)
|
|
};
|
|
}
|
|
}),
|
|
editReportStatus: defineAction({
|
|
input: z.object({
|
|
reportId: z.number(),
|
|
status: z.enum(['open', 'closed']).nullable(),
|
|
notice: z.string().nullable(),
|
|
statement: z.string().nullable(),
|
|
strikeReasonId: z.number().nullable()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
|
|
|
let preReportStrike;
|
|
if (input.status === 'closed') preReportStrike = await db.getStrikeByReportId({ reportId: input.reportId });
|
|
|
|
await db.transaction(async (tx) => {
|
|
await tx.editReportStatus(input);
|
|
|
|
if (input.strikeReasonId) {
|
|
await db.editStrike({
|
|
reportId: input.reportId,
|
|
strikeReasonId: input.strikeReasonId
|
|
});
|
|
}
|
|
});
|
|
|
|
if (input.status === 'closed' && preReportStrike?.strikeReasonId != input.strikeReasonId) {
|
|
const report = await db.getReportById({ id: input.reportId });
|
|
if (report.reported) {
|
|
const strikes = await db.getStrikesByTeamId({ teamId: report.reported.id });
|
|
const teamMembers = await db.getTeamMembersByTeamId({ teamId: report.reported.id });
|
|
|
|
// send webhook in background
|
|
sendWebhook(WebhookAction.Strike, {
|
|
users: teamMembers.map((tm) => tm.user.uuid!),
|
|
totalWeight: strikes.map((strike) => strike.reason.weight).reduce((a, b) => a + b, 0)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
reports: defineAction({
|
|
input: z.object({
|
|
reporter: z.string().nullish(),
|
|
reported: z.string().nullish()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
|
|
|
return {
|
|
reports: await db.getReports(input)
|
|
};
|
|
}
|
|
}),
|
|
addStrikeReason: defineAction({
|
|
input: z.object({
|
|
name: z.string(),
|
|
weight: z.number()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Admin);
|
|
|
|
return await db.addStrikeReason(input);
|
|
}
|
|
}),
|
|
editStrikeReason: defineAction({
|
|
input: z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
weight: z.number()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Admin);
|
|
|
|
await db.editStrikeReason(input);
|
|
}
|
|
}),
|
|
deleteStrikeReason: defineAction({
|
|
input: z.object({
|
|
id: z.number()
|
|
}),
|
|
handler: async (input, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Admin);
|
|
|
|
await db.deleteStrikeReason(input);
|
|
}
|
|
}),
|
|
strikeReasons: defineAction({
|
|
handler: async (_, context) => {
|
|
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
|
|
|
return {
|
|
strikeReasons: await db.getStrikeReasons({})
|
|
};
|
|
}
|
|
}),
|
|
teamNamesByUsername: defineAction({
|
|
input: z.object({
|
|
username: z.string().nullish()
|
|
}),
|
|
handler: async (input) => {
|
|
const teams = await db.getTeamsByUsername({ username: input.username ?? '', limit: 5 });
|
|
|
|
return {
|
|
teamNames: teams.map((team) => ({ name: team.user.username, value: team.team.name }))
|
|
};
|
|
}
|
|
}),
|
|
teamNamesByTeamName: defineAction({
|
|
input: z.object({
|
|
teamName: z.string().nullish()
|
|
}),
|
|
handler: async (input) => {
|
|
const teams = await db.getTeams({ name: input.teamName, limit: 5 });
|
|
|
|
return {
|
|
teamNames: teams.map((team) => ({ name: team.name, value: team.name }))
|
|
};
|
|
}
|
|
})
|
|
};
|