import { getScreenshotUrl, getSkinUrl } from "./skins"; import { TweetStatus, SkinRow, ReviewRow } from "../types"; import UserContext, { ctxWeakMapMemoize } from "./UserContext"; import TweetModel, { TweetDebugData } from "./TweetModel"; import IaItemModel from "./IaItemModel"; import FileModel, { FileDebugData } from "./FileModel"; import { MD5_REGEX } from "../utils"; import DataLoader from "dataloader"; import { knex } from "../db"; export default class SkinModel { constructor(readonly ctx: UserContext, readonly row: SkinRow) {} static async fromMd5( ctx: UserContext, md5: string ): Promise { const row = await getSkinLoader(ctx).load(md5); 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 getSkinLoader(ctx).load(md5); return row != null; } async tweeted(): Promise { return (await this.getTweet()) != null; } async getTweet(): Promise { const tweets = await this.getTweets(); return tweets[0] || null; } async getTweets(): Promise { return TweetModel.fromMd5(this.ctx, this.row.md5); } getIaItem(): Promise { return IaItemModel.fromMd5(this.ctx, this.row.md5); } getReviews(): Promise { return getReviewsLoader(this.ctx).load(this.row.md5); } getFiles(): Promise { return FileModel.fromMd5(this.ctx, this.row.md5); } async getIsNsfw(): Promise { const reviews = await this.getReviews(); return reviews.some((review) => review.review === "NSFW"); } async getTweetStatus(): Promise { const tweet = await this.getTweet(); if (tweet != null) { "TWEETED"; } const reviewRows = await this.getReviews(); const reviews = new Set(reviewRows.map((row) => row.review)); if (reviews.has("REJECTED")) { return "REJECTED"; } if (reviews.has("APPROVED")) { return "APPROVED"; } return "UNREVIEWED"; } async getFileName(): Promise { const files = await this.getFiles(); if (files.length === 0) { throw new Error(`Could not find file for skin with md5 ${this.getMd5()}`); } return files[0].getFileName(); } getMd5(): string { return this.row.md5; } getEmails(): string[] { const { emails } = this.row; return emails ? emails.split(" ") : []; } getReadme(): string | null { return this.row.readme_text || null; } getMuseumUrl(): string { return `https://skins.webamp.org/skin/${this.row.md5}`; } getWebampUrl(): string { return `https://webamp.org?skinUrl=${this.getSkinUrl()}`; } getScreenshotUrl(): string { return getScreenshotUrl(this.row.md5); } getSkinUrl(): string { return getSkinUrl(this.row.md5); } async debug(): Promise<{ row: SkinRow; reviews: ReviewRow[]; tweets: TweetDebugData[]; files: FileDebugData[]; }> { const tweets = await this.getTweets(); const files = await this.getFiles(); return { row: this.row, reviews: await this.getReviews(), tweets: await Promise.all(tweets.map((tweet) => tweet.debug())), files: await Promise.all(files.map((file) => file.debug())), }; } } const getSkinLoader = ctxWeakMapMemoize>( () => new DataLoader(async (md5s) => { const rows = await knex("skins").whereIn("md5", md5s).select(); return md5s.map((md5) => rows.find((x) => x.md5 === md5)); }) ); const getReviewsLoader = ctxWeakMapMemoize>( () => new DataLoader(async (md5s) => { const rows = await knex("skin_reviews") .whereIn("skin_md5", md5s) .select(); return md5s.map((md5) => rows.filter((x) => x.skin_md5 === md5)); }) );