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