Handle new report submission process
This commit is contained in:
parent
a132fd667f
commit
a3a5308d81
@ -10,6 +10,7 @@ import {
|
||||
Input,
|
||||
Link,
|
||||
Text,
|
||||
Textarea,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
@ -96,9 +97,8 @@ export default function () {
|
||||
).value
|
||||
.replaceAll(" ", "")
|
||||
.split(",");
|
||||
const file = (
|
||||
document.getElementById("evidence") as HTMLInputElement
|
||||
).files?.item(0);
|
||||
const files = (document.getElementById("evidence") as HTMLInputElement)
|
||||
.files;
|
||||
|
||||
if (!usernames.length)
|
||||
return toast({
|
||||
@ -108,9 +108,9 @@ export default function () {
|
||||
title: "Error",
|
||||
});
|
||||
|
||||
if (!file)
|
||||
if (!files?.length)
|
||||
return toast({
|
||||
description: "Must attach a file",
|
||||
description: "Must attach at least one file",
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Error",
|
||||
@ -124,10 +124,29 @@ export default function () {
|
||||
title: "Too Many Usernames",
|
||||
});
|
||||
|
||||
if (!logged_in && !turnstileToken)
|
||||
return toast({
|
||||
description: "Please complete the captcha and try again",
|
||||
isClosable: true,
|
||||
status: "error",
|
||||
title: "Captcha not completed",
|
||||
});
|
||||
|
||||
const description = (
|
||||
document.getElementById("description") as HTMLTextAreaElement
|
||||
).value;
|
||||
|
||||
const filelist = [];
|
||||
|
||||
for (const file of files) {
|
||||
filelist.push({ name: file.name, size: file.size });
|
||||
}
|
||||
|
||||
const submitReq = await fetch("/api/reports/submit", {
|
||||
body: JSON.stringify({
|
||||
filename: file.name,
|
||||
filesize: file.size,
|
||||
description: description || undefined,
|
||||
files: filelist,
|
||||
turnstileResponse: logged_in ? undefined : turnstileToken,
|
||||
usernames,
|
||||
}),
|
||||
headers: {
|
||||
@ -152,42 +171,62 @@ export default function () {
|
||||
});
|
||||
}
|
||||
|
||||
const { id, upload_url }: { id: string; upload_url: string } =
|
||||
const { id, upload_urls }: { id: string; upload_urls: string[] } =
|
||||
await submitReq.json();
|
||||
|
||||
setUploading(true);
|
||||
const reader = file.stream().getReader();
|
||||
const totalSize = filelist.reduce((a, b) => a + b.size, 0);
|
||||
let bytesRead = 0;
|
||||
let shouldRecall = false;
|
||||
|
||||
const uploadReq = await fetch(upload_url, {
|
||||
body: supportsRequestStreams
|
||||
? new ReadableStream({
|
||||
async pull(controller) {
|
||||
const chunk = await reader.read();
|
||||
setUploading(true);
|
||||
|
||||
if (chunk.done) {
|
||||
controller.close();
|
||||
setUploading(false);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < upload_urls.length; i++) {
|
||||
const reader = files[i].stream().getReader();
|
||||
|
||||
controller.enqueue(chunk.value);
|
||||
bytesRead += chunk.value.length;
|
||||
setFileProgress(Math.floor((bytesRead / file.size) * 100));
|
||||
},
|
||||
})
|
||||
: file,
|
||||
// @ts-expect-error
|
||||
duplex: supportsRequestStreams ? "half" : undefined,
|
||||
headers: {
|
||||
"content-type":
|
||||
file.type ||
|
||||
fileTypes[file.name.split(".")[file.name.split(".").length - 1]],
|
||||
},
|
||||
method: "PUT",
|
||||
}).catch(console.error);
|
||||
try {
|
||||
const uploadReq = await fetch(upload_urls[i], {
|
||||
body: supportsRequestStreams
|
||||
? new ReadableStream({
|
||||
async pull(controller) {
|
||||
const chunk = await reader.read();
|
||||
|
||||
if (!uploadReq?.ok) {
|
||||
if (chunk.done) {
|
||||
controller.close();
|
||||
|
||||
if (i === upload_urls.length - 1) setUploading(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
controller.enqueue(chunk.value);
|
||||
bytesRead += chunk.value.length;
|
||||
setFileProgress(Math.floor((bytesRead / totalSize) * 100));
|
||||
},
|
||||
})
|
||||
: files[i],
|
||||
// @ts-expect-error
|
||||
duplex: supportsRequestStreams ? "half" : undefined,
|
||||
headers: {
|
||||
"content-type":
|
||||
files[i].type ||
|
||||
fileTypes[files[i].name.split(".").at(-1) as string],
|
||||
},
|
||||
method: "PUT",
|
||||
});
|
||||
|
||||
if (!uploadReq.ok) {
|
||||
shouldRecall = true;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
shouldRecall = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRecall) {
|
||||
await fetch("/api/reports/recall", {
|
||||
body: JSON.stringify({ id }),
|
||||
headers: {
|
||||
@ -245,7 +284,7 @@ export default function () {
|
||||
</FormControl>
|
||||
<br />
|
||||
<FormControl isRequired>
|
||||
<FormLabel>Your Evidence (Max Size: 512MB)</FormLabel>
|
||||
<FormLabel>Your Evidence (Max size per file: 512MB)</FormLabel>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
mr="8px"
|
||||
@ -256,6 +295,11 @@ export default function () {
|
||||
<input id="evidence" multiple type="file" />
|
||||
</FormControl>
|
||||
<br />
|
||||
<FormControl>
|
||||
<FormLabel>Optional description</FormLabel>
|
||||
<Textarea id="description" maxLength={512} />
|
||||
</FormControl>
|
||||
<br />
|
||||
<div
|
||||
className="cf-turnstile"
|
||||
data-callback="setToken"
|
||||
|
@ -10,8 +10,16 @@ function errorResponse(error: string, status: number): Response {
|
||||
}
|
||||
|
||||
export async function onRequestPost(context: RequestContext) {
|
||||
const { actions, bypass, filename, filesize, turnstileResponse, usernames } =
|
||||
context.data.body;
|
||||
const {
|
||||
actions,
|
||||
bypass,
|
||||
description,
|
||||
filename,
|
||||
files,
|
||||
filesize,
|
||||
turnstileResponse,
|
||||
usernames,
|
||||
} = context.data.body;
|
||||
|
||||
if (!context.data.current_user) {
|
||||
if (typeof turnstileResponse !== "string")
|
||||
@ -21,6 +29,7 @@ export async function onRequestPost(context: RequestContext) {
|
||||
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||
{
|
||||
body: JSON.stringify({
|
||||
remoteip: context.request.headers.get("CF-Connecting-IP"),
|
||||
response: turnstileResponse,
|
||||
secret: context.env.TURNSTILE_SECRETKEY,
|
||||
}),
|
||||
@ -45,12 +54,42 @@ export async function onRequestPost(context: RequestContext) {
|
||||
if (!Array.isArray(usernames))
|
||||
return errorResponse("Usernames must be type of array", 400);
|
||||
|
||||
if (
|
||||
!["string", "undefined"].includes(typeof description) ||
|
||||
description?.length > 512
|
||||
)
|
||||
return errorResponse("Invalid description", 400);
|
||||
|
||||
if (
|
||||
!Array.isArray(files) ||
|
||||
files.find((file) => {
|
||||
const keys = Object.keys(file);
|
||||
|
||||
return !keys.includes("name") || !keys.includes("size");
|
||||
})
|
||||
)
|
||||
return errorResponse("File list missing name(s) and/or size(s)", 400);
|
||||
|
||||
if (typeof filename !== "string")
|
||||
return errorResponse("Invalid file name", 400);
|
||||
|
||||
if (typeof filesize !== "number" || filesize < 0 || filesize > 536870912)
|
||||
return errorResponse("Invalid file size", 400);
|
||||
|
||||
if (
|
||||
files.find(
|
||||
(file) =>
|
||||
typeof file.name !== "string" ||
|
||||
typeof file.size !== "number" ||
|
||||
file.size < 0 ||
|
||||
file.size > 536870912
|
||||
)
|
||||
)
|
||||
return errorResponse(
|
||||
"One or more files contain an invalid name or size",
|
||||
400
|
||||
);
|
||||
|
||||
if (!usernames.length || usernames.length > 20)
|
||||
return errorResponse(
|
||||
"Number of usernames provided must be between 1 and 20",
|
||||
@ -111,45 +150,53 @@ export async function onRequestPost(context: RequestContext) {
|
||||
metaNames.push(data.name);
|
||||
}
|
||||
|
||||
const fileParts = filename.split(".");
|
||||
let fileExt = fileParts[fileParts.length - 1];
|
||||
const uploadUrlPromises: Promise<string>[] = [];
|
||||
|
||||
if (
|
||||
fileParts.length < 2 ||
|
||||
![
|
||||
"mkv",
|
||||
"mp4",
|
||||
"wmv",
|
||||
"jpg",
|
||||
"png",
|
||||
"m4v",
|
||||
"jpeg",
|
||||
"jfif",
|
||||
"gif",
|
||||
"webm",
|
||||
"heif",
|
||||
"heic",
|
||||
"webp",
|
||||
"mov",
|
||||
].includes(fileExt.toLowerCase())
|
||||
)
|
||||
return errorResponse("This type of file cannot be uploaded", 415);
|
||||
for (const file of files) {
|
||||
const filePartes = file.name.split(".");
|
||||
let fileExten = filePartes.at(-1);
|
||||
|
||||
const fileKey = `${crypto.randomUUID().replaceAll("-", "")}/${crypto
|
||||
.randomUUID()
|
||||
.replaceAll("-", "")}${context.request.headers.get("cf-ray")}${Date.now()}`;
|
||||
if (
|
||||
filePartes.length < 2 ||
|
||||
![
|
||||
"mkv",
|
||||
"mp4",
|
||||
"wmv",
|
||||
"jpg",
|
||||
"png",
|
||||
"m4v",
|
||||
"jpeg",
|
||||
"jfif",
|
||||
"gif",
|
||||
"webm",
|
||||
"heif",
|
||||
"heic",
|
||||
"webp",
|
||||
"mov",
|
||||
].includes(fileExten.toLowerCase())
|
||||
)
|
||||
return errorResponse(
|
||||
`File ${file.name} cannot be uploaded as it is unsupported`,
|
||||
415
|
||||
);
|
||||
|
||||
const fileUploadKey = `${crypto.randomUUID().replaceAll("-", "")}/${crypto
|
||||
.randomUUID()
|
||||
.replaceAll("-", "")}${context.request.headers.get(
|
||||
"cf-ray"
|
||||
)}${Date.now()}`;
|
||||
|
||||
uploadUrlPromises.push(
|
||||
GenerateUploadURL(context.env, `t/${fileUploadKey}`, file.size, fileExten)
|
||||
);
|
||||
}
|
||||
|
||||
const uploadUrls = await Promise.allSettled(uploadUrlPromises);
|
||||
|
||||
const reportId = `${Date.now()}${context.request.headers.get(
|
||||
"cf-ray"
|
||||
)}${crypto.randomUUID().replaceAll("-", "")}`;
|
||||
|
||||
const uploadUrl = await GenerateUploadURL(
|
||||
context.env,
|
||||
fileKey,
|
||||
filesize,
|
||||
fileExt
|
||||
);
|
||||
|
||||
const { current_user: currentUser } = context.data;
|
||||
|
||||
await context.env.DATA.put(
|
||||
@ -158,22 +205,32 @@ export async function onRequestPost(context: RequestContext) {
|
||||
{ expirationTtl: 3600 }
|
||||
);
|
||||
|
||||
if (["mkv", "mov", "wmv"].includes(fileExt.toLowerCase()))
|
||||
await context.env.DATA.put(`videoprocessing_${fileKey}.${fileExt}`, "1", {
|
||||
expirationTtl: 3600,
|
||||
});
|
||||
if (uploadUrls.find((uploadUrl) => uploadUrl.status === "rejected"))
|
||||
return errorResponse("Failed to generate upload url", 500);
|
||||
|
||||
const attachments: string[] = [];
|
||||
|
||||
for (const urlResult of uploadUrls) {
|
||||
let url = urlResult.toString().replace("t/", "");
|
||||
const extension = (url.split(".").at(-1) as string).toLowerCase();
|
||||
|
||||
if (["mkv", "mov", "wmv"].includes(extension)) {
|
||||
await context.env.DATA.put(`videoprocessing_${url}.${extension}`, "1");
|
||||
|
||||
url = url.replace(`.${extension}`, ".mp4");
|
||||
}
|
||||
|
||||
attachments.push(url);
|
||||
}
|
||||
|
||||
await context.env.DATA.put(
|
||||
`report_${reportId}`,
|
||||
JSON.stringify({
|
||||
attachment: `${fileKey}.${
|
||||
["mkv", "mov", "wmv"].includes(fileExt.toLowerCase()) ? "mp4" : fileExt
|
||||
}`,
|
||||
attachments,
|
||||
id: reportId,
|
||||
open: !bypass,
|
||||
user: currentUser
|
||||
? {
|
||||
discriminator: currentUser.discriminator,
|
||||
email: currentUser.email,
|
||||
id: currentUser.id,
|
||||
username: currentUser.username,
|
||||
@ -192,9 +249,12 @@ export async function onRequestPost(context: RequestContext) {
|
||||
.run();
|
||||
} catch {}
|
||||
|
||||
return new Response(JSON.stringify({ id: reportId, upload_url: uploadUrl }), {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ id: reportId, upload_urls: uploadUrls }),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user