rewrite website
This commit is contained in:
300
src/actions/report.ts
Normal file
300
src/actions/report.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
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 { MAX_UPLOAD_BYTES, 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';
|
||||
import { allowedImageTypes, allowedVideoTypes } from '@util/media.ts';
|
||||
|
||||
export const report = {
|
||||
submitReport: defineAction({
|
||||
input: z.object({
|
||||
urlHash: z.string(),
|
||||
reported: z.string().nullish(),
|
||||
reason: z.string(),
|
||||
body: z.string(),
|
||||
files: z
|
||||
.array(
|
||||
z
|
||||
.instanceof(File)
|
||||
.refine((f) => [...allowedImageTypes, ...allowedVideoTypes].findIndex((v) => v === f.type) !== -1)
|
||||
)
|
||||
.nullable()
|
||||
}),
|
||||
handler: async (input) => {
|
||||
const fileSize = input.files?.reduce((prev, curr) => prev + curr.size, 0);
|
||||
if (fileSize && fileSize > MAX_UPLOAD_BYTES) {
|
||||
throw new ActionError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Die Anhänge sind zu groß'
|
||||
});
|
||||
}
|
||||
|
||||
const report = await db.getReportByUrlHash({ urlHash: input.urlHash });
|
||||
if (!report) {
|
||||
throw new ActionError({
|
||||
code: 'NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
let reportedId = report.reported?.id ?? null;
|
||||
if (input.reported != report.reported?.username) {
|
||||
if (input.reported == null) reportedId = null;
|
||||
else {
|
||||
const reportedUser = await db.getUserByUsername({ username: input.reported });
|
||||
if (!reportedUser)
|
||||
throw new ActionError({
|
||||
code: 'NOT_FOUND'
|
||||
});
|
||||
reportedId = reportedUser.id;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let type: 'image' | 'video';
|
||||
if (allowedImageTypes.includes(file.type)) {
|
||||
type = 'image';
|
||||
} else if (allowedVideoTypes.includes(file.type)) {
|
||||
type = 'video';
|
||||
} else {
|
||||
throw new ActionError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Invalid file type'
|
||||
});
|
||||
}
|
||||
|
||||
await tx.addReportAttachment({
|
||||
type: type,
|
||||
hash: hash,
|
||||
reportId: report.id
|
||||
});
|
||||
|
||||
fs.renameSync(tmpFilePath, filePath);
|
||||
filePaths.pop();
|
||||
filePaths.push(filePath);
|
||||
}
|
||||
|
||||
await tx.submitReport({
|
||||
urlHash: input.urlHash,
|
||||
reportedId: reportedId,
|
||||
reason: input.reason,
|
||||
body: input.body
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
for (const filePath of filePaths) {
|
||||
fs.rmSync(filePath);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
sendWebhook(WebhookAction.Report, {
|
||||
reporter: report.reporter.username,
|
||||
reported: report.reported?.username ?? null,
|
||||
reason: input.reason
|
||||
});
|
||||
},
|
||||
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,
|
||||
reporterId: input.reporter,
|
||||
reportedId: input.reported
|
||||
});
|
||||
|
||||
return {
|
||||
id: id
|
||||
};
|
||||
}
|
||||
}),
|
||||
editReport: defineAction({
|
||||
input: z.object({
|
||||
reportId: z.number(),
|
||||
reported: z.number().nullable()
|
||||
}),
|
||||
handler: async (input, context) => {
|
||||
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||
|
||||
await db.editReport({
|
||||
id: input.reportId,
|
||||
reportedId: input.reported
|
||||
});
|
||||
}
|
||||
}),
|
||||
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
|
||||
});
|
||||
} else {
|
||||
await db.deleteStrike({ reportId: input.reportId });
|
||||
}
|
||||
});
|
||||
|
||||
if (input.status === 'closed' && preReportStrike?.strikeReason?.id != input.strikeReasonId) {
|
||||
const report = await db.getReportById({ id: input.reportId });
|
||||
if (report.reported) {
|
||||
const user = await db.getUserById({ id: report.reported.id });
|
||||
|
||||
// send webhook in background
|
||||
sendWebhook(WebhookAction.Strike, {
|
||||
user: user!.uuid!
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
reports: defineAction({
|
||||
input: z.object({
|
||||
reporter: z.string().nullish(),
|
||||
reported: z.string().nullish(),
|
||||
includeDrafts: z.boolean().nullish()
|
||||
}),
|
||||
handler: async (input, context) => {
|
||||
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||
|
||||
return {
|
||||
reports: await db.getReports(input)
|
||||
};
|
||||
}
|
||||
}),
|
||||
reportAttachments: defineAction({
|
||||
input: z.object({
|
||||
reportId: z.number()
|
||||
}),
|
||||
handler: async (input, context) => {
|
||||
Session.actionSessionFromCookies(context.cookies, Permissions.Reports);
|
||||
|
||||
return {
|
||||
reportAttachments: (await db.getReportAttachments(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({})
|
||||
};
|
||||
}
|
||||
}),
|
||||
usernames: defineAction({
|
||||
input: z.object({
|
||||
username: z.string()
|
||||
}),
|
||||
handler: async (input) => {
|
||||
const users = await db.getUsers({ username: input.username, limit: 5 });
|
||||
|
||||
return {
|
||||
usernames: users.map((u) => u.username)
|
||||
};
|
||||
}
|
||||
})
|
||||
};
|
||||
Reference in New Issue
Block a user