Add TTL cache for game version/loader APIs and enrich upload metadata

This commit is contained in:
CPTProgrammer
2026-07-10 06:54:14 +08:00
parent 7ddbd36c63
commit e8d4994cec
5 changed files with 73 additions and 33 deletions
+23 -18
View File
@@ -5,31 +5,36 @@ import {
type GameVersionType, type GameVersionType,
type GameVersion, type GameVersion,
} from "@/types"; } from "@/types";
import { createTtlCache } from "@/lib/utils/cache";
const TTL = 3_600_000; // 1 hour
/** /**
* 获取 CurseForge 支持的 Game Version Type 列表。 * 获取 CurseForge 支持的 Game Version Type 列表1h 缓存)
* GET /api/game/version-types * GET /api/game/version-types
*/ */
export async function getVersionTypes( export const getVersionTypes = createTtlCache(
client: CurseForgeClient, async (client: CurseForgeClient): Promise<GameVersionType[]> => {
): Promise<GameVersionType[]> { const data = await client.uploadGet<unknown[]>(
const data = await client.uploadGet<unknown[]>( "/api/game/version-types",
"/api/game/version-types", );
); return GameVersionTypeSchema.array().parse(data);
return GameVersionTypeSchema.array().parse(data); },
} TTL,
);
/** /**
* 获取 CurseForge 支持的 Game Version 列表。 * 获取 CurseForge 支持的 Game Version 列表1h 缓存)
* 包含 Minecraft 版本、Mod Loader、Environment、Java 等。 * 包含 Minecraft 版本、Mod Loader、Environment、Java 等。
* 通过 gameVersionTypeID 区分类型。 * 通过 gameVersionTypeID 区分类型。
* GET /api/game/versions * GET /api/game/versions
*/ */
export async function getGameVersions( export const getGameVersions = createTtlCache(
client: CurseForgeClient, async (client: CurseForgeClient): Promise<GameVersion[]> => {
): Promise<GameVersion[]> { const data = await client.uploadGet<unknown[]>(
const data = await client.uploadGet<unknown[]>( "/api/game/versions",
"/api/game/versions", );
); return GameVersionSchema.array().parse(data);
return GameVersionSchema.array().parse(data); },
} TTL,
);
+19 -14
View File
@@ -5,25 +5,30 @@ import {
type LoaderTag, type LoaderTag,
type GameVersionTag, type GameVersionTag,
} from "@/types"; } from "@/types";
import { createTtlCache } from "@/lib/utils/cache";
const TTL = 3_600_000; // 1 hour
/** /**
* 获取 Modrinth 支持的全部模组加载器列表。 * 获取 Modrinth 支持的全部模组加载器列表1h 缓存)
* GET /tag/loader * GET /tag/loader
*/ */
export async function getLoaders( export const getLoaders = createTtlCache(
client: ModrinthClient, async (client: ModrinthClient): Promise<LoaderTag[]> => {
): Promise<LoaderTag[]> { const data = await client.get<unknown[]>("/tag/loader");
const data = await client.get<unknown[]>("/tag/loader"); return LoaderTagSchema.array().parse(data);
return LoaderTagSchema.array().parse(data); },
} TTL,
);
/** /**
* 获取 Modrinth 支持的全部 Minecraft 版本列表。 * 获取 Modrinth 支持的全部 Minecraft 版本列表1h 缓存)
* GET /tag/game_version * GET /tag/game_version
*/ */
export async function getGameVersions( export const getGameVersions = createTtlCache(
client: ModrinthClient, async (client: ModrinthClient): Promise<GameVersionTag[]> => {
): Promise<GameVersionTag[]> { const data = await client.get<unknown[]>("/tag/game_version");
const data = await client.get<unknown[]>("/tag/game_version"); return GameVersionTagSchema.array().parse(data);
return GameVersionTagSchema.array().parse(data); },
} TTL,
);
+16
View File
@@ -0,0 +1,16 @@
export function createTtlCache<T>(
factory: (...args: any[]) => Promise<T>,
ttlMs: number,
keyFn?: (...args: any[]) => string,
): (...args: any[]) => Promise<T> {
const cache = new Map<string, { data: T; ts: number }>();
return async (...args) => {
const key = keyFn ? keyFn(...args) : "_";
const entry = cache.get(key);
if (entry && Date.now() - entry.ts < ttlMs) return entry.data;
const data = await factory(...args);
cache.set(key, { data, ts: Date.now() });
return data;
};
}
+14 -1
View File
@@ -1,5 +1,5 @@
import { curseforgeClient } from "@/services/clients"; import { curseforgeClient } from "@/services/clients";
import { uploadFile } from "@/lib/curseforge"; import { uploadFile, getGameVersions } from "@/lib/curseforge";
import { resolveVersionName, buildJarPath } from "@/lib/utils/format"; import { resolveVersionName, buildJarPath } from "@/lib/utils/format";
import { readJarBlob } from "@/lib/utils/file"; import { readJarBlob } from "@/lib/utils/file";
import type { UploadMetadata } from "@/types"; import type { UploadMetadata } from "@/types";
@@ -26,11 +26,24 @@ export async function publishCurseForgeVersion(
const primaryBlob = readJarBlob(primaryPath); const primaryBlob = readJarBlob(primaryPath);
const gameVersions = await getGameVersions(curseforgeClient);
const resolveId = (name: string): number => {
const found = gameVersions.find((v) => v.name === name);
if (!found) throw new Error(`Game version "${name}" not found in CurseForge`);
return found.id;
};
const metadata: UploadMetadata = { const metadata: UploadMetadata = {
changelog: input.changelog, changelog: input.changelog,
changelogType: "markdown", changelogType: "markdown",
displayName, displayName,
releaseType: input.versionType, releaseType: input.versionType,
gameVersions: [
resolveId(mcVersion),
...config.curseforge.loaders.map(resolveId),
...config.curseforge.environment.map(resolveId),
],
relations: config.curseforge.relations, relations: config.curseforge.relations,
}; };
+1
View File
@@ -47,6 +47,7 @@ export async function publishModrinthVersion(
project_id: config.modrinth.project_id, project_id: config.modrinth.project_id,
name: versionName, name: versionName,
version_number: versionNumber, version_number: versionNumber,
changelog: input.changelog,
game_versions: [mcVersion], game_versions: [mcVersion],
version_type: input.versionType, version_type: input.versionType,
loaders: config.modrinth.loaders, loaders: config.modrinth.loaders,