From 017b61976683bc0333b31bee220284d72db486ee Mon Sep 17 00:00:00 2001 From: McMistrzYT <56406996+McMistrzYT@users.noreply.github.com> Date: Sun, 28 Jan 2024 22:02:29 +0100 Subject: [PATCH] raah --- Server/Source/Routes/Admin.ts | 52 +++++++++++++++++++------------ Server/Source/Routes/Discovery.ts | 2 +- Server/Source/Routes/Downloads.ts | 17 ++++++++-- Server/Source/Routes/Drafting.ts | 32 ++++++++++++------- Server/Source/Routes/Library.ts | 19 ++++++++--- Server/Source/Schemas/Song.ts | 3 ++ package.json | 14 ++++++--- src/components/Song.tsx | 5 ++- src/routes/Profile.tsx | 27 ++++++++++++++-- 9 files changed, 124 insertions(+), 47 deletions(-) diff --git a/Server/Source/Routes/Admin.ts b/Server/Source/Routes/Admin.ts index 49d3217..0a06281 100644 --- a/Server/Source/Routes/Admin.ts +++ b/Server/Source/Routes/Admin.ts @@ -7,8 +7,9 @@ import { RequireAuthentication, ValidateBody } from "../Modules/Middleware"; import { writeFileSync } from "fs"; import { ForcedCategory } from "../Schemas/ForcedCategory"; import { fromBuffer } from "file-type"; +import { Debug } from "../Modules/Logger"; +import { magenta } from "colorette"; import ffmpeg from "fluent-ffmpeg"; -import exif from "exif-reader"; import j from "joi"; const App = Router(); @@ -75,26 +76,37 @@ async (req, res) => { }); App.post("/upload/audio", -ValidateBody(j.object({ - Data: j.string().hex().required(), - TargetSong: j.string().uuid().required() -})), -async (req, res) => { - const Decoded = Buffer.from(req.body.Data, "hex"); - const ext = (await fromBuffer(Decoded))!.ext; + RequireAuthentication(), + ValidateBody(j.object({ + Data: j.string().hex().required(), + TargetSong: j.string().uuid().required() + })), + async (req, res) => { + const Decoded = Buffer.from(req.body.Data, "hex"); + const ext = (await fromBuffer(Decoded))!.ext; - if (!["mp3", "m4a", "ogg", "wav"].includes(ext)) - return res.status(404).send("Invalid audio file. (supported: mp3, m4a, ogg, wav)"); + if (!["mp3", "m4a", "ogg", "wav"].includes(ext)) + return res.status(404).send("Invalid audio file. (supported: mp3, m4a, ogg, wav)"); - if (!await Song.exists({ where: { ID: req.body.TargetSong } })) - return res.status(404).send("The song you're trying to upload audio for does not exist."); + if (!await Song.exists({ where: { ID: req.body.TargetSong } })) + return res.status(404).send("The song you're trying to upload audio for does not exist."); - // TODO: implement checks for this - writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`, Decoded); + await writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`, Decoded); + ffmpeg() + .input(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`) + .outputOptions([ + "-map 0", + "-use_timeline 1", + "-f dash" + ]) + .output(`./Saved/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`) + .on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`)) + .on("end", () => Debug("Ffmpeg finished running")) + .on("error", (e, stdout, stderr) => { console.error(e); console.log(stdout); console.error(stderr); }) + .run(); - ffmpeg() - .input("") -}) + res.send("ffmpeg now running on song."); + }); App.post("/upload/cover", ValidateBody(j.object({ @@ -106,13 +118,13 @@ async (req, res) => { const ext = (await fromBuffer(Decoded))!.ext; if (ext !== "png") - return res.status(404).send("Invalid image file. (supported: png)"); + return res.status(400).send("Invalid image file. (supported: png)"); if (!await Song.exists({ where: { ID: req.body.TargetSong } })) return res.status(404).send("The song you're trying to upload a cover for does not exist."); try { - const ImageMetadata = exif(Decoded); + /*const ImageMetadata = exif(Decoded); if (!ImageMetadata.Image?.ImageWidth || !ImageMetadata.Image?.ImageLength) throw new Error("Invalid image file."); @@ -120,7 +132,7 @@ async (req, res) => { return res.status(400).send("Image must have a 1:1 ratio."); if (ImageMetadata.Image.ImageWidth < 512 || ImageMetadata.Image.ImageWidth > 2048) - return res.status(400).send("Image cannot be smaller than 512 pixels and larger than 2048 pixels."); + return res.status(400).send("Image cannot be smaller than 512 pixels and larger than 2048 pixels.");*/ } catch (err) { console.error(err) return res.status(400).send("Invalid image file."); diff --git a/Server/Source/Routes/Discovery.ts b/Server/Source/Routes/Discovery.ts index 11ae71a..8c68f03 100644 --- a/Server/Source/Routes/Discovery.ts +++ b/Server/Source/Routes/Discovery.ts @@ -9,7 +9,7 @@ App.get("/", async (req, res) => { const New = { ID: "new", Header: "Recently added", - Songs: (await Song.find({ take: 10, order: { CreationDate: "DESC" } })).map(x => x.Package()), + Songs: (await Song.find({ where: { IsDraft: false }, take: 10, order: { CreationDate: "DESC" } })).map(x => x.Package()), Priority: 100, Custom: false } diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts index 500ce9c..c2f3b34 100644 --- a/Server/Source/Routes/Downloads.ts +++ b/Server/Source/Routes/Downloads.ts @@ -3,15 +3,23 @@ import { existsSync, readFileSync } from "fs"; import { FULL_SERVER_ROOT } from "../Modules/Constants"; import { CreateBlurl } from "../Modules/BLURL"; import { Song } from "../Schemas/Song"; +import { RequireAuthentication } from "../Modules/Middleware"; const App = Router(); -App.get("/song/download/:InternalID/:File", async (req, res) => { +App.get("/song/download/:InternalID/:File", +RequireAuthentication(), +async (req, res) => { //const Song = AvailableFestivalSongs.find(x => x.UUID === req.params.SongUUID); - const SongData = await Song.findOne({ where: { ID: req.params.InternalID } }); + const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); if (!SongData) return res.status(404).json({ errorMessage: "Song not found." }); + console.log(SongData); + + if (SongData.IsDraft && SongData.Author.ID !== req.user!.ID) + return res.status(403).json({ errorMessage: "You cannot use this track, because it's a draft." }); + const BaseURL = `${FULL_SERVER_ROOT}/song/download/${SongData.ID}/`; switch (req.params.File.toLowerCase()) { case "master.blurl": @@ -64,10 +72,13 @@ App.get("/song/download/:InternalID/:File", async (req, res) => { }); App.get("/:InternalID", async (req, res, next) => { - const SongData = await Song.findOne({ where: { ID: req.params.InternalID } }); + const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); if (!SongData) return next(); // trust me bro + if (SongData.IsDraft && SongData.Author.ID !== req.user!.ID) + return res.status(403).json({ errorMessage: "You cannot use this track, because it's a draft." }); + const BaseURL = `${FULL_SERVER_ROOT}/song/download/${SongData.ID}/`; res.set("content-type", "application/json"); res.json({ diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index fad8f8d..7a39911 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -7,8 +7,9 @@ import { Song } from "../Schemas/Song"; import { Debug } from "../Modules/Logger"; import { magenta } from "colorette"; import { fromBuffer } from "file-type"; -import { writeFileSync } from "fs"; +import { rmSync, writeFileSync } from "fs"; import { FULL_SERVER_ROOT } from "../Modules/Constants"; +import { UserPermissions } from "../Schemas/User"; const App = Router(); @@ -32,7 +33,8 @@ App.post("/create", async (req, res) => { const SongData = await Song.create({ ...req.body, - IsDraft: true + IsDraft: true, + Author: req.user! }).save(); Debug(`New draft created by ${magenta(req.user!.ID!)} as ${magenta(`${SongData.ArtistName} - ${SongData.Name}`)}`) @@ -71,11 +73,15 @@ App.post("/upload/cover", if (ext !== "png") return res.status(404).send("Invalid image file. (supported: png)"); - if (!await Song.exists({ where: { ID: req.body.TargetSong } })) + const SongData = await Song.findOne({ where: { ID: req.body.TargetSong }, relations: { Author: true } }) + if (!SongData) return res.status(404).send("The song you're trying to upload a cover for does not exist."); + 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."); + try { - const ImageMetadata = exif(Decoded); + /*const ImageMetadata = exif(Decoded); if (!ImageMetadata.Image?.ImageWidth || !ImageMetadata.Image?.ImageLength) throw new Error("Invalid image file."); @@ -83,7 +89,7 @@ App.post("/upload/cover", return res.status(400).send("Image must have a 1:1 ratio."); if (ImageMetadata.Image.ImageWidth < 512 || ImageMetadata.Image.ImageWidth > 2048) - return res.status(400).send("Image cannot be smaller than 512 pixels and larger than 2048 pixels."); + return res.status(400).send("Image cannot be smaller than 512 pixels and larger than 2048 pixels.");*/ } catch (err) { console.error(err) return res.status(400).send("Invalid image file."); @@ -112,19 +118,23 @@ App.post("/upload/audio", await writeFileSync(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`, Decoded); ffmpeg() .input(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`) - .inputOptions(["-re"]) .outputOptions([ "-map 0", - "-c:a aac", - "-ar:a:0 48000", "-use_timeline 1", - "-adaptation_sets \"id=0,streams=a\"", "-f dash" ]) .output(`./Saved/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`) .on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`)) - .on("end", () => Debug("Ffmpeg finished running")) - .on("error", (e, stdout, stderr) => { console.error(e); console.log(stdout); console.error(stderr); }) + .on("end", () => { + Debug("Ffmpeg finished running"); + rmSync(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`); + }) + .on("error", (e, stdout, stderr) => { + console.error(e); + console.log(stdout); + console.error(stderr); + rmSync(`./Saved/Songs/${req.body.TargetSong}/Audio.${ext}`); + }) .run(); res.send("ffmpeg now running on song."); diff --git a/Server/Source/Routes/Library.ts b/Server/Source/Routes/Library.ts index 318194f..8cb7684 100644 --- a/Server/Source/Routes/Library.ts +++ b/Server/Source/Routes/Library.ts @@ -6,9 +6,10 @@ import j from "joi"; const App = Router(); -App.get("/me", RequireAuthentication({ BookmarkedSongs: true }), (req, res) => { +App.get("/me", RequireAuthentication({ BookmarkedSongs: true, CreatedTracks: true }), (req, res) => { res.json({ Bookmarks: req.user?.BookmarkedSongs.map(x => x.Package()), + Created: req.user?.CreatedTracks.map(x => x.Package()), Library: req.user?.Library }) }) @@ -60,10 +61,13 @@ async (req, res) => { if (req.user?.BookmarkedSongs.findIndex(x => x.ID.toLowerCase() === req.body.SongID.toLowerCase()) !== -1) return res.status(400).json({ errorMessage: "You're already subscribed to this song." }); - const SongData = await Song.findOne({ where: { ID: req.body.SongID } }); + const SongData = await Song.findOne({ where: { ID: req.body.SongID }, relations: { Author: true } }); if (!SongData) return res.status(404).json({ errorMessage: "Provided song doesn't exist." }); + if (SongData.IsDraft && SongData.Author.ID !== req.user.ID) + return res.status(403).json({ errorMessage: "You cannot subscribe to this track, because it's a draft." }); + req.user?.BookmarkedSongs.push(SongData); req.user?.save(); @@ -86,10 +90,15 @@ async (req, res) => { res.json(req.user?.BookmarkedSongs.map(x => x.Package())); }) -App.get("/song/data/:InternalID", async (req, res) => { - const SongData = await Song.findOne({ where: { ID: req.params.InternalID } }); +App.get("/song/data/:InternalID", +RequireAuthentication(), +async (req, res) => { + const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); if (!SongData) - return res.status(404).json({ errorMessage: "Song not found." }); + return res.status(404).json({ errorMessage: "Provided song doesn't exist." }); + + if (SongData.IsDraft && SongData.Author.ID !== req.user!.ID) + return res.status(403).json({ errorMessage: "You cannot use this track, because it's a draft." }); res.json(SongData.Package()); }) diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts index 3c64aa2..c30a673 100644 --- a/Server/Source/Schemas/Song.ts +++ b/Server/Source/Schemas/Song.ts @@ -65,6 +65,9 @@ export class Song extends BaseEntity { @Column() IsDraft: boolean; + @Column({ default: false }) + DraftAwaitingReview: boolean; + @Column() CreationDate: Date; diff --git a/package.json b/package.json index 8f99ac8..83b155a 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,18 @@ "build:prod": "vite build", "build:stage": "vite build --mode staging", - "create:prod": "mkdir \"./Out\" && npm run build:prod && move \"./dist\" \"./Out/dist\" && cd \"Server\" && tsc && cd .. && copy \"./Server/.env.prod\" \"./Out/.env\" && copy \"./Server/package.json\" \"./Out/package.json\" && copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", - "publish:prod": "npm run create:prod && ssh partypack \"cd /home/PartypackProd; rm -rf ./Out\" && scp -r \"./Out\" partypack:/home/PartypackProd && ssh partypack \"cd /home/PartypackProd/Out && npm i && pm2 restart PartypackProd --update-env\" && rmdir \"./Out\"", + "win:create:prod": "mkdir \"./Out\" && npm run build:prod && move \"./dist\" \"./Out/dist\" && cd \"Server\" && tsc && cd .. && copy \"./Server/.env.prod\" \"./Out/.env\" && copy \"./Server/package.json\" \"./Out/package.json\" && copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", + "win:publish:prod": "npm run create:prod && ssh partypack \"cd /home/PartypackProd; rm -rf ./Out\" && scp -r \"./Out\" partypack:/home/PartypackProd && ssh partypack \"cd /home/PartypackProd/Out && npm i && pm2 restart PartypackProd --update-env\" && rmdir \"./Out\"", - "create:stage": "mkdir \"./Out\" && npm run build:stage && move \"./dist\" \"./Out/dist\" && cd \"Server\" && tsc && cd .. && copy \"./Server/.env.staging\" \"./Out/.env\" && copy \"./Server/package.json\" \"./Out/package.json\" && copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", - "publish:stage": "npm run create:stage && ssh partypack \"cd /home/PartypackStage; rm -rf ./Out\" && scp -r ./Out partypack:/home/PartypackStage && ssh partypack \"cd /home/PartypackStage/Out && npm i && pm2 restart PartypackStage --update-env\" && rmdir \"./Out\"", + "win:create:stage": "mkdir \"./Out\" && npm run build:stage && move \"./dist\" \"./Out/dist\" && cd \"Server\" && tsc && cd .. && copy \"./Server/.env.staging\" \"./Out/.env\" && copy \"./Server/package.json\" \"./Out/package.json\" && copy \"./Server/package-lock.json\" \"./Out/package-lock.json\"", + "win:publish:stage": "npm run create:stage && ssh partypack \"cd /home/PartypackStage; rm -rf ./Out\" && scp -r ./Out partypack:/home/PartypackStage && ssh partypack \"cd /home/PartypackStage/Out && npm i && pm2 restart PartypackStage --update-env\" && rmdir \"./Out\"", + "create:prod": "mkdir ./Out && npm run build:prod && mv ./dist ./Out/dist && cd Server && tsc && cd .. && cp ./Server/.env.prod ./Out/.env && cp ./Server/package.json ./Out && cp ./Server/package-lock.json ./Out", + "publish:prod": "npm run create:prod && ssh partypack \"cd /home/PartypackProd; rm -rf ./Out\" && scp -r ./Out partypack:/home/PartypackProd && ssh partypack \"cd /home/PartypackProd/Out && npm i && pm2 restart PartypackProd --update-env\" && rm -rf ./Out", + + "create:stage": "mkdir ./Out && npm run build:stage && mv ./dist ./Out/dist && cd Server && tsc && cd .. && cp ./Server/.env.staging ./Out/.env && cp ./Server/package.json ./Out && cp ./Server/package-lock.json ./Out", + "publish:stage": "npm run create:stage && ssh partypack \"cd /home/PartypackStage; rm -rf ./Out\" && scp -r ./Out partypack:/home/PartypackStage && ssh partypack \"cd /home/PartypackStage/Out && npm i && pm2 restart PartypackStage --update-env\" && rm -rf ./Out", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "dev:all": "start cmd.exe /k \"cd ./Server && npm run dev:watch\" && vite" diff --git a/src/components/Song.tsx b/src/components/Song.tsx index 0698314..91b735d 100644 --- a/src/components/Song.tsx +++ b/src/components/Song.tsx @@ -1,4 +1,4 @@ -import { Box, Text } from "@primer/react"; +import { Box, Label, Text } from "@primer/react"; import { Divider } from "@primer/react/lib-esm/ActionList/Divider"; export function Song({ data, children }: { data: any, children?: JSX.Element[] | JSX.Element | string }) { @@ -8,6 +8,9 @@ export function Song({ data, children }: { data: any, children?: JSX.Element[] |
{data.ArtistName} {data.Name} + { + data.IsDraft ? : <> + } { children ? : <> } diff --git a/src/routes/Profile.tsx b/src/routes/Profile.tsx index 5a7d201..338285f 100644 --- a/src/routes/Profile.tsx +++ b/src/routes/Profile.tsx @@ -15,6 +15,7 @@ export function Profile() { const [isActivateDialogOpen, setIsActivateDialogOpen] = useState(false); const [librarySongs, setLibrarySongs] = useState([]); const [bookmarkedSongs, setBookmarkedSongs] = useState([]); + const [draftsSongs, setDraftsSongs] = useState([]); const [availableOverrides, setAvailableOverrides] = useState<{ Name: string, Template: string }[]>([]); const [overriding, setOverriding] = useState({}); const navigate = useNavigate(); @@ -28,9 +29,9 @@ export function Profile() { return toast("An error has occured while getting your library!", { type: "error" }); const LibSongs = (await Promise.all(Data.data.Library.map((x: { SongID: string; }) => axios.get(`/api/library/song/data/${x.SongID}`)))).map(x => { return { ...x.data, Override: Data.data.Library.find((y: { SongID: string; }) => y.SongID === x.data.ID).Overriding } }); - const BookSongs = (await Promise.all(Data.data.Bookmarks.map((x: { ID: string; }) => axios.get(`/api/library/song/data/${x.ID}`)))).map(x => x.data); setLibrarySongs(LibSongs); - setBookmarkedSongs(BookSongs); + setBookmarkedSongs(Data.data.Bookmarks); + setDraftsSongs(Data.data.Created); setAvailableOverrides(Overrides.data); })(); }, []); @@ -126,6 +127,28 @@ export function Profile() { : You have no bookmarked songs. } + My Drafts & Published Songs + + { + draftsSongs.length >= 1 ? + draftsSongs.map(x => { + return + + + + ; + }) + : You have no bookmarked songs. + } + : <> You are not logged in.
Log in using the button in the top right.