mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-20 16:49:52 +00:00
189 lines
4.6 KiB
TypeScript
189 lines
4.6 KiB
TypeScript
import SkinModel from "../../data/SkinModel";
|
|
import * as Skins from "../../data/skins";
|
|
import { knex } from "../../db";
|
|
import SkinResolver from "./resolvers/SkinResolver";
|
|
import LRU from "lru-cache";
|
|
import { Int } from "grats";
|
|
import { ISkin } from "./resolvers/CommonSkinResolver";
|
|
import UserContext from "../../data/UserContext.js";
|
|
|
|
const options = {
|
|
max: 100,
|
|
maxAge: 1000 * 60 * 60,
|
|
};
|
|
let skinCount: number | null = null;
|
|
const cache = new LRU<string, Skins.MuseumPage>(options);
|
|
|
|
// A supery hacky global cache for common requests.
|
|
async function getMuseumSkinCountFromCache() {
|
|
if (skinCount == null) {
|
|
skinCount = await Skins.getClassicSkinCount();
|
|
}
|
|
return skinCount;
|
|
}
|
|
|
|
// A supery hacky global cache for common requests.
|
|
async function getSkinMuseumPageFromCache(first: number, offset: number) {
|
|
const key = `${first}-${offset}`;
|
|
const cached = cache.get(key);
|
|
if (cached != null) {
|
|
return cached;
|
|
}
|
|
const skins = await Skins.getMuseumPage({
|
|
offset: Number(offset),
|
|
first: Number(first),
|
|
});
|
|
cache.set(key, skins);
|
|
return skins;
|
|
}
|
|
|
|
/**
|
|
* A collection of classic Winamp skins
|
|
* @gqlType
|
|
*/
|
|
export default class SkinsConnection {
|
|
_first: number;
|
|
_offset: number;
|
|
_sort?: string | null;
|
|
_filter?: string | null;
|
|
constructor(
|
|
first: number,
|
|
offset: number,
|
|
sort?: string | null,
|
|
filter?: string | null
|
|
) {
|
|
this._first = first;
|
|
this._offset = offset;
|
|
this._filter = filter;
|
|
this._sort = sort;
|
|
}
|
|
_getQuery() {
|
|
let query = knex("skins").where({ skin_type: 1 });
|
|
|
|
// TODO: What happens if there are multiple tweets/reviews?
|
|
// Do we return multiple instances of that skin?
|
|
switch (this._filter) {
|
|
case "APPROVED":
|
|
query = query
|
|
.leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
|
|
.where("review", "APPROVED");
|
|
break;
|
|
case "REJECTED":
|
|
query = query
|
|
.leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
|
|
.where("review", "REJECTED");
|
|
break;
|
|
case "NSFW":
|
|
query = query
|
|
.leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5")
|
|
.where("review", "NSFW");
|
|
break;
|
|
case "TWEETED":
|
|
query = query.leftJoin("tweets", "tweet.skin_md5", "=", "skins.md5");
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
/**
|
|
* The total number of skins matching the filter
|
|
* @gqlField
|
|
*/
|
|
async count(): Promise<Int> {
|
|
if (this._sort === "MUSEUM") {
|
|
// This is the common case, so serve it from cache.
|
|
return getMuseumSkinCountFromCache();
|
|
}
|
|
const count = await this._getQuery().count("*", { as: "count" });
|
|
return Number(count[0].count);
|
|
}
|
|
|
|
/**
|
|
* The list of skins
|
|
* @gqlField
|
|
*/
|
|
async nodes(ctx: UserContext): Promise<Array<ISkin | null>> {
|
|
if (this._sort === "MUSEUM") {
|
|
if (this._filter) {
|
|
throw new Error(
|
|
"We don't support combining sorting and filtering at the same time."
|
|
);
|
|
}
|
|
const items = await getSkinMuseumPageFromCache(this._first, this._offset);
|
|
return Promise.all(
|
|
items.map(async (item) => {
|
|
const model = await SkinModel.fromMd5Assert(ctx, item.md5);
|
|
return SkinResolver.fromModel(model);
|
|
})
|
|
);
|
|
}
|
|
|
|
const skins = await this._getQuery()
|
|
.select()
|
|
.limit(this._first)
|
|
.offset(this._offset);
|
|
return skins.map((skin) => {
|
|
return SkinResolver.fromModel(new SkinModel(ctx, skin));
|
|
});
|
|
}
|
|
}
|
|
|
|
/** @gqlEnum */
|
|
type SkinsSortOption =
|
|
/**
|
|
the Museum's (https://skins.webamp.org) special sorting rules.
|
|
|
|
Roughly speaking, it's:
|
|
|
|
1. The four classic default skins
|
|
2. Tweeted skins first (sorted by the number of likes/retweets)
|
|
3. Approved, but not tweeted yet, skins
|
|
4. Unreviwed skins
|
|
5. Rejected skins
|
|
6. NSFW skins
|
|
*/
|
|
"MUSEUM";
|
|
|
|
/** @gqlEnum */
|
|
type SkinsFilterOption =
|
|
/*
|
|
Only the skins that have been approved for tweeting
|
|
*/
|
|
| "APPROVED"
|
|
|
|
/*
|
|
Only the skins that have been rejected for tweeting
|
|
*/
|
|
| "REJECTED"
|
|
|
|
/*
|
|
Only the skins that have been marked NSFW
|
|
*/
|
|
| "NSFW"
|
|
|
|
/*
|
|
Only the skins that have been tweeted
|
|
*/
|
|
| "TWEETED";
|
|
|
|
/**
|
|
* All classic skins in the database
|
|
*
|
|
* **Note:** We don't currently support combining sorting and filtering.
|
|
* @gqlQueryField */
|
|
export function skins({
|
|
first = 10,
|
|
offset = 0,
|
|
sort,
|
|
filter,
|
|
}: {
|
|
first?: Int;
|
|
offset?: Int;
|
|
sort?: SkinsSortOption | null;
|
|
filter?: SkinsFilterOption | null;
|
|
}): SkinsConnection {
|
|
if (first > 1000) {
|
|
throw new Error("Maximum limit is 1000");
|
|
}
|
|
return new SkinsConnection(first, offset, sort, filter);
|
|
}
|