mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-22 01:29:22 +00:00
327 lines
8 KiB
TypeScript
327 lines
8 KiB
TypeScript
import { Router } from "express";
|
|
import asyncHandler from "express-async-handler";
|
|
import SkinModel from "../data/SkinModel";
|
|
import * as Skins from "../data/skins";
|
|
import {
|
|
DISCORD_CLIENT_ID,
|
|
DISCORD_REDIRECT_URL,
|
|
LOGIN_REDIRECT_URL,
|
|
} from "../config";
|
|
import S3 from "../s3";
|
|
import LRU from "lru-cache";
|
|
import { MuseumPage } from "../data/skins";
|
|
import { processUserUploads } from "./processUserUploads";
|
|
import { auth } from "./auth";
|
|
import * as Parallel from "async-parallel";
|
|
|
|
const router = Router();
|
|
|
|
const options = {
|
|
max: 100,
|
|
maxAge: 1000 * 60 * 60,
|
|
};
|
|
let skinCount: number | null = null;
|
|
const cache = new LRU<string, MuseumPage>(options);
|
|
|
|
// Purposefully REST
|
|
router.get(
|
|
"/auth/",
|
|
asyncHandler(async (req, res) => {
|
|
res.redirect(
|
|
302,
|
|
`https://discord.com/api/oauth2/authorize?client_id=${encodeURIComponent(
|
|
DISCORD_CLIENT_ID
|
|
)}&redirect_uri=${encodeURIComponent(
|
|
DISCORD_REDIRECT_URL
|
|
)}&response_type=code&scope=identify%20guilds`
|
|
);
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/authed/",
|
|
asyncHandler(async (req, res) => {
|
|
res.json({ username: req.ctx.username });
|
|
})
|
|
);
|
|
|
|
// Purposefully REST
|
|
router.get(
|
|
"/auth/discord",
|
|
asyncHandler(async (req, res) => {
|
|
const code = req.query.code as string | undefined;
|
|
|
|
if (code == null) {
|
|
res.status(400).send({ message: "Expected to get a code" });
|
|
return;
|
|
}
|
|
const username = await auth(code);
|
|
if (username == null) {
|
|
res.status(400).send({ message: "Invalid code" });
|
|
return;
|
|
}
|
|
req.session.username = username;
|
|
|
|
// TODO: What about dev?
|
|
res.redirect(302, LOGIN_REDIRECT_URL);
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/skins/",
|
|
asyncHandler(async (req, res) => {
|
|
if (skinCount == null) {
|
|
skinCount = await Skins.getClassicSkinCount();
|
|
}
|
|
const { offset = 0, first = 100 } = req.query;
|
|
const key = req.originalUrl;
|
|
const cached = cache.get(key);
|
|
if (cached != null) {
|
|
req.log(`Cache hit for ${key}`);
|
|
res.json({ skinCount, skins: cached });
|
|
return;
|
|
}
|
|
req.log(`Getting offset: ${offset}, first: ${first}`);
|
|
|
|
const start = Date.now();
|
|
const skins = await Skins.getMuseumPage({
|
|
offset: Number(offset),
|
|
first: Number(first),
|
|
});
|
|
req.log(`Query took ${(Date.now() - start) / 1000}`);
|
|
req.log(`Cache set for ${key}`);
|
|
cache.set(key, skins);
|
|
res.json({ skinCount, skins });
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.post(
|
|
"/skins/get_upload_urls",
|
|
asyncHandler(async (req, res) => {
|
|
const payload = req.body.skins as { [md5: string]: string };
|
|
const missing = {};
|
|
await Parallel.each(
|
|
Object.entries(payload),
|
|
async ([md5, filename]) => {
|
|
if (!(await SkinModel.exists(req.ctx, md5))) {
|
|
const id = await Skins.recordUserUploadRequest(md5, filename);
|
|
const url = S3.getSkinUploadUrl(md5, id);
|
|
missing[md5] = { id, url };
|
|
}
|
|
},
|
|
5
|
|
);
|
|
res.json(missing);
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.post(
|
|
"/feedback",
|
|
asyncHandler(async (req, res) => {
|
|
const payload = req.body as {
|
|
email?: string;
|
|
message: string;
|
|
url?: string;
|
|
};
|
|
req.notify({
|
|
type: "GOT_FEEDBACK",
|
|
url: payload.url,
|
|
message: payload.message,
|
|
email: payload.email,
|
|
});
|
|
res.json({ message: "sent" });
|
|
})
|
|
);
|
|
|
|
// @deprecate Use GraphQL
|
|
router.post(
|
|
"/skins/status",
|
|
asyncHandler(async (req, res) => {
|
|
const statuses = await Skins.getUploadStatuses(req.body.hashes);
|
|
res.json(statuses);
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/skins/:md5",
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
const skin = await SkinModel.fromMd5(req.ctx, md5);
|
|
if (skin == null) {
|
|
req.log(`Details for hash "${md5}" NOT FOUND`);
|
|
res.status(404).json();
|
|
return;
|
|
}
|
|
res.json({
|
|
md5: skin.getMd5(),
|
|
nsfw: await skin.getIsNsfw(),
|
|
fileName: await skin.getFileName(),
|
|
});
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/skins/:md5/metadata",
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
const skin = await SkinModel.fromMd5(req.ctx, md5);
|
|
if (skin == null) {
|
|
req.log(`Details for hash "${md5}" NOT FOUND`);
|
|
res.status(404).json();
|
|
return;
|
|
}
|
|
res.json({
|
|
md5: skin.getMd5(),
|
|
nsfw: await skin.getIsNsfw(),
|
|
fileName: await skin.getFileName(),
|
|
readme: skin.getReadme(),
|
|
});
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/skins/:md5/debug",
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
const skin = await SkinModel.fromMd5(req.ctx, md5);
|
|
if (skin == null) {
|
|
req.log(`Details for hash "${md5}" NOT FOUND`);
|
|
res.status(404).json();
|
|
return;
|
|
}
|
|
res.json(await skin.debug());
|
|
})
|
|
);
|
|
|
|
function requireAuthed(req, res, next) {
|
|
if (!req.ctx.authed()) {
|
|
res.status(403);
|
|
res.send({ message: "You must be logged in" });
|
|
} else {
|
|
next();
|
|
}
|
|
}
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/to_review",
|
|
requireAuthed,
|
|
asyncHandler(async (req, res) => {
|
|
const { filename, md5 } = await Skins.getSkinToReview();
|
|
res.json({ filename, md5 });
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.post(
|
|
"/skins/:md5/reject",
|
|
requireAuthed,
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
req.log(`Rejecting skin with hash "${md5}"`);
|
|
const skin = await SkinModel.fromMd5(req.ctx, md5);
|
|
if (skin == null) {
|
|
res.status(404).send("Skin not found");
|
|
return;
|
|
}
|
|
await Skins.reject(req.ctx, md5);
|
|
req.notify({ type: "REJECTED_SKIN", md5 });
|
|
res.send({ message: "The skin has been rejected." });
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.post(
|
|
"/skins/:md5/approve",
|
|
requireAuthed,
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
req.log(`Approving skin with hash "${md5}"`);
|
|
const skin = await SkinModel.fromMd5(req.ctx, md5);
|
|
if (skin == null) {
|
|
res.status(404).send("Skin not found");
|
|
return;
|
|
}
|
|
await Skins.approve(req.ctx, md5);
|
|
req.notify({ type: "APPROVED_SKIN", md5 });
|
|
res.send({ message: "The skin has been approved." });
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
// Unlike /report, this marks the skin NSFW right away without sending to
|
|
// Discord. Because of this, it requires auth.
|
|
router.post(
|
|
"/skins/:md5/nsfw",
|
|
requireAuthed,
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
req.log(`Approving skin with hash "${md5}"`);
|
|
const skin = await SkinModel.fromMd5(req.ctx, md5);
|
|
if (skin == null) {
|
|
res.status(404).send("Skin not found");
|
|
return;
|
|
}
|
|
await Skins.markAsNSFW(req.ctx, md5);
|
|
req.notify({ type: "MARKED_SKIN_NSFW", md5 });
|
|
res.send({ message: "The skin has been marked as NSFW." });
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.post(
|
|
"/skins/:md5/report",
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
req.log(`Reporting skin with hash "${md5}"`);
|
|
// Blow up if there is no skin with this hash
|
|
await SkinModel.fromMd5Assert(req.ctx, md5);
|
|
req.notify({ type: "REVIEW_REQUESTED", md5 });
|
|
res.send("The skin has been reported and will be reviewed shortly.");
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
// User reports that they uploaded a skin
|
|
router.post(
|
|
"/skins/:md5/uploaded",
|
|
asyncHandler(async (req, res) => {
|
|
const { md5 } = req.params;
|
|
const id = req.query.id as string;
|
|
if (id == null) {
|
|
throw new Error("Missing upload id");
|
|
}
|
|
// TODO: Validate md5 and id;
|
|
await Skins.recordUserUploadComplete(md5, id);
|
|
// Don't await, just kick off the task.
|
|
processUserUploads(req.notify);
|
|
res.json({ done: true });
|
|
})
|
|
);
|
|
|
|
// @deprecated Use GraphQL
|
|
router.get(
|
|
"/approved",
|
|
asyncHandler(async (req, res) => {
|
|
const approved = await Skins.getAllApproved();
|
|
res.json(approved);
|
|
})
|
|
);
|
|
|
|
// @deprecated Special purpose URL
|
|
router.get(
|
|
"/stylegan.json",
|
|
asyncHandler(async (req, res) => {
|
|
const images = await Skins.getAllClassicScreenshotUrls();
|
|
res.json(images);
|
|
})
|
|
);
|
|
|
|
export default router;
|