Allow skins to be marked as NSFW via API

This commit is contained in:
Jordan Eldredge 2020-11-30 22:00:38 -05:00
parent 929aef926b
commit 915ba85c8a
9 changed files with 86 additions and 25 deletions

View file

@ -0,0 +1,3 @@
export const searchIndex = {
partialUpdateObjects: jest.fn(),
};

View file

@ -34,37 +34,44 @@ export default class DiscordEventHandler {
await this.requestReview(action.md5, ctx);
break;
case "APPROVED_SKIN":
await this.reportApproved(action.md5, ctx);
await this.reportSkin(
action.md5,
ctx,
(filename) => `Approved by ${ctx.username}: ${filename}`
);
break;
case "REJECTED_SKIN":
await this.reportRejected(action.md5, ctx);
await this.reportSkin(
action.md5,
ctx,
(filename) => `Rejected by ${ctx.username}: ${filename}`
);
break;
case "MARKED_SKIN_NSFW":
await this.reportSkin(
action.md5,
ctx,
(filename) => `Marked NSFW by ${ctx.username}: ${filename}`,
Config.NSFW_SKIN_CHANNEL_ID
);
break;
}
}
async reportApproved(md5: string, ctx: UserContext) {
async reportSkin(
md5: string,
ctx: UserContext,
getFilename: (filename: string) => string,
channelId = Config.SKIN_REVIEW_CHANNEL_ID
) {
const skin = await SkinModel.fromMd5(ctx, md5);
if (skin == null) {
return;
}
const dest = await this.getChannel(Config.SKIN_REVIEW_CHANNEL_ID);
const dest = await this.getChannel(channelId);
await DiscordUtils.postSkin({
md5,
title: (filename) => `Approved by ${ctx.username}: ${filename}`,
dest,
});
}
async reportRejected(md5: string, ctx: UserContext) {
console.log("Report rejected");
const skin = await SkinModel.fromMd5(ctx, md5);
if (skin == null) {
return;
}
const dest = await this.getChannel(Config.SKIN_REVIEW_CHANNEL_ID);
await DiscordUtils.postSkin({
md5,
title: (filename) => `Rejected by ${ctx.username}: ${filename}`,
title: getFilename,
dest,
});
}

View file

@ -7,7 +7,9 @@ import * as S3 from "../../s3";
import * as Auth from "../auth";
import { processUserUploads } from "../processUserUploads";
import UserContext from "../../data/UserContext";
import { searchIndex } from "../../algolia";
jest.mock("../../s3");
jest.mock("../../algolia");
jest.mock("../processUserUploads");
jest.mock("../auth");
@ -155,7 +157,7 @@ test("/skins/a_fake_md5/approve", async () => {
type: "APPROVED_SKIN",
md5: "a_fake_md5",
});
expect(body).toEqual({}); // TODO: Where does the text response go?
expect(body).toEqual({ message: "The skin has been approved." });
const skin = await SkinModel.fromMd5(ctx, "a_fake_md5");
expect(await skin?.getTweetStatus()).toEqual("APPROVED");
@ -195,7 +197,7 @@ test("/skins/a_fake_md5/reject", async () => {
type: "REJECTED_SKIN",
md5: "a_fake_md5",
});
expect(body).toEqual({}); // TODO: Where does the text response go?
expect(body).toEqual({ message: "The skin has been rejected." }); // TODO: Where does the text response go?
const skin = await SkinModel.fromMd5(ctx, "a_fake_md5");
expect(await skin?.getTweetStatus()).toEqual("REJECTED");
@ -210,6 +212,24 @@ test("/skins/a_md5_that_does_not_exist/reject (404)", async () => {
expect(handler).not.toHaveBeenCalled();
});
test.only("/skins/a_fake_md5/nsfw", async () => {
const ctx = new UserContext();
const { body } = await request(app)
.post("/skins/a_fake_md5/nsfw")
.expect(200);
expect(handler).toHaveBeenCalledWith({
type: "MARKED_SKIN_NSFW",
md5: "a_fake_md5",
});
expect(searchIndex.partialUpdateObjects).toHaveBeenCalledWith([
{ nsfw: true, objectID: "a_fake_md5" },
]);
expect(body).toEqual({ message: "The skin has been marked as NSFW." });
const skin = await SkinModel.fromMd5(ctx, "a_fake_md5");
expect(await skin?.getTweetStatus()).toEqual("NSFW");
});
// TODO: Actually upload some skins?
test("/skins/status", async () => {
const { body } = await request(app)

View file

@ -14,6 +14,7 @@ export type ApiAction =
| { type: "REVIEW_REQUESTED"; md5: string }
| { type: "REJECTED_SKIN"; md5: string }
| { type: "APPROVED_SKIN"; md5: string }
| { type: "MARKED_SKIN_NSFW"; md5: string }
| { type: "SKIN_UPLOADED"; md5: string }
| { type: "ERROR_PROCESSING_UPLOAD"; id: string; message: string };

View file

@ -165,7 +165,7 @@ router.post(
}
await Skins.reject(req.ctx, md5);
req.notify({ type: "REJECTED_SKIN", md5 });
res.send("The skin has been rejected.");
res.send({ message: "The skin has been rejected." });
})
);
@ -182,7 +182,26 @@ router.post(
}
await Skins.approve(req.ctx, md5);
req.notify({ type: "APPROVED_SKIN", md5 });
res.send("The skin has been approved.");
res.send({ message: "The skin has been approved." });
})
);
// 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." });
})
);

View file

@ -77,6 +77,9 @@ export default class SkinModel {
}
const reviewRows = await this.getReviews();
const reviews = new Set(reviewRows.map((row) => row.review));
if (reviews.has("NSFW")) {
return "NSFW";
}
if (reviews.has("REJECTED")) {
return "REJECTED";
}

View file

@ -162,6 +162,8 @@ function getPrettyTwitterStatus(status: TweetStatus): string {
switch (status) {
case "APPROVED":
return "Approved ✅";
case "NSFW":
return "Rejected (NSFW) ❌";
case "REJECTED":
return "Rejected ❌";
case "UNREVIEWED":

View file

@ -32,7 +32,7 @@ export async function tweet(discordClient: Client, anything: string | null) {
await tweetBotChannel.send(`Oops! This skin has alraedy been tweeted.`);
return;
}
if (tweetStatus == "REJECTED") {
if (tweetStatus == "REJECTED" || tweetStatus == "NSFW") {
// @ts-ignore
await tweetBotChannel.send(`Oops! Can't tweet a rejected skin.`);
return;

View file

@ -1,4 +1,10 @@
export type TweetStatus = "APPROVED" | "REJECTED" | "TWEETED" | "UNREVIEWED";
export type TweetStatus =
| "APPROVED"
| "REJECTED"
| "TWEETED"
| "UNREVIEWED"
| "NSFW";
export type SkinType = "MODERN" | "CLASSIC";
export type SkinRow = {