the funny fixes from qa

This commit is contained in:
mc 2024-02-03 01:41:24 +01:00
parent 903688b3d6
commit 7b8990bc95
12 changed files with 129 additions and 16 deletions

View File

@ -0,0 +1,4 @@
// https://advancedweb.hu/how-to-use-async-functions-with-array-filter-in-javascript/
export async function AsyncFilter(arr: unknown[], predicate: (value: unknown, index: number, array: unknown[]) => Promise<boolean>) {
return Promise.all(arr.map(predicate)).then((results) => arr.filter((_v, index) => results[index]));
}

View File

@ -1,8 +1,9 @@
import { Router } from "express"; import { Router } from "express";
import { ENVIRONMENT } from "../Modules/Constants"; import { ENVIRONMENT } from "../Modules/Constants";
import { RequireAuthentication, ValidateBody } from "../Modules/Middleware"; import { RequireAuthentication, ValidateBody } from "../Modules/Middleware";
import { UserPermissions } from "../Schemas/User"; import { User, UserPermissions } from "../Schemas/User";
import j from "joi"; import j from "joi";
import { Song } from "../Schemas/Song";
const App = Router(); const App = Router();
@ -38,6 +39,12 @@ async (req, res) => {
res.json(req.user); res.json(req.user);
}) })
App.get("/raw/song/:SongID",
async (req, res) => res.json(await Song.findOne({ where: { ID: req.params.SongID } })));
App.get("/raw/user/:UserID",
async (req, res) => res.json(await User.findOne({ where: { ID: req.params.UserID } })));
export default { export default {
App, App,
DefaultAPI: "/api/debug" DefaultAPI: "/api/debug"

View File

@ -1,6 +1,7 @@
import j from "joi"; import j from "joi";
import ffmpeg from "fluent-ffmpeg"; import ffmpeg from "fluent-ffmpeg";
import sizeOf from "image-size"; import sizeOf from "image-size";
import cron from "node-cron";
import { Router } from "express"; import { Router } from "express";
import { RequireAuthentication, ValidateBody } from "../Modules/Middleware"; import { RequireAuthentication, ValidateBody } from "../Modules/Middleware";
import { Song, SongStatus } from "../Schemas/Song"; import { Song, SongStatus } from "../Schemas/Song";
@ -11,6 +12,21 @@ import { rmSync, writeFileSync, renameSync, readFileSync } from "fs";
import { FULL_SERVER_ROOT, MAX_AMOUNT_OF_DRAFTS_AT_ONCE } from "../Modules/Constants"; import { FULL_SERVER_ROOT, MAX_AMOUNT_OF_DRAFTS_AT_ONCE } from "../Modules/Constants";
import { UserPermissions } from "../Schemas/User"; import { UserPermissions } from "../Schemas/User";
cron.schedule("*/2 * * * *", async () => {
Debug("Running cron schedule to check for broken drafts.")
const EligibleSongs = await Song.find({ where: { IsDraft: true, Status: SongStatus.PROCESSING } });
for (const SongData of EligibleSongs) {
if (SongData.HasMidi && SongData.HasCover && SongData.HasAudio)
continue;
if (SongData.CreationDate.getTime() + 60 * 1000 > Date.now())
continue;
SongData.Status = SongStatus.BROKEN;
await SongData.save();
}
});
const App = Router(); const App = Router();
App.post("/create", App.post("/create",
@ -64,11 +80,22 @@ App.post("/upload/midi",
if (req.user!.PermissionLevel! < UserPermissions.Administrator && SongData.Author.ID !== req.user!.ID) if (req.user!.PermissionLevel! < UserPermissions.Administrator && SongData.Author.ID !== req.user!.ID)
return res.status(403).send("You don't have permission to upload to this song."); return res.status(403).send("You don't have permission to upload to this song.");
if (SongData.HasMidi) {
if (SongData.Status !== SongStatus.BROKEN && SongData.Status !== SongStatus.DEFAULT && SongData.Status !== SongStatus.DENIED && SongData.Status !== SongStatus.PUBLIC)
return res.status(400).send("You cannot update this song at this moment.");
rmSync(`./Saved/Songs/${req.body.TargetSong}/Data.mid`);
SongData.HasMidi = false;
SongData.IsDraft = true;
await SongData.save();
}
writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Data.mid`, Decoded); writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Data.mid`, Decoded);
res.send(`${FULL_SERVER_ROOT}/song/download/${req.body.TargetSong}/midi.mid`); res.send(`${FULL_SERVER_ROOT}/song/download/${req.body.TargetSong}/midi.mid`);
await SongData.reload(); await SongData.reload();
SongData.HasMidi = true; SongData.HasMidi = true;
SongData.Status = SongData.HasMidi && SongData.HasCover && SongData.HasAudio ? SongStatus.DEFAULT : SongData.Status;
await SongData.save(); await SongData.save();
}); });
@ -92,6 +119,16 @@ App.post("/upload/cover",
if (req.user!.PermissionLevel! < UserPermissions.Administrator && SongData.Author.ID !== req.user!.ID) if (req.user!.PermissionLevel! < UserPermissions.Administrator && SongData.Author.ID !== req.user!.ID)
return res.status(403).send("You don't have permission to upload to this song."); return res.status(403).send("You don't have permission to upload to this song.");
if (SongData.HasCover) {
if (SongData.Status !== SongStatus.BROKEN && SongData.Status !== SongStatus.DEFAULT && SongData.Status !== SongStatus.DENIED && SongData.Status !== SongStatus.PUBLIC)
return res.status(400).send("You cannot update this song at this moment.");
rmSync(`./Saved/Songs/${req.body.TargetSong}/Cover.png`);
SongData.HasCover = false;
SongData.IsDraft = true;
await SongData.save();
}
try { try {
const ImageSize = sizeOf(Decoded); const ImageSize = sizeOf(Decoded);
if (!ImageSize.height || !ImageSize.width) if (!ImageSize.height || !ImageSize.width)
@ -118,6 +155,7 @@ App.post("/upload/cover",
await SongData.reload(); await SongData.reload();
SongData.HasCover = true; SongData.HasCover = true;
SongData.Status = SongData.HasMidi && SongData.HasCover && SongData.HasAudio ? SongStatus.DEFAULT : SongData.Status;
await SongData.save(); await SongData.save();
writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Cover.png`, Decoded); writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Cover.png`, Decoded);
@ -150,6 +188,7 @@ App.post("/upload/audio",
rmSync(`./Saved/Songs/${req.body.TargetSong}/Chunks`, { recursive: true }); rmSync(`./Saved/Songs/${req.body.TargetSong}/Chunks`, { recursive: true });
SongData.HasAudio = false; SongData.HasAudio = false;
SongData.IsDraft = true;
SongData.Status = SongStatus.PROCESSING; SongData.Status = SongStatus.PROCESSING;
await SongData.save(); await SongData.save();
} }
@ -158,7 +197,6 @@ App.post("/upload/audio",
ffmpeg() ffmpeg()
.input(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`) .input(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`)
.outputOptions([ .outputOptions([
"-map 0",
"-use_timeline 1", "-use_timeline 1",
"-f dash" "-f dash"
]) ])

View File

@ -4,14 +4,26 @@ import { Song, SongStatus } from "../Schemas/Song";
import { OriginalSparks } from "../Modules/FNUtil"; import { OriginalSparks } from "../Modules/FNUtil";
import j from "joi"; import j from "joi";
import { UserPermissions } from "../Schemas/User"; import { UserPermissions } from "../Schemas/User";
import { AsyncFilter } from "../Modules/Extensions";
const App = Router(); const App = Router();
App.get("/me", RequireAuthentication({ BookmarkedSongs: true, CreatedTracks: true }), async (req, res) => { App.get("/me", RequireAuthentication({ BookmarkedSongs: true, CreatedTracks: true }), async (req, res) => {
const ProcessingTracks = req.user!.CreatedTracks.filter(x => x.Status === SongStatus.PROCESSING); const ProcessingTracks = req.user!.CreatedTracks.filter(x => x.Status === SongStatus.PROCESSING);
// @ts-expect-error not gonna bother making type
const NonExistingActiveTracks = await AsyncFilter(req.user!.Library, async x => !(await Song.exists({ where: { ID: x.SongID } })));
if (NonExistingActiveTracks.length > 0) {
for (const Track of NonExistingActiveTracks) {
console.log(Track);
// @ts-expect-error again not gonna bother making type
req.user!.Library.splice(req.user!.Library.findIndex(x => x.SongID === Track.SongID), 1);
}
await req.user!.save();
}
if (ProcessingTracks.length > 0) if (ProcessingTracks.length > 0)
for (const Track of ProcessingTracks) { for (const Track of ProcessingTracks) {
console.log(Track.HasAudio, Track.HasMidi, Track.HasCover)
if (!Track.HasAudio || !Track.HasMidi || !Track.HasCover) if (!Track.HasAudio || !Track.HasMidi || !Track.HasCover)
continue; continue;

View File

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/node-cron": "^3.0.11",
"axios": "^1.6.5", "axios": "^1.6.5",
"better-sqlite3": "^9.3.0", "better-sqlite3": "^9.3.0",
"colorette": "^2.0.20", "colorette": "^2.0.20",
@ -22,6 +23,7 @@
"image-size": "^1.1.1", "image-size": "^1.1.1",
"joi": "^17.12.0", "joi": "^17.12.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"node-cron": "^3.0.3",
"typeorm": "^0.3.19", "typeorm": "^0.3.19",
"underscore": "^1.13.6", "underscore": "^1.13.6",
"uuid": "^9.0.1" "uuid": "^9.0.1"
@ -337,6 +339,11 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
"integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==" "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w=="
}, },
"node_modules/@types/node-cron": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz",
"integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.8", "version": "6.9.8",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz",
@ -1756,6 +1763,25 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/node-cron": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
"dependencies": {
"uuid": "8.3.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/node-cron/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",

View File

@ -41,6 +41,7 @@
"typescript": "^5.2.2" "typescript": "^5.2.2"
}, },
"dependencies": { "dependencies": {
"@types/node-cron": "^3.0.11",
"axios": "^1.6.5", "axios": "^1.6.5",
"better-sqlite3": "^9.3.0", "better-sqlite3": "^9.3.0",
"colorette": "^2.0.20", "colorette": "^2.0.20",
@ -54,6 +55,7 @@
"image-size": "^1.1.1", "image-size": "^1.1.1",
"joi": "^17.12.0", "joi": "^17.12.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"node-cron": "^3.0.3",
"typeorm": "^0.3.19", "typeorm": "^0.3.19",
"underscore": "^1.13.6", "underscore": "^1.13.6",
"uuid": "^9.0.1" "uuid": "^9.0.1"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

@ -2,6 +2,7 @@ import { Box, Label, Text } from "@primer/react";
import { Divider } from "@primer/react/lib-esm/ActionList/Divider"; import { Divider } from "@primer/react/lib-esm/ActionList/Divider";
import { SongStatus } from "../utils/Extensions"; import { SongStatus } from "../utils/Extensions";
import { LabelColorOptions } from "@primer/react/lib-esm/Label/Label"; import { LabelColorOptions } from "@primer/react/lib-esm/Label/Label";
import DefaultCover from "../assets/NoCoverDetected.png";
export function Song({ data, children }: { data: any, children?: JSX.Element[] | JSX.Element | string }) { export function Song({ data, children }: { data: any, children?: JSX.Element[] | JSX.Element | string }) {
function GetStatusLabel() { function GetStatusLabel() {
@ -50,7 +51,7 @@ export function Song({ data, children }: { data: any, children?: JSX.Element[] |
return ( return (
<Box sx={{ overflow: "hidden", minWidth: 50, maxWidth: 200, padding: 2, borderRadius: 10, border: "solid", borderColor: "border.default" }}> <Box sx={{ overflow: "hidden", minWidth: 50, maxWidth: 200, padding: 2, borderRadius: 10, border: "solid", borderColor: "border.default" }}>
<img src={data.Cover} style={{ width: "100%", borderRadius: 10 }} /> <img onError={e => (e.target as HTMLImageElement).src = DefaultCover} src={data.Cover} style={{ width: "100%", borderRadius: 10 }} />
<center> <center>
<Text sx={{ display: "block", textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}>{data.ArtistName}</Text> <Text sx={{ display: "block", textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}>{data.ArtistName}</Text>
<Text sx={{ display: "block", textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}><b>{data.Name}</b></Text> <Text sx={{ display: "block", textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}><b>{data.Name}</b></Text>

View File

@ -7,6 +7,9 @@
.songCategory { .songCategory {
display: inline-flex; display: inline-flex;
flex: none;
overflow-x: auto;
overflow-y: hidden;
gap: 10px; gap: 10px;
} }

View File

@ -1,12 +1,11 @@
import { Box, Text } from "@primer/react"; import { Box, Text } from "@primer/react";
import { EULA } from "./EULA";
export function Home() { export function Home() {
return ( return (
<> <>
<Box> <Box>
<Text>Welcome to the Online Test 1</Text> <Text>online test 2 please kill me thanks</Text>
<EULA /> <Text>CLICK THE LOGIN ICON ON THE TOP RIGHT</Text>
</Box> </Box>
</> </>
) )

View File

@ -1,3 +1,5 @@
import axios from "axios";
import { Buffer } from "buffer/";
import { ActionList, ActionMenu, Avatar, Box, Button, Dialog, FormControl, Heading, Text, TextInput } from "@primer/react" import { ActionList, ActionMenu, Avatar, Box, Button, Dialog, FormControl, Heading, Text, TextInput } from "@primer/react"
import { Divider } from "@primer/react/lib-esm/ActionList/Divider"; import { Divider } from "@primer/react/lib-esm/ActionList/Divider";
import { PageHeader } from "@primer/react/drafts"; import { PageHeader } from "@primer/react/drafts";
@ -5,7 +7,6 @@ import { useContext, useEffect, useRef, useState } from "react";
import { SiteContext } from "../utils/State"; import { SiteContext } from "../utils/State";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import { Song } from "../components/Song"; import { Song } from "../components/Song";
import axios from "axios";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { SongStatus } from "../utils/Extensions"; import { SongStatus } from "../utils/Extensions";
@ -82,27 +83,42 @@ export function Profile() {
} }
</Text> </Text>
<form method="GET" action="" ref={formRef}> <form method="GET" action="" ref={formRef}>
<FormControl required={true} sx={formControlStyle}> <FormControl sx={formControlStyle}>
<FormControl.Label>MIDI File (.mid)</FormControl.Label> <FormControl.Label>MIDI File (.mid)</FormControl.Label>
<TextInput sx={{ width: "100%" }} type="file" /> <TextInput sx={{ width: "100%" }} type="file" />
<FormControl.Caption>You can use the #tools-and-resources channel to find useful resources on how to create MIDIs.</FormControl.Caption> <FormControl.Caption>You can use the #tools-and-resources channel to find useful resources on how to create MIDIs.</FormControl.Caption>
</FormControl> </FormControl>
<FormControl required={true} sx={formControlStyle}> <FormControl sx={formControlStyle}>
<FormControl.Label>Audio File (.m4a, .mp3, .wav)</FormControl.Label> <FormControl.Label>Audio File (.m4a, .mp3, .wav)</FormControl.Label>
<TextInput sx={{ width: "100%" }} type="file" /> <TextInput sx={{ width: "100%" }} type="file" />
<FormControl.Caption>This will play in the background of your song. Make sure it was exported from REAPER.</FormControl.Caption> <FormControl.Caption>This will play in the background of your song. Make sure it was exported from REAPER.</FormControl.Caption>
</FormControl> </FormControl>
<FormControl required={true} sx={formControlStyle}> <FormControl sx={formControlStyle}>
<FormControl.Label>Cover Image (.png)</FormControl.Label> <FormControl.Label>Cover Image (.png)</FormControl.Label>
<TextInput sx={{ width: "100%" }} type="file" /> <TextInput sx={{ width: "100%" }} type="file" />
<FormControl.Caption>Must be a 1:1 ratio. Max: 2048x2048, min: 512x512</FormControl.Caption> <FormControl.Caption>Must be a 1:1 ratio. Max: 2048x2048, min: 512x512</FormControl.Caption>
</FormControl> </FormControl>
<Button type="submit" sx={{ marginTop: 3, width: "100%" }} onClick={e => { <Button type="submit" sx={{ marginTop: 3, width: "100%" }} onClick={async e => {
e.preventDefault(); e.preventDefault();
const Midi = (formRef.current[0] as HTMLInputElement).files![0]; const Midi = (formRef.current[0] as HTMLInputElement).files![0];
const Music = (formRef.current[1] as HTMLInputElement).files![0]; const Music = (formRef.current[1] as HTMLInputElement).files![0];
const Cover = (formRef.current[2] as HTMLInputElement).files![0]; const Cover = (formRef.current[2] as HTMLInputElement).files![0];
if (Midi) {
const MidiRes = await axios.post("/api/drafts/upload/midi", { Data: Buffer.from(await Midi.arrayBuffer()).toString("hex"), TargetSong: updating.ID });
toast(MidiRes.status === 200 ? "Uploaded MIDI chart successfully." : MidiRes.data, { type: MidiRes.status === 200 ? "success" : "error" });
}
if (Cover) {
const CoverRes = await axios.post("/api/drafts/upload/cover", { Data: Buffer.from(await Cover.arrayBuffer()).toString("hex"), TargetSong: updating.ID });
toast(CoverRes.status === 200 ? "Uploaded cover image successfully." : CoverRes.data, { type: CoverRes.status === 200 ? "success" : "error" });
}
if (Music) {
const AudioRes = await axios.post("/api/drafts/upload/audio", { Data: Buffer.from(await Music.arrayBuffer()).toString("hex"), TargetSong: updating.ID });
toast(AudioRes.status === 200 ? "Uploaded audio for processing successfully." : AudioRes.data, { type: AudioRes.status === 200 ? "success" : "error" });
}
}}>{ updating.Status === SongStatus.PUBLIC ? "Unlist and Update" : "Update" }</Button> }}>{ updating.Status === SongStatus.PUBLIC ? "Unlist and Update" : "Update" }</Button>
</form> </form>
</Box> </Box>

View File

@ -8,6 +8,7 @@ const formControlStyle = { paddingTop: 3 };
export function TrackSubmission() { export function TrackSubmission() {
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null);
const [waiting, setWaiting] = useState<boolean>(false);
const [Key, setKey] = useState<string>("Select a key..."); const [Key, setKey] = useState<string>("Select a key...");
const [Scale, setScale] = useState<string>("Select a scale..."); const [Scale, setScale] = useState<string>("Select a scale...");
const [GuitarStarterType, setGuitarStarterType] = useState<string>("Select the starter type..."); const [GuitarStarterType, setGuitarStarterType] = useState<string>("Select the starter type...");
@ -119,7 +120,8 @@ export function TrackSubmission() {
<TextInput type="number" /> <TextInput type="number" />
<FormControl.Caption>Ranges from 0-6</FormControl.Caption> <FormControl.Caption>Ranges from 0-6</FormControl.Caption>
</FormControl> </FormControl>
<Button sx={{ marginTop: 2 }} type="submit" onClick={async e => { <Button sx={{ marginTop: 2 }} type="submit" disabled={waiting} onClick={async e => {
setWaiting(true);
e.preventDefault(); e.preventDefault();
console.log(formRef); console.log(formRef);
@ -172,12 +174,15 @@ export function TrackSubmission() {
const MidiRes = await axios.post("/api/drafts/upload/midi", { Data: Buffer.from(await Midi.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID }); const MidiRes = await axios.post("/api/drafts/upload/midi", { Data: Buffer.from(await Midi.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID });
toast(MidiRes.status === 200 ? "Uploaded MIDI chart successfully." : MidiRes.data, { type: MidiRes.status === 200 ? "success" : "error" }); toast(MidiRes.status === 200 ? "Uploaded MIDI chart successfully." : MidiRes.data, { type: MidiRes.status === 200 ? "success" : "error" });
const CoverRes = await axios.post("/api/drafts/upload/cover", { Data: Buffer.from(await Cover.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID });
toast(CoverRes.status === 200 ? "Uploaded cover image successfully." : CoverRes.data, { type: CoverRes.status === 200 ? "success" : "error" });
const AudioRes = await axios.post("/api/drafts/upload/audio", { Data: Buffer.from(await Music.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID }); const AudioRes = await axios.post("/api/drafts/upload/audio", { Data: Buffer.from(await Music.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID });
toast(AudioRes.status === 200 ? "Uploaded audio for processing successfully." : AudioRes.data, { type: AudioRes.status === 200 ? "success" : "error" }); toast(AudioRes.status === 200 ? "Uploaded audio for processing successfully." : AudioRes.data, { type: AudioRes.status === 200 ? "success" : "error" });
const CoverRes = await axios.post("/api/drafts/upload/cover", { Data: Buffer.from(await Cover.arrayBuffer()).toString("hex"), TargetSong: SongData.data.ID }); setWaiting(false);
toast(CoverRes.status === 200 ? "Uploaded cover image successfully." : CoverRes.data, { type: CoverRes.status === 200 ? "success" : "error" }); toast("Finished processing song. You can now find it in your profile tab.");
}}>Create</Button> }}>{waiting ? "Please wait..." : "Create"}</Button>
</form> </form>
</> </>
) )