mid is really fat like if you agree

This commit is contained in:
McMistrzYT 2024-02-04 22:28:42 +01:00
parent 8145800120
commit 883d5ca4ec
19 changed files with 305 additions and 45 deletions

View File

@ -4,6 +4,7 @@ import { Song } from "../Schemas/Song";
import { ForcedCategory } from "../Schemas/ForcedCategory";
import { User } from "../Schemas/User";
import { Rating } from "../Schemas/Rating";
import { DiscordRole } from "../Schemas/DiscordRole";
export const DBSource = new DataSource({
type: "better-sqlite3",
@ -14,7 +15,8 @@ export const DBSource = new DataSource({
Song,
ForcedCategory,
User,
Rating
Rating,
DiscordRole
/*join(__dirname, "..", "Schemas") + "\\*{.js,.ts}"*/ // does not work in prod
],
subscribers: [],

View File

@ -0,0 +1,20 @@
import { ActivityType, Client, IntentsBitField } from "discord.js";
import { Msg } from "../Modules/Logger";
import { green } from "colorette";
import { BOT_TOKEN } from "../Modules/Constants";
export const Bot: Client<true> = new Client({
intents: IntentsBitField.Flags.Guilds,
presence: {
status: "online",
activities: [
{
name: "over Partypack",
type: ActivityType.Watching
}
]
}
});
Bot.on("ready", () => Msg(`Discord bot now ready as ${green(Bot.user.username)}${green("#")}${green(Bot.user.discriminator)}`));
Bot.login(BOT_TOKEN);

View File

@ -1,17 +1,13 @@
/* eslint-disable no-case-declarations */
import { FULL_SERVER_ROOT } from "../Modules/Constants";
import j from "joi";
import { Router } from "express";
import { UserPermissions } from "../Schemas/User";
import { Song, SongStatus } from "../Schemas/Song";
import { Song } from "../Schemas/Song";
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 j from "joi";
import sizeOf from "image-size";
import { DiscordRole } from "../Schemas/DiscordRole";
import { Bot } from "../Handlers/DiscordBot";
import { DISCORD_SERVER_ID } from "../Modules/Constants";
const App = Router();
@ -28,6 +24,50 @@ App.use((req, res, next) => {
next();
});
App.post("/create/role",
ValidateBody(j.object({
ID: j.string().min(10).max(32).required(),
Comment: j.string().max(128).optional(),
PermissionLevel: j.number().valid(...(Object.values(UserPermissions).filter(x => !isNaN(Number(x))))).required()
})),
async (req, res) => {
if (!Bot.isReady())
return res.status(500).send("This Partypack instance has a misconfigured Discord bot.");
if (!Bot.guilds.cache.get(DISCORD_SERVER_ID as string)?.roles.cache.has(req.body.ID))
return res.status(404).send("This role does not exist in the Discord server.");
const Existing = await DiscordRole.findOne({ where: { ID: req.body.ID } });
if (Existing) {
Existing.GrantedPermissions = req.body.PermissionLevel as UserPermissions;
Existing.Comment = req.body.Comment ?? Existing.Comment;
await Existing.save();
return res.json(Existing.Package(true));
}
const RoleEntry = await DiscordRole.create({
ID: req.body.ID,
Comment: req.body.Comment ?? "No comment",
GrantedPermissions: req.body.PermissionLevel as UserPermissions
}).save();
res.json(RoleEntry.Package(true));
});
App.post("/delete/role",
ValidateBody(j.object({
ID: j.string().min(10).max(32).required()
})),
async (req, res) => {
const RoleData = await DiscordRole.findOne({ where: { ID: req.body.ID } });
if (!RoleData)
return res.status(404).send("This role does not exist in the database.");
await RoleData.remove();
res.send("Removed role successfully.");
})
App.get("/roles", async (_, res) => res.json((await DiscordRole.find()).map(x => x.Package(true))));
App.get("/tracks", async (_, res) => res.json((await Song.find()).map(x => x.Package(true))));
App.post("/update/discovery",

View File

@ -3,13 +3,16 @@ import jwt from "jsonwebtoken";
import qs from "querystring";
import j from "joi";
import { Response, Router } from "express";
import { BOT_TOKEN, DASHBOARD_ROOT, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_SERVER_ID, FULL_SERVER_ROOT, JWT_KEY } from "../Modules/Constants";
import { DASHBOARD_ROOT, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_SERVER_ID, FULL_SERVER_ROOT, JWT_KEY } from "../Modules/Constants";
import { User, UserPermissions } from "../Schemas/User";
import { ValidateQuery } from "../Modules/Middleware";
import { Err } from "../Modules/Logger";
import { Bot } from "../Handlers/DiscordBot";
import { Debug } from "../Modules/Logger";
import { DiscordRole } from "../Schemas/DiscordRole";
import { In } from "typeorm";
import { magenta } from "colorette";
const App = Router();
//let DiscordServerRoleMetadata;
// ? hacky, if you want, make it less hacky
async function QuickRevokeToken(res: Response, Token: string) {
@ -17,18 +20,6 @@ async function QuickRevokeToken(res: Response, Token: string) {
return res;
}
async function ReloadRoleData() {
const DRMt = await axios.get(`https://discord.com/api/guilds/${DISCORD_SERVER_ID}/roles`, { headers: { Authorization: `Bot ${BOT_TOKEN}` } });
Err(`Discord roles request failed to execute. Did you set up the .env correctly?`)
if (DRMt.status !== 200)
process.exit(-1);
//DiscordServerRoleMetadata = DRMt.data as { id: string, name: string, permissions: number }[];
}
//ReloadRoleData();
App.get("/discord/url", (_ ,res) => res.send(`https://discord.com/api/oauth2/authorize?client_id=${qs.escape(DISCORD_CLIENT_ID!)}&response_type=code&redirect_uri=${qs.escape(`${FULL_SERVER_ROOT}/api/discord`)}&scope=identify`))
App.get("/discord",
@ -51,16 +42,42 @@ async (req, res) => {
await QuickRevokeToken(res, Discord.data.access_token);
// TODO: add discord role thingy
let UserPermissionLevel = UserPermissions.User;
const AnyUserExists = await User.exists(); // automatically grant the first user on the database administrator permissions
let UserPermissionLevel = !AnyUserExists ? UserPermissions.Administrator : UserPermissions.User;
if (AnyUserExists && Bot.isReady()) {
Debug("Using Discord roles to determine user permission level since the Discord bot exists and is ready.");
const Sewer = Bot.guilds.cache.get(DISCORD_SERVER_ID as string);
const Membuh = await Sewer?.members.fetch(UserData.data.id);
if (Membuh) {
const RoulInDeightabaise = await DiscordRole.find({ where: { ID: In(Membuh.roles.cache.map(x => x.id)) }, order: { GrantedPermissions: "DESC" } });
if (RoulInDeightabaise.length > 0)
UserPermissionLevel = RoulInDeightabaise[0].GrantedPermissions;
Debug(`Detected ${magenta(RoulInDeightabaise.length)} roles that override Database permissions for user ${magenta(`@${UserData.data.username}`)}. Giving permission level ${magenta(UserPermissionLevel)}.`)
}
}
let DBUser = await User.findOne({ where: { ID: UserData.data.id } });
if (!DBUser)
DBUser = await User.create({
ID: UserData.data.id,
Username: UserData.data.username,
DisplayName: UserData.data.global_name ?? UserData.data.username,
ProfilePictureURL: `https://cdn.discordapp.com/avatars/${UserData.data.id}/${UserData.data.avatar}.webp`,
Library: [],
PermissionLevel: UserPermissionLevel
}).save();
else
{
DBUser.Username = UserData.data.username;
DBUser.DisplayName = UserData.data.global_name ?? UserData.data.username;
DBUser.ProfilePictureURL = `https://cdn.discordapp.com/avatars/${UserData.data.id}/${UserData.data.avatar}.webp`;
DBUser.PermissionLevel = UserPermissionLevel;
await DBUser.save();
}
const JWT = jwt.sign({ ID: UserData.data.id }, JWT_KEY!, { algorithm: "HS256" });
const UserDetails = Buffer.from(JSON.stringify({ ID: UserData.data.id, Username: UserData.data.username, GlobalName: UserData.data.global_name, Avatar: `https://cdn.discordapp.com/avatars/${UserData.data.id}/${UserData.data.avatar}.webp`, IsAdmin: DBUser.PermissionLevel >= UserPermissions.Administrator, Role: DBUser.PermissionLevel })).toString("hex")

View File

@ -1,9 +1,10 @@
import j from "joi";
import { Router } from "express";
import { ENVIRONMENT } from "../Modules/Constants";
import { ENVIRONMENT, JWT_KEY } from "../Modules/Constants";
import { RequireAuthentication, ValidateBody } from "../Modules/Middleware";
import { User, UserPermissions } from "../Schemas/User";
import j from "joi";
import { Song } from "../Schemas/Song";
import { sign } from "jsonwebtoken";
const App = Router();
@ -39,6 +40,12 @@ async (req, res) => {
res.json(req.user);
})
App.post("/create/auth",
ValidateBody(j.object({
ID: j.string().min(10).max(25).required()
})),
(req, res) => res.send(sign(req.body, JWT_KEY as string)));
App.get("/raw/song/:SongID",
async (req, res) => res.json(await Song.findOne({ where: { ID: req.params.SongID } })));

View File

@ -8,6 +8,8 @@ import { UserPermissions } from "../Schemas/User";
const App = Router();
App.get("/api/download/partypacker", (_, res) => res.redirect("https://cdn.discordapp.com/attachments/1202728144935583804/1203083689840607252/Partypacker_OT2.zip"))
App.get("/song/download/:InternalID/:File",
RequireAuthentication(),
async (req, res) => {
@ -60,7 +62,7 @@ async (req, res) => {
if (!/^[\w\-.]+$/g.test(req.params.File))
return res.status(400).send("File name failed validation.");
if (!req.params.File.endsWith(".m4s"))
if (!req.params.File.endsWith(".m4s") && !req.params.File.endsWith(".webm"))
return res.sendStatus(403);
if (!existsSync(`${SongData.Directory}/Chunks/${req.params.File}`))

View File

@ -199,7 +199,8 @@ App.post("/upload/audio",
.audioCodec("libopus")
.outputOptions([
"-use_timeline 1",
"-f dash"
"-f dash",
"-mapping_family 255"
])
.output(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`)
.on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`))

View File

@ -0,0 +1,21 @@
import { BaseEntity, Column, Entity, PrimaryColumn } from "typeorm";
import { UserPermissions } from "./User";
@Entity()
export class DiscordRole extends BaseEntity {
@PrimaryColumn()
ID: string;
@Column()
GrantedPermissions: UserPermissions;
@Column({ default: "No comment" })
Comment: string;
public Package(IncludeComment: boolean) {
return {
...this,
Comment: IncludeComment ? this.Comment : undefined
}
}
}

View File

@ -119,7 +119,7 @@ export class Song extends BaseEntity {
return {
...this,
Status: IncludeStatus ? this.Status : SongStatus.DEFAULT,
Author: this.Author ? this.Author.ID : undefined,
Author: this.Author ? this.Author.Package() : undefined,
Directory: undefined, // we should NOT reveal that
Midi: this.Midi ?? `${FULL_SERVER_ROOT}/song/download/${this.ID}/midi.mid`,
Cover: this.Cover ?? `${FULL_SERVER_ROOT}/song/download/${this.ID}/cover.png`

View File

@ -39,4 +39,14 @@ export class User extends BaseEntity {
@ManyToMany(() => Song, { eager: true })
@JoinTable()
BookmarkedSongs: Song[];
public Package(IncludeRatings: boolean = false, IncludeCreatedTracks: boolean = false) {
return {
...this,
Ratings: IncludeRatings ? this.Ratings : undefined,
Library: undefined,
CreatedTracks: IncludeCreatedTracks ? this.CreatedTracks : undefined,
BookmarkedSongs: undefined
}
}
}

View File

@ -3,6 +3,7 @@ config();
import "./Handlers/Database";
import "./Handlers/Server";
import "./Handlers/DiscordBot";
/*
Welcome to Mc's BasedServer template! (v1.1 - 30.12.2023 update)

View File

@ -8,9 +8,9 @@
"build:prod": "vite build",
"build:stage": "vite build --mode staging",
"win:create:prod": "mkdir \"./Out\" ; vite build ; 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 win: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:stage": "mkdir \"./Out\" && vite build --mode staging && 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 win: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:publish:prod": "npm run win: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:stage": "mkdir \"./Out\" ; vite build --mode staging ; 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": "mkdir \"./Out\" ; vite build --mode staging ; 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\" ; 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",

View File

@ -14,6 +14,7 @@ import { Tracks } from "./routes/Tracks";
import { TrackSubmission } from "./routes/TrackSubmission";
import { Profile } from "./routes/Profile";
import { NotFound } from "./routes/404";
import { Credits } from "./routes/Credits";
import { AdminHome } from "./routes/AdminHome";
import { AdminTrackList } from "./routes/AdminTrackList";
import { AdminSubmissions } from "./routes/AdminSubmissions";
@ -23,6 +24,7 @@ import merge from "deepmerge";
import "react-toastify/dist/ReactToastify.css";
import "./css/index.css";
import { FrequentlyAskedQuestions } from "./routes/FrequentlyAskedQuestions";
const DefaultTheme = merge(theme, {}); // we'll use this!! eventually!!!
@ -43,9 +45,11 @@ function App() {
{/* User-accessible routes */}
<Route path="/" element={<Home />} />
<Route path="/download" element={<Download />} />
<Route path="/faq" element={<FrequentlyAskedQuestions />} />
<Route path="/tracks" element={<Tracks />} />
<Route path="/submissions" element={<TrackSubmission />} />
<Route path="/profile" element={<Profile />} />
<Route path="/credits" element={<Credits />} />
<Route path="*" element={<NotFound />} />
{/* Staff routes */}

19
src/routes/Credits.tsx Normal file
View File

@ -0,0 +1,19 @@
import { Heading } from "@primer/react";
export function Credits() {
return (
<center>
<Heading>Partypack</Heading>
<h4>Created by</h4>
<a href="https://twitter.com/McMistrzYT">McMistrzYT</a><br />
<h4>Additional help from</h4>
<a href="https://twitter.com/KruzShady">shady</a><br />
<a href="https://twitter.com/AveryMadness">AveryMadness</a><br />
<a href="https://twitter.com/NotJulesDev">YLS-Dev</a><br />
<a href="https://twitter.com/samuels1v">Samuel</a><br />
<a href="https://github.com/McMistrzYT/Partypack/graphs/contributors">Contributors on GitHub</a>
<h4>Special thanks</h4>
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">Bryan Griffin</a><br />
</center>
)
}

View File

@ -1,6 +1,13 @@
import { Button, Heading, Text } from "@primer/react";
export function Download() {
return (
<>
<center>
<Heading>You're one step away from experiencing peak Fortnite Festival</Heading>
<Text>Click the button below to download <b>Partypacker</b> to launch into Fortnite with custom Partypack songs!</Text>
<Button onClick={() => window.open((import.meta.env.VITE_SERVER_ROOT_URL ?? "http://localhost:6677/") + "api/download/partypacker")}>Download Partypacker</Button>
</center>
</>
)
}

View File

@ -0,0 +1,46 @@
import { Box, Heading, Text } from "@primer/react";
const FAQ: { Q: string, A: string }[] = [
{
Q: "Can custom jam tracks get me banned?",
A: "No, custom songs do not interfere with Fortnite's anti-cheat and will NOT get you banned."
},
{
Q: "How do custom jam tracks work?",
A: "Using our custom launcher, Partypacker, you are able to redirect Fortnite's requests to the Partypack backend, which modifies Fortnite's tracklist. This allows us to replace existing jam tracks with completely custom jam tracks which include metadata, music, and charts."
},
{
Q: "Can I play custom jam tracks with friends?",
A: "Yes, you can! ...but they have to activate the exact same songs, replacing the exact same songs as you."
},
{
Q: "Will stats save for custom jam tracks?",
A: "Unfortunately, stats and leaderboards will not work for custom songs."
},
{
Q: "Can I play jam tracks aleady inside Fortnite for free as a custom jam track?",
A: "No, this is not currently allowed and will never be allowed on this instance."
},
{
Q: "Do you have to chart every instrument on every difficulty for the custom jam track to work?",
A: "No, you can chart as little as you want (or none at all) and the jam track will still load just fine. However, there currently is no way to disable a specific instrument or difficulty on a jam track."
},
{
Q: "Does this have a token logger or spyware hidden inside?",
A: "No! All our source code used for the server and launcher are available on McMistrzYT's GitHub. (links available on the home page)"
}
];
export function FrequentlyAskedQuestions() {
return (
<Box>
<Heading>Frequently Asked Questions</Heading>
{
FAQ.map(x => <>
<Text><b>Q:</b> {x.Q}</Text><br />
<Text><b>A:</b> {x.A}</Text><br /><br />
</>)
}
</Box>
)
}

View File

@ -1,13 +1,31 @@
import { Box, Heading, Text } from "@primer/react";
import { SignInIcon } from "@primer/octicons-react";
export function Home() {
return (
<>
<Box>
<Heading>PARTYPACK - Placeholder Place Logo Here</Heading>
<Text>Welcome to Partypack! blah blah someone please make this text for me im way too lazy to do allat</Text>
<Heading>Welcome to Partypack</Heading>
<Text>
Welcome to Partypack, the ultimate game-changing experience for Fortnite Festival! Partypack brings custom songs into the game, letting players turn it into a community driven experience!
<br />
Here, you can view tracks by other people or submit your own, read tutorials and look at the FAQ for important topics. Be sure to also read the quickstart guide to know how to get started!
<br />
Partypack was created by <a href="/credits">everyone listed here</a>. If you're interested in hosting your own instance of Partypack, be sure to check the <a href="https://github.com/McMistrzYT/Partypack">GitHub repo</a>.</Text>
<Heading>Quickstart Guide</Heading>
<Text>1. Kill yourself<br />2. Do it again</Text>
<Text>
<b>Consider watching the easier to understand, visual guide available <a href="/tutorials/Quickstart.mp4">here</a>.</b><br />
1. Join this instance's <a href="https://discord.gg/Rhd9Hq4D62">Discord server</a><br />
2. Click on the <SignInIcon size={16} /> icon in the top right<br />
3. Log in using your Discord account<br />
4. Go to <b>Tracks</b> and subscribe to tracks you like<br />
5. Click on your profile picture in the top right to access your <b>Profile</b> page<br />
6. <b>Activate</b> songs by replacing original Fortnite songs<br />
7. <b>Download</b> the Partypacker Launcher from the <b>Download</b> tab<br />
8. Run the Partypacker application, log in using your Discord account and press Launch<br />
9. If any warnings pop up, press YES on all of them for the proxy to work correctly<br />
10. Enter a Festival Main Stage match and try out your custom songs!
</Text>
</Box>
</>
)

View File

@ -1,6 +1,6 @@
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, Label, Text, TextInput } from "@primer/react"
import { Divider } from "@primer/react/lib-esm/ActionList/Divider";
import { PageHeader } from "@primer/react/drafts";
import { useContext, useEffect, useRef, useState } from "react";
@ -8,7 +8,8 @@ import { SiteContext } from "../utils/State";
import { useCookies } from "react-cookie";
import { Song } from "../components/Song";
import { toast } from "react-toastify";
import { SongStatus } from "../utils/Extensions";
import { SongStatus, UserPermissions } from "../utils/Extensions";
import { LabelColorOptions } from "@primer/react/lib-esm/Label/Label";
const formControlStyle = { paddingTop: 3 };
@ -17,6 +18,8 @@ export function Profile() {
const { state, setState } = useContext(SiteContext);
const [, , removeCookie] = useCookies();
const [isActivateDialogOpen, setIsActivateDialogOpen] = useState<boolean>(false);
const [variant, setVariant] = useState<LabelColorOptions>("success");
const [labelText, setLabelText] = useState<string>("");
const [librarySongs, setLibrarySongs] = useState<unknown[]>([]);
const [bookmarkedSongs, setBookmarkedSongs] = useState<unknown[]>([]);
const [draftsSongs, setDraftsSongs] = useState<unknown[]>([]);
@ -25,6 +28,44 @@ export function Profile() {
const [isUpdateDialogOpen, setIsUpdateDialogOpen] = useState<boolean>(false);
const [updating, setUpdating] = useState<unknown>({});
useEffect(() => {
if (state.UserDetails === undefined)
return;
let Variant: LabelColorOptions = "default";
let LabelText: string = "";
switch (state.UserDetails.Role) {
case UserPermissions.User:
Variant = "secondary";
LabelText = "User";
break;
case UserPermissions.VerifiedUser:
Variant = "success";
LabelText = "Verified Track Creator";
break;
case UserPermissions.TrackVerifier:
Variant = "done";
LabelText = "Track Verifier";
break;
case UserPermissions.Moderator:
Variant = "accent";
LabelText = "Moderator";
break;
case UserPermissions.Administrator:
Variant = "danger";
LabelText = "Administrator";
break;
}
setVariant(Variant);
setLabelText(LabelText);
}, [state.UserDetails?.Role])
useEffect(() => {
(async () => {
const Data = await axios.get("/api/library/me");
@ -63,6 +104,7 @@ export function Profile() {
</PageHeader.LeadingVisual>
<PageHeader.Title>
{state.UserDetails.GlobalName} (@{state.UserDetails.Username})
<Label sx={{ alignSelf: "center", marginLeft: 2 }} size="large" variant={variant}>{labelText}</Label>
</PageHeader.Title>
<PageHeader.Actions>
<Button size="large" variant="danger" onClick={() => { removeCookie("UserDetails"); removeCookie("Token"); setState({ ...state, UserDetails: null }); window.location.assign("/") }}>Log out</Button>

View File

@ -9,8 +9,8 @@ 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 [Key, setKey] = useState<string>("Select key...");
const [Scale, setScale] = useState<string>("Select mode...");
const [GuitarStarterType, setGuitarStarterType] = useState<string>("Select the starter type...");
return (
@ -61,7 +61,7 @@ export function TrackSubmission() {
</ActionMenu>
</FormControl>
<FormControl required={true} sx={formControlStyle}>
<FormControl.Label>Scale</FormControl.Label>
<FormControl.Label>Mode</FormControl.Label>
<ActionMenu>
<ActionMenu.Button>{Scale}</ActionMenu.Button>
<ActionMenu.Overlay width="medium">
@ -126,7 +126,7 @@ export function TrackSubmission() {
console.log(formRef);
if (formRef.current == null)
return;
return setWaiting(false);
const Name = (formRef.current[0] as HTMLInputElement).value;
const ArtistName = (formRef.current[1] as HTMLInputElement).value;
@ -162,14 +162,17 @@ export function TrackSubmission() {
};
if (Object.values(B).includes(NaN) || Object.values(B).includes(null) || Object.values(B).includes(undefined))
{
setWaiting(false);
return toast("One or more required fields missing.", { type: "error" });
}
const SongData = await axios.post("/api/drafts/create", B);
toast(SongData.data, { type: SongData.status === 200 ? "success" : "error" });
if (SongData.status !== 200)
return;
return setWaiting(false);
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" });