mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-20 10:18:24 -05:00
* Initial * CSS lint * Test CI * Add 1v1, 2v2, and 3v3 Tags (#1771) * Initial * CSS lint * Test CI * Rename step --------- Co-authored-by: xi <104683822+ximk@users.noreply.github.com>
126 lines
3.3 KiB
TypeScript
126 lines
3.3 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 { ordinalToSp } from "../mmr/mmr-utils";
|
|
import {
|
|
DEFAULT_LEADERBOARD_MAX_SIZE,
|
|
MATCHES_COUNT_NEEDED_FOR_LEADERBOARD,
|
|
} from "./leaderboards-constants";
|
|
|
|
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,
|
|
onlyOneEntryPerUser,
|
|
}: {
|
|
season: number;
|
|
onlyOneEntryPerUser: boolean;
|
|
}) {
|
|
const entries = await teamLeaderboardBySeasonQuery(season).execute();
|
|
|
|
const oneEntryPerUser = onlyOneEntryPerUser
|
|
? filterOneEntryPerUser(entries)
|
|
: 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,
|
|
};
|
|
});
|
|
}
|