From e1d29222a55007b973feef38a46df62ad1201faa Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 19 Nov 2020 19:02:21 -0500 Subject: [PATCH] Extract fromAnything logic into models --- .../api/__tests__/router.test.ts | 107 +++++++--- packages/skin-database/data/IaItemModel.ts | 26 +++ packages/skin-database/data/SkinModel.ts | 19 +- .../data/__tests__/SkinModel.test.ts | 25 +++ .../data/__tests__/skins.test.ts | 197 +++++++++++------- packages/skin-database/data/skins.ts | 30 --- .../discord-bot/commands/skin.ts | 10 +- packages/skin-database/seeds/test_data.ts | 5 + packages/skin-database/tasks/tweet.ts | 13 +- 9 files changed, 279 insertions(+), 153 deletions(-) create mode 100644 packages/skin-database/data/__tests__/SkinModel.test.ts diff --git a/packages/skin-database/api/__tests__/router.test.ts b/packages/skin-database/api/__tests__/router.test.ts index bf659231..4fb8643c 100644 --- a/packages/skin-database/api/__tests__/router.test.ts +++ b/packages/skin-database/api/__tests__/router.test.ts @@ -17,27 +17,60 @@ beforeEach(async () => { describe("/skins/", () => { test("no query params", async () => { const { body } = await request(app).get("/skins/"); - expect(body).toEqual({ - skinCount: 4, - skins: [ - { fileName: "path.wsz", md5: "a_fake_md5", nsfw: false }, - { fileName: "approved.wsz", md5: "an_approved_md5", nsfw: false }, - { fileName: "rejected.wsz", md5: "a_rejected_md5", nsfw: false }, - { fileName: "nsfw.wsz", md5: "a_nsfw_md5", nsfw: true }, - ], - }); + expect(body).toMatchInlineSnapshot(` + Object { + "skinCount": 5, + "skins": Array [ + Object { + "fileName": "Zelda_Amp_3.wsz", + "md5": "48bbdbbeb03d347e59b1eebda4d352d0", + "nsfw": false, + }, + Object { + "fileName": "path.wsz", + "md5": "a_fake_md5", + "nsfw": false, + }, + Object { + "fileName": "approved.wsz", + "md5": "an_approved_md5", + "nsfw": false, + }, + Object { + "fileName": "rejected.wsz", + "md5": "a_rejected_md5", + "nsfw": false, + }, + Object { + "fileName": "nsfw.wsz", + "md5": "a_nsfw_md5", + "nsfw": true, + }, + ], + } + `); }); test("first and offset", async () => { const { body } = await request(app) .get("/skins/") .query({ first: 2, offset: 1 }); - expect(body).toEqual({ - skinCount: 4, - skins: [ - { fileName: "approved.wsz", md5: "an_approved_md5", nsfw: false }, - { fileName: "rejected.wsz", md5: "a_rejected_md5", nsfw: false }, - ], - }); + expect(body).toMatchInlineSnapshot(` + Object { + "skinCount": 5, + "skins": Array [ + Object { + "fileName": "path.wsz", + "md5": "a_fake_md5", + "nsfw": false, + }, + Object { + "fileName": "approved.wsz", + "md5": "an_approved_md5", + "nsfw": false, + }, + ], + } + `); }); }); @@ -76,22 +109,28 @@ test("/skins/a_fake_md5", async () => { test("/stylegan.json", async () => { let response = await request(app).get("/stylegan.json"); - expect(response.body).toEqual([ - { - fileName: "path.wsz", - url: "https://cdn.webampskins.org/screenshots/a_fake_md5.png", - }, - { - fileName: "nsfw.wsz", - url: "https://cdn.webampskins.org/screenshots/a_nsfw_md5.png", - }, - { - fileName: "rejected.wsz", - url: "https://cdn.webampskins.org/screenshots/a_rejected_md5.png", - }, - { - fileName: "approved.wsz", - url: "https://cdn.webampskins.org/screenshots/an_approved_md5.png", - }, - ]); + expect(response.body).toMatchInlineSnapshot(` + Array [ + Object { + "fileName": "Zelda_Amp_3.wsz", + "url": "https://cdn.webampskins.org/screenshots/48bbdbbeb03d347e59b1eebda4d352d0.png", + }, + Object { + "fileName": "path.wsz", + "url": "https://cdn.webampskins.org/screenshots/a_fake_md5.png", + }, + Object { + "fileName": "nsfw.wsz", + "url": "https://cdn.webampskins.org/screenshots/a_nsfw_md5.png", + }, + Object { + "fileName": "rejected.wsz", + "url": "https://cdn.webampskins.org/screenshots/a_rejected_md5.png", + }, + Object { + "fileName": "approved.wsz", + "url": "https://cdn.webampskins.org/screenshots/an_approved_md5.png", + }, + ] + `); }); diff --git a/packages/skin-database/data/IaItemModel.ts b/packages/skin-database/data/IaItemModel.ts index 99009127..e12953d4 100644 --- a/packages/skin-database/data/IaItemModel.ts +++ b/packages/skin-database/data/IaItemModel.ts @@ -1,5 +1,8 @@ import UserContext from "./UserContext"; import { IaItemRow } from "../types"; +import SkinModel from "./SkinModel"; + +const IA_URL = /^(https:\/\/)?archive.org\/details\/([^\/]+)\/?/; export default class IaItemModel { constructor(readonly ctx: UserContext, readonly row: IaItemRow) {} @@ -20,6 +23,29 @@ export default class IaItemModel { return row == null ? null : new IaItemModel(ctx, row); } + static async fromAnything( + ctx: UserContext, + anything: string + ): Promise { + const itemMatchResult = anything.match(IA_URL); + if (itemMatchResult != null) { + const itemName = itemMatchResult[2]; + const item = await IaItemModel.fromIdentifier(ctx, itemName); + if (item != null) { + return item; + } + } + return IaItemModel.fromIdentifier(ctx, anything); + } + + async getSkin(): Promise { + const skin = await SkinModel.fromMd5(this.ctx, this.getMd5()); + if (skin == null) { + throw new Error(`Could not find skin for md5 "${this.getMd5()}"`); + } + return skin; + } + getMd5(): string { return this.row.skin_md5; } diff --git a/packages/skin-database/data/SkinModel.ts b/packages/skin-database/data/SkinModel.ts index c8f6f32c..2cd35461 100644 --- a/packages/skin-database/data/SkinModel.ts +++ b/packages/skin-database/data/SkinModel.ts @@ -1,10 +1,11 @@ import path from "path"; import { getScreenshotUrl, getSkinUrl } from "./skins"; -import { TweetStatus, SkinRow, TweetRow, ReviewRow, FileRow } from "../types"; +import { TweetStatus, SkinRow, ReviewRow } from "../types"; import UserContext from "./UserContext"; import TweetModel, { TweetDebugData } from "./TweetModel"; import IaItemModel from "./IaItemModel"; import FileModel, { FileDebugData } from "./FileModel"; +import { MD5_REGEX } from "../utils"; export default class SkinModel { constructor(readonly ctx: UserContext, readonly row: SkinRow) {} @@ -17,6 +18,22 @@ export default class SkinModel { return row == null ? null : new SkinModel(ctx, row); } + static async fromAnything( + ctx: UserContext, + anything: string + ): Promise { + const md5Match = anything.match(MD5_REGEX); + if (md5Match != null) { + const md5 = md5Match[1]; + const found = await SkinModel.fromMd5(ctx, md5); + if (found != null) { + return found; + } + } + const iaItem = await IaItemModel.fromAnything(ctx, anything); + return iaItem?.getSkin() ?? null; + } + static async exists(ctx: UserContext, md5: string): Promise { const row = await ctx.skin.load(md5); return row != null; diff --git a/packages/skin-database/data/__tests__/SkinModel.test.ts b/packages/skin-database/data/__tests__/SkinModel.test.ts new file mode 100644 index 00000000..de051817 --- /dev/null +++ b/packages/skin-database/data/__tests__/SkinModel.test.ts @@ -0,0 +1,25 @@ +import { knex } from "../../db"; +import SkinModel from "../SkinModel"; +import UserContext from "../UserContext"; + +beforeEach(async () => { + await knex.migrate.latest(); + await knex.seed.run(); +}); + +test("fromMd5", async () => { + const ctx = new UserContext(); + const skin = await SkinModel.fromMd5(ctx, "a_fake_md5"); + expect(skin?.getMd5()).toBe("a_fake_md5"); +}); + +describe("fromAnything", () => { + test("md5", async () => { + const ctx = new UserContext(); + const skin = await SkinModel.fromAnything( + ctx, + "48bbdbbeb03d347e59b1eebda4d352d0" + ); + expect(skin?.getMd5()).toBe("48bbdbbeb03d347e59b1eebda4d352d0"); + }); +}); diff --git a/packages/skin-database/data/__tests__/skins.test.ts b/packages/skin-database/data/__tests__/skins.test.ts index e1ecb4ee..36fb933f 100644 --- a/packages/skin-database/data/__tests__/skins.test.ts +++ b/packages/skin-database/data/__tests__/skins.test.ts @@ -5,85 +5,125 @@ beforeEach(async () => { await knex.migrate.latest(); }); -describe("empty", () => { - test("empty", async () => { - expect(await Skins.getAllClassicSkins()).toEqual([]); - expect(await Skins.getClassicSkinCount()).toBe(0); - expect(await Skins.getTweetableSkinCount()).toBe(0); - expect(await Skins.getAllClassicScreenshotUrls()).toEqual([]); - expect(await Skins.getMuseumPage({ offset: 0, first: 10 })).toEqual([]); - expect(await Skins.getStats()).toEqual({ - approved: 0, - rejected: 0, - nsfw: 0, - tweeted: 0, - tweetable: 0, - webUploads: 0, - }); - - expect(await Skins.getSkinToTweet()).toBe(null); - expect(Skins.getSkinToReview()).rejects.toThrow( - "Could not find any skins to review" - ); - expect(await Skins.getReportedUpload()).toBe(null); - expect(await Skins.getSkinToShoot()).toBe(null); - expect(await Skins.getUploadStatuses([])).toEqual({}); +test("empty", async () => { + expect(await Skins.getAllClassicSkins()).toEqual([]); + expect(await Skins.getClassicSkinCount()).toBe(0); + expect(await Skins.getTweetableSkinCount()).toBe(0); + expect(await Skins.getAllClassicScreenshotUrls()).toEqual([]); + expect(await Skins.getMuseumPage({ offset: 0, first: 10 })).toEqual([]); + expect(await Skins.getStats()).toEqual({ + approved: 0, + rejected: 0, + nsfw: 0, + tweeted: 0, + tweetable: 0, + webUploads: 0, }); + + expect(await Skins.getSkinToTweet()).toBe(null); + expect(Skins.getSkinToReview()).rejects.toThrow( + "Could not find any skins to review" + ); + expect(await Skins.getReportedUpload()).toBe(null); + expect(await Skins.getSkinToShoot()).toBe(null); + expect(await Skins.getUploadStatuses([])).toEqual({}); }); describe("seeded", () => { beforeEach(async () => { await knex.seed.run(); }); - test("empty", async () => { - expect(await Skins.getAllClassicSkins()).toEqual([ - { fileName: "path.wsz", md5: "a_fake_md5" }, - { fileName: "nsfw.wsz", md5: "a_nsfw_md5" }, - { fileName: "rejected.wsz", md5: "a_rejected_md5" }, - { fileName: "approved.wsz", md5: "an_approved_md5" }, - ]); - expect(await Skins.getClassicSkinCount()).toBe(4); + test("getAllClassicSkins", async () => { + expect(await Skins.getAllClassicSkins()).toMatchInlineSnapshot(` + Array [ + Object { + "fileName": "Zelda_Amp_3.wsz", + "md5": "48bbdbbeb03d347e59b1eebda4d352d0", + }, + Object { + "fileName": "path.wsz", + "md5": "a_fake_md5", + }, + Object { + "fileName": "nsfw.wsz", + "md5": "a_nsfw_md5", + }, + Object { + "fileName": "rejected.wsz", + "md5": "a_rejected_md5", + }, + Object { + "fileName": "approved.wsz", + "md5": "an_approved_md5", + }, + ] + `); + }); + test("getClassicSkinCount", async () => { + expect(await Skins.getClassicSkinCount()).toBe(5); + }); + test("getTweetableSkinCount", async () => { expect(await Skins.getTweetableSkinCount()).toBe(1); - expect(await Skins.getAllClassicScreenshotUrls()).toEqual([ - { - fileName: "path.wsz", - url: "https://cdn.webampskins.org/screenshots/a_fake_md5.png", - }, - { - fileName: "nsfw.wsz", - url: "https://cdn.webampskins.org/screenshots/a_nsfw_md5.png", - }, - { - fileName: "rejected.wsz", - url: "https://cdn.webampskins.org/screenshots/a_rejected_md5.png", - }, - { - fileName: "approved.wsz", - url: "https://cdn.webampskins.org/screenshots/an_approved_md5.png", - }, - ]); - expect(await Skins.getMuseumPage({ offset: 0, first: 10 })).toEqual([ - { - fileName: "path.wsz", - md5: "a_fake_md5", - nsfw: false, - }, - { - fileName: "approved.wsz", - md5: "an_approved_md5", - nsfw: false, - }, - { - fileName: "rejected.wsz", - md5: "a_rejected_md5", - nsfw: false, - }, - { - fileName: "nsfw.wsz", - md5: "a_nsfw_md5", - nsfw: true, - }, - ]); + }); + test("getAllClassicScreenshotUrls", async () => { + expect(await Skins.getAllClassicScreenshotUrls()).toMatchInlineSnapshot(` + Array [ + Object { + "fileName": "Zelda_Amp_3.wsz", + "url": "https://cdn.webampskins.org/screenshots/48bbdbbeb03d347e59b1eebda4d352d0.png", + }, + Object { + "fileName": "path.wsz", + "url": "https://cdn.webampskins.org/screenshots/a_fake_md5.png", + }, + Object { + "fileName": "nsfw.wsz", + "url": "https://cdn.webampskins.org/screenshots/a_nsfw_md5.png", + }, + Object { + "fileName": "rejected.wsz", + "url": "https://cdn.webampskins.org/screenshots/a_rejected_md5.png", + }, + Object { + "fileName": "approved.wsz", + "url": "https://cdn.webampskins.org/screenshots/an_approved_md5.png", + }, + ] + `); + }); + test("getMuseumPage", async () => { + expect(await Skins.getMuseumPage({ offset: 0, first: 10 })) + .toMatchInlineSnapshot(` + Array [ + Object { + "fileName": "Zelda_Amp_3.wsz", + "md5": "48bbdbbeb03d347e59b1eebda4d352d0", + "nsfw": false, + }, + Object { + "fileName": "path.wsz", + "md5": "a_fake_md5", + "nsfw": false, + }, + Object { + "fileName": "approved.wsz", + "md5": "an_approved_md5", + "nsfw": false, + }, + Object { + "fileName": "rejected.wsz", + "md5": "a_rejected_md5", + "nsfw": false, + }, + Object { + "fileName": "nsfw.wsz", + "md5": "a_nsfw_md5", + "nsfw": true, + }, + ] + `); + }); + test("getStats", async () => { expect(await Skins.getStats()).toEqual({ approved: 1, rejected: 1, @@ -92,16 +132,27 @@ describe("seeded", () => { tweetable: 1, webUploads: 0, }); - + }); + test("getSkinToTweet", async () => { expect(await Skins.getSkinToTweet()).toEqual({ canonicalFilename: "approved.wsz", md5: "an_approved_md5", }); + }); + test("getSkinToReview", async () => { expect(Skins.getSkinToReview()).rejects.toThrow( "Could not find any skins to review" ); + }); + test("getReportedUpload", async () => { expect(await Skins.getReportedUpload()).toBe(null); - expect(await Skins.getSkinToShoot()).toBe("a_fake_md5"); + }); + test("getSkinToShoot", async () => { + expect(await Skins.getSkinToShoot()).toBe( + "48bbdbbeb03d347e59b1eebda4d352d0" + ); + }); + test("getUploadStatuses", async () => { expect(await Skins.getUploadStatuses([])).toEqual({}); }); }); diff --git a/packages/skin-database/data/skins.ts b/packages/skin-database/data/skins.ts index f05d7c4c..a3055602 100644 --- a/packages/skin-database/data/skins.ts +++ b/packages/skin-database/data/skins.ts @@ -55,8 +55,6 @@ export async function addSkin({ ); } -const IA_URL = /^(https:\/\/)?archive.org\/details\/([^\/]+)\/?/; - const CRUFT_FILENAME = /winampskins\.info\.(html)|(txt)$/; export async function setContentHash(md5: string): Promise { @@ -77,28 +75,6 @@ export async function setContentHash(md5: string): Promise { return contentHash; } -export async function getMd5ByAnything( - anything: string -): Promise { - const md5Match = anything.match(MD5_REGEX); - if (md5Match != null) { - const md5 = md5Match[1]; - const found = await knex("skins").where({ md5, skin_type: 1 }).first(); - if (found != null) { - return md5; - } - } - const itemMatchResult = anything.match(IA_URL); - if (itemMatchResult != null) { - const itemName = itemMatchResult[2]; - const md5 = await getMd5FromInternetArchvieItemName(itemName); - if (md5 != null) { - return md5; - } - } - return (await getMd5FromInternetArchvieItemName(anything)) ?? null; -} - export async function getSkinDebugData(md5: string): Promise { const skin = await knex("skins").where({ md5 }).select(); const searchIndexUpdates = await knex("algolia_field_updates") @@ -126,12 +102,6 @@ export async function getSkinDebugData(md5: string): Promise { }; } -async function getMd5FromInternetArchvieItemName(itemName: string) { - const ctx = new UserContext(); - const item = await IaItemModel.fromIdentifier(ctx, itemName); - return item?.getMd5(); -} - export async function getTweetableSkinCount(): Promise { const tweetable = await knex("skins") .leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5") diff --git a/packages/skin-database/discord-bot/commands/skin.ts b/packages/skin-database/discord-bot/commands/skin.ts index 771428cb..bf9d28c2 100644 --- a/packages/skin-database/discord-bot/commands/skin.ts +++ b/packages/skin-database/discord-bot/commands/skin.ts @@ -1,21 +1,23 @@ import { Message } from "discord.js"; import * as Utils from "../utils"; -import * as Skins from "../../data/skins"; +import UserContext from "../../data/UserContext"; +import SkinModel from "../../data/SkinModel"; async function handler(message: Message, args: [string]) { + const ctx = new UserContext(); const [anything] = args; if (anything == null) { message.channel.send(` is required.`); return; } - const md5 = await Skins.getMd5ByAnything(anything); - if (md5 == null) { + const skin = await SkinModel.fromAnything(ctx, anything); + if (skin == null) { message.channel.send(`Could not find a skin matching ${anything}`); return; } await Utils.postSkin({ - md5, + md5: skin.getMd5(), dest: message.channel, }); } diff --git a/packages/skin-database/seeds/test_data.ts b/packages/skin-database/seeds/test_data.ts index 4e2b49ea..fc778bb3 100644 --- a/packages/skin-database/seeds/test_data.ts +++ b/packages/skin-database/seeds/test_data.ts @@ -12,6 +12,7 @@ export async function seed(knex: Knex): Promise { { md5: "an_approved_md5", skin_type: 1 }, { md5: "a_rejected_md5", skin_type: 1 }, { md5: "a_nsfw_md5", skin_type: 1 }, + { md5: "48bbdbbeb03d347e59b1eebda4d352d0", skin_type: 1 }, ]); await knex("files").insert([ { skin_md5: "a_fake_md5", file_path: "/a/fake/path.wsz" }, @@ -19,6 +20,10 @@ export async function seed(knex: Knex): Promise { { skin_md5: "an_approved_md5", file_path: "/a/fake/approved.wsz" }, { skin_md5: "a_rejected_md5", file_path: "/a/fake/rejected.wsz" }, { skin_md5: "a_nsfw_md5", file_path: "/a/fake/nsfw.wsz" }, + { + skin_md5: "48bbdbbeb03d347e59b1eebda4d352d0", + file_path: "/a/fake/Zelda_Amp_3.wsz", + }, ]); await knex("skin_reviews").insert([ { skin_md5: "an_approved_md5", review: "APPROVED" }, diff --git a/packages/skin-database/tasks/tweet.ts b/packages/skin-database/tasks/tweet.ts index 2ac3fd0e..73c7ff1b 100644 --- a/packages/skin-database/tasks/tweet.ts +++ b/packages/skin-database/tasks/tweet.ts @@ -18,23 +18,14 @@ export async function tweet(discordClient: Client, anything: string | null) { } let tweetableSkin: null | SkinModel = null; if (anything != null) { - const _md5 = await Skins.getMd5ByAnything(anything); - if (_md5 == null) { + tweetableSkin = await SkinModel.fromAnything(ctx, anything); + if (tweetableSkin == null) { // @ts-ignore await tweetBotChannel.send( `Oops! Could not find a skin matching ${anything}` ); return; } - tweetableSkin = await SkinModel.fromMd5(ctx, _md5); - if (tweetableSkin == null) { - // @ts-ignore - await tweetBotChannel.send( - `Oops! Could not find a skin matching the md5 hash ${_md5}` - ); - logger.info(`Could not find a skin matching hash ${_md5}`); - return; - } const tweetStatus = await tweetableSkin.getTweetStatus(); if (tweetStatus == "TWEETED") { // @ts-ignore