mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-31 08:06:51 -05:00
* Initial * Saves preferences * Include TW * mapModePreferencesToModeList * mapPoolFromPreferences initial * Preference to map pool * Adjust seed * q.looking tests * adds about created map preferences to memento in the correct spot (two preferrers) * Failing test about modes * Mode preferences to memento * Remove old Plus Voting code * Fix seeding * find match by id via kysely * View map memento * Fix up map list generation logic * Mode memento info * Future match modes * Add TODO * Migration number * Migrate test DB * Remove old map pool code * createGroupFromPrevious new * Settings styling * VC to settings * Weapon pool * Add TODOs * Progress * Adjust mode exclusion policy * Progress * Progress * Progress * Notes in progress * Note feedback after submit * Textarea styling * Unskip tests * Note sorting failing test * Private note in Q * Ownerpicksmaps later * New bottom section * Mobile layout initial * Add basic match meta * Tabs initial * Sticky tab * Unseen messages in match page * Front page i18n * Settings i18n * Looking 18n * Chat i18n * Progress * Tranfer weapon pools script * Sticky on match page * Match page translations * i18n - tiers page * Preparing page i18n * Icon * Show add note right after report
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import type { InferResult } from "kysely";
|
|
import { jsonArrayFrom } from "kysely/helpers/sqlite";
|
|
import { db } from "~/db/sql";
|
|
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
|
import {
|
|
DEFAULT_LEADERBOARD_MAX_SIZE,
|
|
MATCHES_COUNT_NEEDED_FOR_LEADERBOARD,
|
|
} from "./leaderboards-constants";
|
|
import { ordinalToSp } from "../mmr";
|
|
|
|
function addPowers<T extends { ordinal: number }>(entries: T[]) {
|
|
return entries.map((entry) => ({
|
|
...entry,
|
|
power: ordinalToSp(entry.ordinal),
|
|
}));
|
|
}
|
|
|
|
function addPlacementRank<T>(entries: T[]) {
|
|
return entries.map((entry, index) => ({
|
|
...entry,
|
|
placementRank: index + 1,
|
|
}));
|
|
}
|
|
|
|
const teamLeaderboardBySeasonQuery = (season: number) =>
|
|
db
|
|
.selectFrom("Skill")
|
|
.innerJoin(
|
|
(eb) =>
|
|
eb
|
|
.selectFrom("Skill as InnerSkill")
|
|
.select(({ fn }) => [
|
|
"InnerSkill.identifier",
|
|
fn.max("InnerSkill.id").as("maxId"),
|
|
])
|
|
.where("season", "=", season)
|
|
.groupBy("InnerSkill.identifier")
|
|
.as("Latest"),
|
|
(join) =>
|
|
join
|
|
.onRef("Latest.identifier", "=", "Skill.identifier")
|
|
.onRef("Latest.maxId", "=", "Skill.id"),
|
|
)
|
|
.select((eb) => [
|
|
"Skill.id as entryId",
|
|
"Skill.ordinal",
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("SkillTeamUser")
|
|
.innerJoin("User", "SkillTeamUser.userId", "User.id")
|
|
.select(COMMON_USER_FIELDS)
|
|
.whereRef("SkillTeamUser.skillId", "=", "Skill.id"),
|
|
).as("members"),
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("SkillTeamUser")
|
|
.innerJoin("User", "SkillTeamUser.userId", "User.id")
|
|
.leftJoin("TeamMember", "TeamMember.userId", "User.id")
|
|
.leftJoin("Team", "Team.id", "TeamMember.teamId")
|
|
.leftJoin(
|
|
"UserSubmittedImage",
|
|
"UserSubmittedImage.id",
|
|
"Team.avatarImgId",
|
|
)
|
|
.select([
|
|
"Team.id",
|
|
"Team.name",
|
|
"UserSubmittedImage.url as avatarUrl",
|
|
"Team.customUrl",
|
|
])
|
|
.whereRef("SkillTeamUser.skillId", "=", "Skill.id"),
|
|
).as("teams"),
|
|
])
|
|
.where("Skill.matchesCount", ">=", MATCHES_COUNT_NEEDED_FOR_LEADERBOARD)
|
|
.where("Skill.season", "=", season)
|
|
.orderBy("Skill.ordinal", "desc")
|
|
.limit(DEFAULT_LEADERBOARD_MAX_SIZE);
|
|
type TeamLeaderboardBySeasonQueryReturnType = InferResult<
|
|
ReturnType<typeof teamLeaderboardBySeasonQuery>
|
|
>;
|
|
export async function teamLeaderboardBySeason(season: number) {
|
|
const entries = await teamLeaderboardBySeasonQuery(season).execute();
|
|
|
|
const oneEntryPerUser = filterOneEntryPerUser(entries);
|
|
const withSharedTeam = resolveSharedTeam(oneEntryPerUser);
|
|
const withPower = addPowers(withSharedTeam);
|
|
return addPlacementRank(withPower);
|
|
}
|
|
|
|
function filterOneEntryPerUser(
|
|
entries: TeamLeaderboardBySeasonQueryReturnType,
|
|
) {
|
|
const encounteredUserIds = new Set<number>();
|
|
return entries.filter((entry) => {
|
|
if (entry.members.some((m) => encounteredUserIds.has(m.id))) {
|
|
return false;
|
|
}
|
|
|
|
for (const member of entry.members) {
|
|
encounteredUserIds.add(member.id);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function resolveSharedTeam(entries: ReturnType<typeof filterOneEntryPerUser>) {
|
|
return entries.map(({ teams, ...entry }) => {
|
|
const sharedSameTeam =
|
|
teams.length === 4 && teams.every((team) => team.id === teams[0].id);
|
|
|
|
return {
|
|
...entry,
|
|
team: sharedSameTeam ? teams[0] : undefined,
|
|
};
|
|
});
|
|
}
|