diff --git a/apps/api/src/skin/skin.service.ts b/apps/api/src/skin/skin.service.ts index 2bd2f289f..4b15586a4 100644 --- a/apps/api/src/skin/skin.service.ts +++ b/apps/api/src/skin/skin.service.ts @@ -18,8 +18,13 @@ import { getMinecraftTexturePath } from "@statsify/assets"; import { renderSkin } from "@statsify/skin-renderer"; import type { ReturnModelType } from "@typegoose/typegoose"; +const REFRESH_AFTER = 1000 * 60 * 60 * 24; +const RETRY_AFTER = 1000 * 60 * 60; + @Injectable() export class SkinService { + private readonly refreshing = new Set(); + public constructor( private readonly httpService: HttpService, @InjectModel(Skin) private readonly skinModel: ReturnModelType @@ -64,22 +69,31 @@ export class SkinService { .lean() .exec(); - if (cachedSkin && Date.now() < cachedSkin.expiresAt) { + if (cachedSkin) { + if (Date.now() >= cachedSkin.expiresAt) this.refresh(cachedSkin.uuid); return cachedSkin; } const uuid = isUsername ? await this.getUuid(tag) : tag; + const skin = await this.requestSkin(uuid); - const skin = await this.requestSkin(uuid).catch((error) => { - if (cachedSkin?.uuid === uuid) return cachedSkin; - throw error; - }); + return this.save(skin); + } - // Cache for 3 hours - skin.expiresAt = Date.now() + (1000 * 60 * 60 * 3); + private refresh(uuid: string) { + if (this.refreshing.has(uuid)) return; + this.refreshing.add(uuid); - await this.skinModel.replaceOne({ uuid }, skin, { upsert: true }).lean().exec(); + this.requestSkin(uuid) + .then((skin) => this.save(skin)) + .catch(() => this.skinModel.updateOne({ uuid }, { expiresAt: Date.now() + RETRY_AFTER }).exec()) + .catch(() => undefined) + .finally(() => this.refreshing.delete(uuid)); + } + private async save(skin: Skin): Promise { + skin.expiresAt = Date.now() + REFRESH_AFTER; + await this.skinModel.replaceOne({ uuid: skin.uuid }, skin, { upsert: true }).lean().exec(); return skin; }