mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Add friend code & seeding power to public tournament teams API Closes #2065
This commit is contained in:
parent
d035873a09
commit
daf7b3fef7
|
|
@ -3,7 +3,10 @@ import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
|||
import { cors } from "remix-utils/cors";
|
||||
import { z } from "zod";
|
||||
import { db } from "~/db/sql";
|
||||
import { ordinalToSp } from "~/features/mmr/mmr-utils";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import i18next from "~/modules/i18n/i18next.server";
|
||||
import { nullifyingAvg } from "~/utils/arrays";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import { parseParams } from "~/utils/remix.server";
|
||||
import { userSubmittedImage } from "~/utils/urls";
|
||||
|
|
@ -66,6 +69,16 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
eb
|
||||
.selectFrom("TournamentTeamMember")
|
||||
.innerJoin("User", "User.id", "TournamentTeamMember.userId")
|
||||
.leftJoin("SeedingSkill as RankedSeedingSkill", (join) =>
|
||||
join
|
||||
.onRef("User.id", "=", "RankedSeedingSkill.userId")
|
||||
.on("RankedSeedingSkill.type", "=", "RANKED"),
|
||||
)
|
||||
.leftJoin("SeedingSkill as UnrankedSeedingSkill", (join) =>
|
||||
join
|
||||
.onRef("User.id", "=", "UnrankedSeedingSkill.userId")
|
||||
.on("UnrankedSeedingSkill.type", "=", "UNRANKED"),
|
||||
)
|
||||
.select([
|
||||
"User.id as userId",
|
||||
"User.username",
|
||||
|
|
@ -75,6 +88,8 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
"TournamentTeamMember.inGameName",
|
||||
"TournamentTeamMember.isOwner",
|
||||
"TournamentTeamMember.createdAt",
|
||||
"RankedSeedingSkill.ordinal as rankedOrdinal",
|
||||
"UnrankedSeedingSkill.ordinal as unrankedOrdinal",
|
||||
])
|
||||
.whereRef(
|
||||
"TournamentTeamMember.tournamentTeamId",
|
||||
|
|
@ -94,6 +109,8 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
.orderBy("TournamentTeam.createdAt asc")
|
||||
.execute();
|
||||
|
||||
const friendCodes = await TournamentRepository.friendCodesByTournamentId(id);
|
||||
|
||||
const logoUrl = (team: (typeof teams)[number]) => {
|
||||
const url = team.team?.logoUrl ?? team.avatarUrl;
|
||||
if (!url) return null;
|
||||
|
|
@ -113,6 +130,14 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
seed: team.seed,
|
||||
registeredAt: databaseTimestampToDate(team.createdAt).toISOString(),
|
||||
checkedIn: Boolean(team.checkedInAt),
|
||||
seedingPower: {
|
||||
ranked: toSeedingPowerSP(
|
||||
team.members.map((member) => member.rankedOrdinal),
|
||||
),
|
||||
unranked: toSeedingPowerSP(
|
||||
team.members.map((member) => member.unrankedOrdinal),
|
||||
),
|
||||
},
|
||||
members: team.members.map((member) => {
|
||||
return {
|
||||
userId: member.userId,
|
||||
|
|
@ -124,6 +149,7 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
: null,
|
||||
captain: Boolean(member.isOwner),
|
||||
inGameName: member.inGameName,
|
||||
friendCode: friendCodes[member.userId],
|
||||
joinedAt: databaseTimestampToDate(member.createdAt).toISOString(),
|
||||
};
|
||||
}),
|
||||
|
|
@ -145,3 +171,13 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
|
||||
return await cors(request, json(result));
|
||||
};
|
||||
|
||||
function toSeedingPowerSP(ordinals: (number | null)[]) {
|
||||
const avg = nullifyingAvg(
|
||||
ordinals.filter((ordinal) => typeof ordinal === "number"),
|
||||
);
|
||||
|
||||
if (typeof avg !== "number") return null;
|
||||
|
||||
return ordinalToSp(avg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,15 @@ export type GetTournamentTeamsResponse = Array<{
|
|||
logoUrl: string | null;
|
||||
seed: number | null;
|
||||
mapPool: Array<StageWithMode> | null;
|
||||
/**
|
||||
* Seeding power is a non-resetting MMR value that is used for sendou.ink's autoseeding capabilities.
|
||||
* It is calculated as the average of the team's members' seeding power.
|
||||
* Ranked and unranked tournaments contribute to different seeding power values.
|
||||
*/
|
||||
seedingPower: {
|
||||
ranked: number | null;
|
||||
unranked: number | null;
|
||||
};
|
||||
members: Array<{
|
||||
userId: number;
|
||||
/**
|
||||
|
|
@ -141,6 +150,12 @@ export type GetTournamentTeamsResponse = Array<{
|
|||
* @example "Sendou#2955"
|
||||
*/
|
||||
inGameName: string | null;
|
||||
/**
|
||||
* Switch friend code used for identification purposes.
|
||||
*
|
||||
* @example "1234-5678-9101"
|
||||
*/
|
||||
friendCode: string;
|
||||
/**
|
||||
* @example "2024-01-12T20:00:00.000Z"
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import type {
|
|||
import * as Progression from "~/features/tournament-bracket/core/Progression";
|
||||
import { Status } from "~/modules/brackets-model";
|
||||
import { modesShort } from "~/modules/in-game-lists";
|
||||
import { nullFilledArray } from "~/utils/arrays";
|
||||
import { nullFilledArray, nullifyingAvg } from "~/utils/arrays";
|
||||
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { COMMON_USER_FIELDS, userChatNameColor } from "~/utils/kysely.server";
|
||||
import type { Unwrapped } from "~/utils/types";
|
||||
|
|
@ -317,11 +317,6 @@ export async function findById(id: number) {
|
|||
};
|
||||
}
|
||||
|
||||
function nullifyingAvg(values: number[]) {
|
||||
if (values.length === 0) return null;
|
||||
return values.reduce((acc, cur) => acc + cur, 0) / values.length;
|
||||
}
|
||||
|
||||
export async function findChildTournaments(parentTournamentId: number) {
|
||||
const rows = await db
|
||||
.selectFrom("Tournament")
|
||||
|
|
|
|||
|
|
@ -97,3 +97,14 @@ export function pickRandomItem<T>(array: T[]): T {
|
|||
export function filterOutFalsy<T>(arr: (T | null | undefined)[]): T[] {
|
||||
return arr.filter(Boolean) as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average of an array of numbers. If the array is empty, returns null.
|
||||
*
|
||||
* @param values - An array of numbers to calculate the average of.
|
||||
* @returns The average of the numbers in the array, or null if the array is empty.
|
||||
*/
|
||||
export function nullifyingAvg(values: number[]) {
|
||||
if (values.length === 0) return null;
|
||||
return values.reduce((acc, cur) => acc + cur, 0) / values.length;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user