perf : increase admin page speed

This commit is contained in:
2026-10-01 13:03:38 +07:00 Unverified
parent 9c91561abe
commit c5037154cb
7 changed files with 164 additions and 24 deletions
+81
View File
@@ -0,0 +1,81 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({ reads: [] as unknown[], rows: new Map<unknown, unknown[]>() }));
vi.mock("server-only", () => ({}));
vi.mock("@/lib/media/storage", () => ({ publicMediaUrl: (key: string) => `/media/${key}` }));
vi.mock("@/db", () => ({
getDb: () => ({
select: () => {
let table: unknown;
const query = {
from(value: unknown) { table = value; return query; },
innerJoin() { return query; },
leftJoin() { return query; },
where() { return query; },
orderBy() { return query; },
limit() { return query; },
then(resolve: (rows: unknown[]) => unknown) {
mocks.reads.push(table);
return Promise.resolve(mocks.rows.get(table) ?? []).then(resolve);
},
};
return query;
},
}),
}));
import {
artifactConditions, constellationConditions, extraSectionRows,
guides, guideSections, teamConditions, weaponConditions,
catalogMaterials, guidePosterBleeding,
} from "@/db/schema";
import { getAdminGuideContent } from "./queries";
beforeEach(() => {
mocks.reads.length = 0;
mocks.rows.clear();
mocks.rows.set(guides, [{
guide: { id: "guide", slug: "test", posterArtMediaId: null },
cover: { objectKey: "cover", status: "ready" },
character: { key: "test", imageKey: "character", constellations: [], talents: [], ascension: [], leveling: [] },
}]);
mocks.rows.set(guideSections, [
{ id: "extra-a", slug: "extra-a", kind: "extra" },
{ id: "extra-b", slug: "extra-b", kind: "extra" },
]);
});
describe("admin section reads", () => {
it.each([
["weapon", weaponConditions],
["artifact", artifactConditions],
["constellations", constellationConditions],
["team", teamConditions],
])("loads only the %s section's conditions", async (section, expectedTable) => {
const result = await getAdminGuideContent("test", section);
expect(result).not.toBeNull();
expect(mocks.reads).toContain(expectedTable);
for (const table of [weaponConditions, artifactConditions, constellationConditions, teamConditions]) {
if (table !== expectedTable) expect(mocks.reads).not.toContain(table);
}
expect(mocks.reads).not.toContain(extraSectionRows);
expect(mocks.reads).not.toContain(catalogMaterials);
expect(mocks.reads).not.toContain(guidePosterBleeding);
});
it("loads just the selected extra section", async () => {
const result = await getAdminGuideContent("test", "extra-b");
expect(result?.extras.map((extra) => extra.id)).toEqual(["extra-b"]);
expect(mocks.reads).toContain(extraSectionRows);
expect(mocks.reads).not.toContain(weaponConditions);
expect(mocks.reads).not.toContain(teamConditions);
});
it("preserves full-guide reads for existing preview and validation callers", async () => {
const result = await getAdminGuideContent("test");
for (const table of [weaponConditions, artifactConditions, constellationConditions, teamConditions, extraSectionRows]) {
expect(mocks.reads).toContain(table);
}
expect(result?.extras).toHaveLength(2);
});
});
+35 -16
View File
@@ -1,7 +1,6 @@
import "server-only";
import { and, asc, desc, eq, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm";
import * as z from "zod";
import { getDb } from "@/db";
import {
@@ -27,11 +26,10 @@ import {
weaponRecommendations,
weaponConditions,
} from "@/db/schema";
import { getMediaStorage, publicMediaUrl } from "@/lib/media/storage";
import { publicMediaUrl } from "@/lib/media/storage";
import { aliasesByItem } from "@/lib/glossary";
import type { PosterBlendMode } from "@/components/public/material-poster";
const catalogVersionSchema = z.object({ version: z.string() });
const posterBlendModes = new Set(["normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"]);
// Leave most of the per-pod pool available to other visitors during cache misses.
@@ -121,17 +119,7 @@ export async function getActiveCatalog() {
if (!characters.length) return null;
const groupedAliases = aliasesByItem(aliases);
const materialByKey = new Map(materials.map((item) => [item.key, item]));
let version = "unknown";
try {
const storage = await getMediaStorage();
version = catalogVersionSchema.parse(
await storage.file("catalog/current.json").json(),
).version;
} catch (error) {
console.error("Unable to read the active catalog version", error);
}
return {
version,
characters: characters.map((item) => ({ ...catalogCharacterWithUrls(item, materialByKey), aliases: groupedAliases.character.get(item.key) ?? [] })),
weapons: weapons.map((item) => ({ ...item, aliases: groupedAliases.weapon.get(item.key) ?? [], imageUrl: publicMediaUrl(item.imageKey) })),
artifacts: artifacts.map((item) => ({ ...item, aliases: groupedAliases.artifact.get(item.key) ?? [], imageUrl: publicMediaUrl(item.imageKey) })),
@@ -139,6 +127,37 @@ export async function getActiveCatalog() {
};
}
// Picker data deliberately excludes character descriptions, talents and materials.
// Editors obtain the selected character's details from their guide content.
export async function getCatalogOptions() {
const [characters, weapons, artifacts, aliases] = await runWithConcurrency([
() => getDb().select({
key: catalogCharacters.key,
name: catalogCharacters.name,
rarity: catalogCharacters.rarity,
weaponType: catalogCharacters.weaponType,
element: catalogCharacters.element,
imageKey: catalogCharacters.imageKey,
elementIconKey: catalogCharacters.elementIconKey,
}).from(catalogCharacters).orderBy(asc(catalogCharacters.name)),
() => getDb().select().from(catalogWeapons).orderBy(asc(catalogWeapons.name)),
() => getDb().select().from(catalogArtifacts).orderBy(asc(catalogArtifacts.name)),
() => getDb().select().from(glossaryAliases),
]);
if (!characters.length) return null;
const groupedAliases = aliasesByItem(aliases);
return {
characters: characters.map((item) => ({
...item,
imageUrl: publicMediaUrl(item.imageKey),
elementIconUrl: item.elementIconKey ? publicMediaUrl(item.elementIconKey) : null,
aliases: groupedAliases.character.get(item.key) ?? [],
})),
weapons: weapons.map((item) => ({ ...item, imageUrl: publicMediaUrl(item.imageKey), aliases: groupedAliases.weapon.get(item.key) ?? [] })),
artifacts: artifacts.map((item) => ({ ...item, imageUrl: publicMediaUrl(item.imageKey), aliases: groupedAliases.artifact.get(item.key) ?? [] })),
};
}
export async function listGuideCards(includeTrash = false) {
const rows = await getDb()
.select({
@@ -375,7 +394,7 @@ async function getGuideContent(slug: string, publicOnly: boolean, selectedSectio
if (!row) return null;
const availableSections = publicOnly ? await populatedSections(row.guide.id) : undefined;
if (publicOnly && selectedSection !== "overview" && !availableSections?.some((section) => section.slug === selectedSection)) return null;
const wants = (section: string) => !publicOnly || selectedSection === section;
const wants = (section: string) => selectedSection === undefined || selectedSection === section;
const materialKeys = [...new Set(
[...row.character.ascension, ...row.character.leveling].flatMap((group) => group.items.map((item) => item.key)),
)];
@@ -506,8 +525,8 @@ export function getPublicGuide(slug: string, section = "overview") {
return getGuideContent(slug, true, section);
}
export function getAdminGuideContent(slug: string) {
return getGuideContent(slug, false);
export function getAdminGuideContent(slug: string, section?: string) {
return getGuideContent(slug, false, section);
}
export async function getAdminGuideContentById(guideId: string) {