prerender

This commit is contained in:
Kalle
2026-08-16 21:54:59 +03:00
parent c3e26dceae
commit c2b47e3422
7 changed files with 110 additions and 33 deletions

View File

@@ -1,6 +1,14 @@
import * as UserRepository from "#lib/features/user-page/UserRepository.server.ts";
import { query } from "$app/server";
import { IS_E2E_TEST_RUN } from "#lib/utils/e2e.ts";
import { prerender, query } from "$app/server";
export const getPatrons = query(async () => {
// xxx: is this really something needed? (other places too)
// prerendered data would be baked from the build machine's database, so the
// e2e build keeps this as a regular query against the per-worker test dbs
export const getPatrons = IS_E2E_TEST_RUN
? query(findPatrons)
: prerender(findPatrons);
function findPatrons() {
return UserRepository.findAllPatrons();
});
}

View File

@@ -27,7 +27,11 @@ import {
userSeasonsPage,
weaponCategoryUrl,
} from "#lib/utils/urls.ts";
import { getLeaderboards } from "../leaderboards.remote.ts";
import { getLeaderboards, getXPLeaderboard } from "../leaderboards.remote.ts";
import {
isXPLeaderboardType,
type LeaderboardType,
} from "../leaderboards-constants.ts";
import { leaderboardsSearchParams } from "../leaderboards-search-params.ts";
import { seasonHasTopTen } from "../leaderboards-utils.ts";
import TopTenPlayer from "../TopTenPlayer.svelte";
@@ -38,7 +42,7 @@ import XPTable from "./XPTable.svelte";
const params = searchParamsState(leaderboardsSearchParams);
const data = $derived(await getLeaderboards(params.current));
const data = $derived(await loadLeaderboards(params.current));
const isAllUserLeaderboard = $derived(params.current.type === "USER");
@@ -75,6 +79,26 @@ function onTabChange(key: string) {
if (key === "TEAMS") return params.set({ type: "TEAM" });
params.set({ type: "XP-ALL", season: null });
}
async function loadLeaderboards(query: {
type: LeaderboardType;
season: number | null;
}) {
if (isXPLeaderboardType(query.type)) {
return {
xpLeaderboard: await getXPLeaderboard(query.type),
userLeaderboard: null,
ownEntryPeek: null,
teamLeaderboard: null,
season: Seasons.currentOrPrevious()!.nth,
};
}
return {
xpLeaderboard: null,
...(await getLeaderboards({ type: query.type, season: query.season })),
};
}
</script>
<Main halfWidth>
@@ -234,7 +258,7 @@ function onTabChange(key: string) {
<TeamTable
entries={data.teamLeaderboard}
season={data.season}
queryArgs={params.current}
leaderboardType={params.current.type}
showQualificationDividers={params.current.type !== "TEAM-ALL"}
/>
{/if}

View File

@@ -9,27 +9,29 @@ import { hasRole } from "#lib/features/auth/user-state.ts";
import * as Seasons from "#lib/features/mmr/Seasons.ts";
import { m } from "#lib/paraglide/messages.js";
import { teamPage, userPage } from "#lib/utils/urls.ts";
import { TEAM_LEADERBOARD_QUALIFYING_COUNT } from "../leaderboards-constants.ts";
import type { LeaderboardsQueryArgs } from "../leaderboards-schemas.ts";
import {
type LeaderboardType,
TEAM_LEADERBOARD_QUALIFYING_COUNT,
} from "../leaderboards-constants.ts";
import type { TeamLeaderboardEntry } from "../leaderboards-types.ts";
import TeamStaffMenu from "./TeamStaffMenu.svelte";
interface Props {
entries: TeamLeaderboardEntry[];
season: number;
queryArgs: LeaderboardsQueryArgs;
leaderboardType: LeaderboardType;
showQualificationDividers?: boolean;
}
let {
entries,
season,
queryArgs,
leaderboardType,
showQualificationDividers: showQualificationDividersProp,
}: Props = $props();
const isStaff = $derived(hasRole("STAFF"));
const showStaffActions = $derived(isStaff && queryArgs.type !== "TEAM-ALL");
const showStaffActions = $derived(isStaff && leaderboardType !== "TEAM-ALL");
const isCurrentSeason = $derived(season === Seasons.current()?.nth);
const showQualificationDividers = $derived(
showQualificationDividersProp && isCurrentSeason && entries.length > 20,

View File

@@ -10,13 +10,16 @@ export const WEAPON_LEADERBOARD_MAX_SIZE = 100;
/** How many teams of the team leaderboard qualify, the divider being shown below the last of them. */
export const TEAM_LEADERBOARD_QUALIFYING_COUNT = 12;
export const LEADERBOARD_TYPES = [
export const SEASONAL_LEADERBOARD_TYPES = [
"USER",
"TEAM",
"TEAM-ALL",
...(weaponCategories.map(
(category) => `USER-${category.name}`,
) as `USER-${(typeof weaponCategories)[number]["name"]}`[]),
] as const;
export const XP_LEADERBOARD_TYPES = [
"XP-ALL",
...(rankedModesShort.map(
(mode) => `XP-MODE-${mode}`,
@@ -26,4 +29,20 @@ export const LEADERBOARD_TYPES = [
) as `XP-WEAPON-${(typeof mainWeaponIds)[number]}`[]),
] as const;
export const LEADERBOARD_TYPES = [
...SEASONAL_LEADERBOARD_TYPES,
...XP_LEADERBOARD_TYPES,
] as const;
export type LeaderboardType = (typeof LEADERBOARD_TYPES)[number];
export type SeasonalLeaderboardType =
(typeof SEASONAL_LEADERBOARD_TYPES)[number];
export type XPLeaderboardType = (typeof XP_LEADERBOARD_TYPES)[number];
export function isXPLeaderboardType(
type: LeaderboardType,
): type is XPLeaderboardType {
return type.startsWith("XP");
}

View File

@@ -1,12 +1,17 @@
import * as v from "valibot";
import type { SkillTeamIdentifier } from "#lib/features/mmr/mmr-utils.ts";
import { LEADERBOARD_TYPES } from "./leaderboards-constants.ts";
import {
SEASONAL_LEADERBOARD_TYPES,
XP_LEADERBOARD_TYPES,
} from "./leaderboards-constants.ts";
export const leaderboardsQuerySchema = v.object({
type: v.picklist(LEADERBOARD_TYPES),
type: v.picklist(SEASONAL_LEADERBOARD_TYPES),
season: v.nullable(v.pipe(v.number(), v.integer())),
});
export const xpLeaderboardTypeSchema = v.picklist(XP_LEADERBOARD_TYPES);
export type LeaderboardsQueryArgs = v.InferOutput<
typeof leaderboardsQuerySchema
>;

View File

@@ -1,4 +1,7 @@
import type { getLeaderboards } from "./leaderboards.remote.ts";
import type {
getLeaderboards,
getXPLeaderboard,
} from "./leaderboards.remote.ts";
export type LeaderboardsData = Awaited<ReturnType<typeof getLeaderboards>>;
@@ -10,8 +13,8 @@ export type TeamLeaderboardEntry = NonNullable<
LeaderboardsData["teamLeaderboard"]
>[number];
export type XPLeaderboardEntry = NonNullable<
LeaderboardsData["xpLeaderboard"]
export type XPLeaderboardEntry = Awaited<
ReturnType<typeof getXPLeaderboard>
>[number];
export type OwnEntryPeekData = NonNullable<LeaderboardsData["ownEntryPeek"]>;

View File

@@ -6,10 +6,15 @@ import type { weaponCategories } from "@sendou/in-game-lists/weapon-ids";
import { logger } from "@sendou/utils/logger";
import { getUser, requireUser } from "#lib/features/auth/user.server.ts";
import { requireRole } from "#lib/modules/permissions/guards.server.ts";
import { command, query, requested } from "$app/server";
import { IS_E2E_TEST_RUN } from "#lib/utils/e2e.ts";
import { command, prerender, query, requested } from "$app/server";
import * as Seasons from "../mmr/Seasons.ts";
import * as LeaderboardRepository from "./LeaderboardRepository.server.ts";
import { WEAPON_LEADERBOARD_MAX_SIZE } from "./leaderboards-constants.ts";
import {
WEAPON_LEADERBOARD_MAX_SIZE,
XP_LEADERBOARD_TYPES,
type XPLeaderboardType,
} from "./leaderboards-constants.ts";
import {
cachedFullUserLeaderboard,
cachedTeamLeaderboard,
@@ -21,6 +26,7 @@ import {
import {
leaderboardsQuerySchema,
teamLeaderboardEntrySchema,
xpLeaderboardTypeSchema,
} from "./leaderboards-schemas.ts";
export const getLeaderboards = query(
@@ -60,19 +66,6 @@ export const getLeaderboards = query(
const showOwnEntryPeek =
fullUserLeaderboard && !isWeaponLeaderboard && user;
const xpLeaderboard =
type === "XP-ALL"
? await LeaderboardRepository.findAllXPLeaderboard()
: type.startsWith("XP-MODE")
? await LeaderboardRepository.findModeXPLeaderboard(
type.split("-")[2] as RankedModeShort,
)
: type.startsWith("XP-WEAPON")
? await LeaderboardRepository.findWeaponXPLeaderboard(
Number(type.split("-")[2]) as MainWeaponId,
)
: null;
return {
userLeaderboard: filteredLeaderboard ?? userLeaderboard,
ownEntryPeek: showOwnEntryPeek
@@ -83,12 +76,35 @@ export const getLeaderboards = query(
})
: null,
teamLeaderboard,
xpLeaderboard,
season,
};
},
);
// prerendered data would be baked from the build machine's database, so the
// e2e build keeps this as a regular query against the per-worker test dbs
export const getXPLeaderboard = IS_E2E_TEST_RUN
? query(xpLeaderboardTypeSchema, findXPLeaderboard)
: prerender(xpLeaderboardTypeSchema, findXPLeaderboard, {
inputs: () => [...XP_LEADERBOARD_TYPES],
});
function findXPLeaderboard(type: XPLeaderboardType) {
if (type === "XP-ALL") {
return LeaderboardRepository.findAllXPLeaderboard();
}
if (type.startsWith("XP-MODE")) {
return LeaderboardRepository.findModeXPLeaderboard(
type.split("-")[2] as RankedModeShort,
);
}
return LeaderboardRepository.findWeaponXPLeaderboard(
Number(type.split("-")[2]) as MainWeaponId,
);
}
export const skipTeam = command(
teamLeaderboardEntrySchema,
async ({ season, identifier }) => {