sendou.ink/app/features/leaderboards/LeaderboardRepository.server.ts
Kalle 7dec8c572e
SendouQ Season 2 changes (#1542)
* 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
2023-11-30 20:57:06 +02:00

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,
};
});
}