|
|
|
@@ -33,6 +33,28 @@ 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.
|
|
|
|
|
const PUBLIC_QUERY_CONCURRENCY = 3;
|
|
|
|
|
|
|
|
|
|
async function runWithConcurrency<
|
|
|
|
|
const T extends ReadonlyArray<() => Promise<unknown>>,
|
|
|
|
|
>(
|
|
|
|
|
tasks: T,
|
|
|
|
|
concurrency = PUBLIC_QUERY_CONCURRENCY,
|
|
|
|
|
): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>> }> {
|
|
|
|
|
const results = new Array<unknown>(tasks.length);
|
|
|
|
|
let next = 0;
|
|
|
|
|
await Promise.all(
|
|
|
|
|
Array.from({ length: Math.min(concurrency, tasks.length) }, async () => {
|
|
|
|
|
while (next < tasks.length) {
|
|
|
|
|
const index = next++;
|
|
|
|
|
results[index] = await tasks[index]();
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
return results as { [K in keyof T]: Awaited<ReturnType<T[K]>> };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function catalogCharacterWithUrls(
|
|
|
|
|
item: typeof catalogCharacters.$inferSelect,
|
|
|
|
|
materialByKey = new Map<string, typeof catalogMaterials.$inferSelect>(),
|
|
|
|
@@ -221,7 +243,11 @@ export async function listPublicGuideCards() {
|
|
|
|
|
.from(guides)
|
|
|
|
|
.innerJoin(media, eq(media.id, guides.coverMediaId))
|
|
|
|
|
.where(and(eq(guides.isPublic, true), isNull(guides.trashedAt)))
|
|
|
|
|
.orderBy(desc(guides.sortOrder), desc(guides.createdAt), asc(guides.name)), getDb().select().from(glossaryAliases)]);
|
|
|
|
|
.orderBy(desc(guides.sortOrder), desc(guides.createdAt), asc(guides.name)), getDb().select({
|
|
|
|
|
kind: glossaryAliases.kind,
|
|
|
|
|
catalogKey: glossaryAliases.catalogKey,
|
|
|
|
|
alias: glossaryAliases.alias,
|
|
|
|
|
}).from(glossaryAliases)]);
|
|
|
|
|
const grouped = aliasesByItem(aliases);
|
|
|
|
|
return rows.map(({ coverObjectKey, ...guide }) => ({
|
|
|
|
|
...guide,
|
|
|
|
@@ -303,22 +329,26 @@ async function getGuideContent(slug: string, publicOnly: boolean) {
|
|
|
|
|
const materialKeys = [...new Set(
|
|
|
|
|
[...row.character.ascension, ...row.character.leveling].flatMap((group) => group.items.map((item) => item.key)),
|
|
|
|
|
)];
|
|
|
|
|
const [sections, weaponConditionsRows, groups, profile, constellationGroups, teamConditionRows, teams, aliases, materials, poster, bleeding] = await Promise.all([
|
|
|
|
|
getDb().select().from(guideSections).where(and(eq(guideSections.guideId, row.guide.id), eq(guideSections.enabled, true))).orderBy(asc(guideSections.sortOrder)),
|
|
|
|
|
getDb().select().from(weaponConditions).where(eq(weaponConditions.guideId, row.guide.id)).orderBy(asc(weaponConditions.sortOrder)),
|
|
|
|
|
getDb().select().from(artifactGroups).where(eq(artifactGroups.guideId, row.guide.id)).orderBy(asc(artifactGroups.sortOrder)),
|
|
|
|
|
getDb().select().from(artifactProfiles).where(eq(artifactProfiles.guideId, row.guide.id)).limit(1),
|
|
|
|
|
getDb().select().from(constellationConditions).where(eq(constellationConditions.guideId, row.guide.id)).orderBy(asc(constellationConditions.sortOrder)),
|
|
|
|
|
getDb().select().from(teamConditions).where(eq(teamConditions.guideId, row.guide.id)).orderBy(asc(teamConditions.sortOrder)),
|
|
|
|
|
getDb().select().from(teamCompositions).where(eq(teamCompositions.guideId, row.guide.id)).orderBy(asc(teamCompositions.sortOrder)),
|
|
|
|
|
getDb().select().from(glossaryAliases),
|
|
|
|
|
materialKeys.length
|
|
|
|
|
const [sections, weaponConditionsRows, groups, profile, constellationGroups, teamConditionRows, teams, aliases, materials, poster, bleeding] = await runWithConcurrency([
|
|
|
|
|
() => getDb().select().from(guideSections).where(and(eq(guideSections.guideId, row.guide.id), eq(guideSections.enabled, true))).orderBy(asc(guideSections.sortOrder)),
|
|
|
|
|
() => getDb().select().from(weaponConditions).where(eq(weaponConditions.guideId, row.guide.id)).orderBy(asc(weaponConditions.sortOrder)),
|
|
|
|
|
() => getDb().select().from(artifactGroups).where(eq(artifactGroups.guideId, row.guide.id)).orderBy(asc(artifactGroups.sortOrder)),
|
|
|
|
|
() => getDb().select().from(artifactProfiles).where(eq(artifactProfiles.guideId, row.guide.id)).limit(1),
|
|
|
|
|
() => getDb().select().from(constellationConditions).where(eq(constellationConditions.guideId, row.guide.id)).orderBy(asc(constellationConditions.sortOrder)),
|
|
|
|
|
() => getDb().select().from(teamConditions).where(eq(teamConditions.guideId, row.guide.id)).orderBy(asc(teamConditions.sortOrder)),
|
|
|
|
|
() => getDb().select().from(teamCompositions).where(eq(teamCompositions.guideId, row.guide.id)).orderBy(asc(teamCompositions.sortOrder)),
|
|
|
|
|
() => getDb().select({
|
|
|
|
|
kind: glossaryAliases.kind,
|
|
|
|
|
catalogKey: glossaryAliases.catalogKey,
|
|
|
|
|
alias: glossaryAliases.alias,
|
|
|
|
|
}).from(glossaryAliases),
|
|
|
|
|
() => materialKeys.length
|
|
|
|
|
? getDb().select().from(catalogMaterials).where(inArray(catalogMaterials.key, materialKeys))
|
|
|
|
|
: [],
|
|
|
|
|
row.guide.posterArtMediaId
|
|
|
|
|
: Promise.resolve([]),
|
|
|
|
|
() => row.guide.posterArtMediaId
|
|
|
|
|
? getDb().select().from(media).where(eq(media.id, row.guide.posterArtMediaId)).limit(1)
|
|
|
|
|
: Promise.resolve([]),
|
|
|
|
|
getDb().select({ layer: guidePosterBleeding, asset: media }).from(guidePosterBleeding).leftJoin(media, eq(media.id, guidePosterBleeding.mediaId)).where(eq(guidePosterBleeding.guideId, row.guide.id)).orderBy(asc(guidePosterBleeding.sortOrder)),
|
|
|
|
|
() => getDb().select({ layer: guidePosterBleeding, asset: media }).from(guidePosterBleeding).leftJoin(media, eq(media.id, guidePosterBleeding.mediaId)).where(eq(guidePosterBleeding.guideId, row.guide.id)).orderBy(asc(guidePosterBleeding.sortOrder)),
|
|
|
|
|
]);
|
|
|
|
|
const groupedAliases = aliasesByItem(aliases);
|
|
|
|
|
const materialByKey = new Map(materials.map((item) => [item.key, item]));
|
|
|
|
|