the funny fixes from qa
This commit is contained in:
parent
903688b3d6
commit
7b8990bc95
4
Server/Source/Modules/Extensions.ts
Normal file
4
Server/Source/Modules/Extensions.ts
Normal 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]));
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { Router } from "express";
|
||||
import { ENVIRONMENT } from "../Modules/Constants";
|
||||
import { RequireAuthentication, ValidateBody } from "../Modules/Middleware";
|
||||
import { UserPermissions } from "../Schemas/User";
|
||||
import { User, UserPermissions } from "../Schemas/User";
|
||||
import j from "joi";
|
||||
import { Song } from "../Schemas/Song";
|
||||
|
||||
const App = Router();
|
||||
|
||||
|
@ -38,6 +39,12 @@ async (req, res) => {
|
|||
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 {
|
||||
App,
|
||||
DefaultAPI: "/api/debug"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import j from "joi";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import sizeOf from "image-size";
|
||||
import cron from "node-cron";
|
||||
import { Router } from "express";
|
||||
import { RequireAuthentication, ValidateBody } from "../Modules/Middleware";
|
||||
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 { 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();
|
||||
|
||||
App.post("/create",
|
||||
|
@ -64,11 +80,22 @@ App.post("/upload/midi",
|
|||
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.");
|
||||
|
||||
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);
|
||||
res.send(`${FULL_SERVER_ROOT}/song/download/${req.body.TargetSong}/midi.mid`);
|
||||
|
||||
await SongData.reload();
|
||||
SongData.HasMidi = true;
|
||||
SongData.Status = SongData.HasMidi && SongData.HasCover && SongData.HasAudio ? SongStatus.DEFAULT : SongData.Status;
|
||||
await SongData.save();
|
||||
});
|
||||
|
||||
|
@ -92,6 +119,16 @@ App.post("/upload/cover",
|
|||
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.");
|
||||
|
||||
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 {
|
||||
const ImageSize = sizeOf(Decoded);
|
||||
if (!ImageSize.height || !ImageSize.width)
|
||||
|
@ -118,6 +155,7 @@ App.post("/upload/cover",
|
|||
|
||||
await SongData.reload();
|
||||
SongData.HasCover = true;
|
||||
SongData.Status = SongData.HasMidi && SongData.HasCover && SongData.HasAudio ? SongStatus.DEFAULT : SongData.Status;
|
||||
await SongData.save();
|
||||
|
||||
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 });
|
||||
SongData.HasAudio = false;
|
||||
SongData.IsDraft = true;
|
||||
SongData.Status = SongStatus.PROCESSING;
|
||||
await SongData.save();
|
||||
}
|
||||
|
@ -158,7 +197,6 @@ App.post("/upload/audio",
|
|||
ffmpeg()
|
||||
.input(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`)
|
||||
.outputOptions([
|
||||
"-map 0",
|
||||
"-use_timeline 1",
|
||||
"-f dash"
|
||||
])
|
||||
|
|
|
@ -4,14 +4,26 @@ import { Song, SongStatus } from "../Schemas/Song";
|
|||
import { OriginalSparks } from "../Modules/FNUtil";
|
||||
import j from "joi";
|
||||
import { UserPermissions } from "../Schemas/User";
|
||||
import { AsyncFilter } from "../Modules/Extensions";
|
||||
|
||||
const App = Router();
|
||||
|
||||
App.get("/me", RequireAuthentication({ BookmarkedSongs: true, CreatedTracks: true }), async (req, res) => {
|
||||
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)
|
||||
for (const Track of ProcessingTracks) {
|
||||
console.log(Track.HasAudio, Track.HasMidi, Track.HasCover)
|
||||
if (!Track.HasAudio || !Track.HasMidi || !Track.HasCover)
|
||||
continue;
|
||||
|
||||
|
|
26
Server/package-lock.json
generated
26
Server/package-lock.json
generated
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"axios": "^1.6.5",
|
||||
"better-sqlite3": "^9.3.0",
|
||||
"colorette": "^2.0.20",
|
||||
|
@ -22,6 +23,7 @@
|
|||
"image-size": "^1.1.1",
|
||||
"joi": "^17.12.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"typeorm": "^0.3.19",
|
||||
"underscore": "^1.13.6",
|
||||
"uuid": "^9.0.1"
|
||||
|
@ -337,6 +339,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
|
||||
"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": {
|
||||
"version": "6.9.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz",
|
||||
|
@ -1756,6 +1763,25 @@
|
|||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"typescript": "^5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"axios": "^1.6.5",
|
||||
"better-sqlite3": "^9.3.0",
|
||||
"colorette": "^2.0.20",
|
||||
|
@ -54,6 +55,7 @@
|
|||
"image-size": "^1.1.1",
|
||||
"joi": "^17.12.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"typeorm": "^0.3.19",
|
||||
"underscore": "^1.13.6",
|
||||
"uuid": "^9.0.1"
|
||||
|
|
BIN
src/assets/NoCoverDetected.png
Normal file
BIN
src/assets/NoCoverDetected.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 MiB |
|
@ -2,6 +2,7 @@ import { Box, Label, Text } from "@primer/react";
|
|||
import { Divider } from "@primer/react/lib-esm/ActionList/Divider";
|
||||
import { SongStatus } from "../utils/Extensions";
|
||||
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 }) {
|
||||
function GetStatusLabel() {
|
||||
|
@ -50,7 +51,7 @@ export function Song({ data, children }: { data: any, children?: JSX.Element[] |
|
|||
|
||||
return (
|
||||
<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>
|
||||
<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>
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
|
||||
.songCategory {
|
||||
display: inline-flex;
|
||||
flex: none;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Box, Text } from "@primer/react";
|
||||
import { EULA } from "./EULA";
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<Text>Welcome to the Online Test 1</Text>
|
||||
<EULA />
|
||||
<Text>online test 2 please kill me thanks</Text>
|
||||
<Text>CLICK THE LOGIN ICON ON THE TOP RIGHT</Text>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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 { Divider } from "@primer/react/lib-esm/ActionList/Divider";
|
||||
import { PageHeader } from "@primer/react/drafts";
|
||||
|
@ -5,7 +7,6 @@ import { useContext, useEffect, useRef, useState } from "react";
|
|||
import { SiteContext } from "../utils/State";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { Song } from "../components/Song";
|
||||
import axios from "axios";
|
||||
import { toast } from "react-toastify";
|
||||
import { SongStatus } from "../utils/Extensions";
|
||||
|
||||
|
@ -82,27 +83,42 @@ export function Profile() {
|
|||
}
|
||||
</Text>
|
||||
<form method="GET" action="" ref={formRef}>
|
||||
<FormControl required={true} sx={formControlStyle}>
|
||||
<FormControl sx={formControlStyle}>
|
||||
<FormControl.Label>MIDI File (.mid)</FormControl.Label>
|
||||
<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>
|
||||
<FormControl required={true} sx={formControlStyle}>
|
||||
<FormControl sx={formControlStyle}>
|
||||
<FormControl.Label>Audio File (.m4a, .mp3, .wav)</FormControl.Label>
|
||||
<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>
|
||||
<FormControl required={true} sx={formControlStyle}>
|
||||
<FormControl sx={formControlStyle}>
|
||||
<FormControl.Label>Cover Image (.png)</FormControl.Label>
|
||||
<TextInput sx={{ width: "100%" }} type="file" />
|
||||
<FormControl.Caption>Must be a 1:1 ratio. Max: 2048x2048, min: 512x512</FormControl.Caption>
|
||||
</FormControl>
|
||||
<Button type="submit" sx={{ marginTop: 3, width: "100%" }} onClick={e => {
|
||||
<Button type="submit" sx={{ marginTop: 3, width: "100%" }} onClick={async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const Midi = (formRef.current[0] as HTMLInputElement).files![0];
|
||||
const Music = (formRef.current[1] 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>
|
||||
</form>
|
||||
</Box>
|
||||
|
|
|
@ -8,6 +8,7 @@ const formControlStyle = { paddingTop: 3 };
|
|||
|
||||
export function TrackSubmission() {
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [waiting, setWaiting] = useState<boolean>(false);
|
||||
const [Key, setKey] = useState<string>("Select a key...");
|
||||
const [Scale, setScale] = useState<string>("Select a scale...");
|
||||
const [GuitarStarterType, setGuitarStarterType] = useState<string>("Select the starter type...");
|
||||
|
@ -119,7 +120,8 @@ export function TrackSubmission() {
|
|||
<TextInput type="number" />
|
||||
<FormControl.Caption>Ranges from 0-6</FormControl.Caption>
|
||||
</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();
|
||||
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 });
|
||||
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 });
|
||||
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 });
|
||||
toast(CoverRes.status === 200 ? "Uploaded cover image successfully." : CoverRes.data, { type: CoverRes.status === 200 ? "success" : "error" });
|
||||
}}>Create</Button>
|
||||
setWaiting(false);
|
||||
toast("Finished processing song. You can now find it in your profile tab.");
|
||||
}}>{waiting ? "Please wait..." : "Create"}</Button>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user