Merge branch 'main' into caro/badges-stats

This commit is contained in:
2025-10-16 11:51:12 -05:00
56 changed files with 2759 additions and 292 deletions

View File

@@ -99,7 +99,7 @@ export function createBadgesService(pb: PocketBase) {
async calculateMatchBadgeProgress(playerId: string, badge: Badge): Promise<number> {
const criteria = badge.criteria;
const stats = await pb.collection("player_stats").getFirstListItem<PlayerStats>(
const stats = await pb.collection("player_mainline_stats").getFirstListItem<PlayerStats>(
`player_id = "${playerId}"`
).catch(() => null);
@@ -111,8 +111,8 @@ export function createBadgesService(pb: PocketBase) {
if (criteria.overtime_matches !== undefined || criteria.overtime_wins !== undefined) {
const matches = await pb.collection("matches").getFullList({
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended" && ot_count > 0`,
expand: 'home,away,home.players,away.players',
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended" && ot_count > 0 && (tournament.regional = false || tournament.regional = null)`,
expand: 'tournament,home,away,home.players,away.players',
});
if (criteria.overtime_matches !== undefined) {
@@ -139,8 +139,8 @@ export function createBadgesService(pb: PocketBase) {
if (criteria.margin_of_victory !== undefined) {
const matches = await pb.collection("matches").getFullList({
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended"`,
expand: 'home,away,home.players,away.players',
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended" && (tournament.regional = false || tournament.regional = null)`,
expand: 'tournament,home,away,home.players,away.players',
});
const bigWins = matches.filter(m => {
@@ -167,7 +167,7 @@ export function createBadgesService(pb: PocketBase) {
const criteria = badge.criteria;
const matches = await pb.collection("matches").getFullList({
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended"`,
filter: `(home.players.id ?~ "${playerId}" || away.players.id ?~ "${playerId}") && status = "ended" && (tournament.regional = false || tournament.regional = null)`,
expand: 'tournament,home,away,home.players,away.players',
});
@@ -217,8 +217,8 @@ export function createBadgesService(pb: PocketBase) {
for (const tournamentId of tournamentIds) {
const tournamentMatches = await pb.collection("matches").getFullList({
filter: `tournament = "${tournamentId}" && status = "ended"`,
expand: 'home,away,home.players,away.players',
filter: `tournament = "${tournamentId}" && status = "ended" && (tournament.regional = false || tournament.regional = null)`,
expand: 'tournament,home,away,home.players,away.players',
});
const winnersMatches = tournamentMatches.filter(m => !m.is_losers_bracket);
@@ -249,8 +249,8 @@ export function createBadgesService(pb: PocketBase) {
for (const tournamentId of tournamentIds) {
const tournamentMatches = await pb.collection("matches").getFullList({
filter: `tournament = "${tournamentId}" && status = "ended"`,
expand: 'home,away,home.players,away.players',
filter: `tournament = "${tournamentId}" && status = "ended" && (tournament.regional = false || tournament.regional = null)`,
expand: 'tournament,home,away,home.players,away.players',
});
if (criteria.placement === 2) {
@@ -301,6 +301,7 @@ export function createBadgesService(pb: PocketBase) {
if (criteria.tournament_record !== undefined) {
const tournaments = await pb.collection("tournaments").getFullList({
filter: 'regional = false || regional = null',
sort: 'start_time',
});
@@ -352,6 +353,7 @@ export function createBadgesService(pb: PocketBase) {
if (criteria.consecutive_wins !== undefined) {
const tournaments = await pb.collection("tournaments").getFullList({
filter: 'regional = false || regional = null',
sort: 'start_time',
});

View File

@@ -32,7 +32,7 @@ export function createMatchesService(pb: PocketBase) {
},
async createMatch(data: MatchInput): Promise<Match> {
logger.info("PocketBase | Creating match", data);
// logger.info("PocketBase | Creating match", data);
const result = await pb.collection("matches").create<Match>(data);
return result;
},
@@ -71,5 +71,75 @@ export function createMatchesService(pb: PocketBase) {
matches.map((match) => pb.collection("matches").delete(match.id))
);
},
async getMatchesBetweenPlayers(player1Id: string, player2Id: string): Promise<Match[]> {
logger.info("PocketBase | Getting matches between players", { player1Id, player2Id });
const player1Teams = await pb.collection("teams").getFullList({
filter: `players ~ "${player1Id}"`,
fields: "id",
});
const player2Teams = await pb.collection("teams").getFullList({
filter: `players ~ "${player2Id}"`,
fields: "id",
});
const player1TeamIds = player1Teams.map(t => t.id);
const player2TeamIds = player2Teams.map(t => t.id);
if (player1TeamIds.length === 0 || player2TeamIds.length === 0) {
return [];
}
const allTeamIds = [...new Set([...player1TeamIds, ...player2TeamIds])];
const batchSize = 10;
const allMatches: any[] = [];
for (let i = 0; i < allTeamIds.length; i += batchSize) {
const batch = allTeamIds.slice(i, i + batchSize);
const teamFilters = batch.map(id => `home="${id}" || away="${id}"`).join(' || ');
const results = await pb.collection("matches").getFullList({
filter: teamFilters,
expand: "tournament, home, away, home.players, away.players",
sort: "-created",
});
allMatches.push(...results);
}
const uniqueMatches = Array.from(
new Map(allMatches.map(m => [m.id, m])).values()
);
return uniqueMatches
.filter(match => {
const homeTeamId = typeof match.home === 'string' ? match.home : match.home?.id;
const awayTeamId = typeof match.away === 'string' ? match.away : match.away?.id;
const player1InHome = player1TeamIds.includes(homeTeamId);
const player1InAway = player1TeamIds.includes(awayTeamId);
const player2InHome = player2TeamIds.includes(homeTeamId);
const player2InAway = player2TeamIds.includes(awayTeamId);
return (player1InHome && player2InAway) || (player1InAway && player2InHome);
})
.map(match => transformMatch(match));
},
async getMatchesBetweenTeams(team1Id: string, team2Id: string): Promise<Match[]> {
logger.info("PocketBase | Getting matches between teams", { team1Id, team2Id });
const filter = `(home="${team1Id}" && away="${team2Id}") || (home="${team2Id}" && away="${team1Id}")`;
const results = await pb.collection("matches").getFullList({
filter,
expand: "tournament, home, away, home.players, away.players",
sort: "-created",
});
return results.map(match => transformMatch(match));
},
};
}

View File

@@ -8,7 +8,6 @@ import type {
import type { Match } from "@/features/matches/types";
import { transformPlayer, transformPlayerInfo, transformMatch } from "@/lib/pocketbase/util/transform-types";
import PocketBase from "pocketbase";
import { DataFetchOptions } from "./base";
export function createPlayersService(pb: PocketBase) {
return {
@@ -65,15 +64,45 @@ export function createPlayersService(pb: PocketBase) {
return result.map(transformPlayer);
},
async getPlayerStats(playerId: string): Promise<PlayerStats> {
const result = await pb.collection("player_stats").getFirstListItem<PlayerStats>(
`player_id = "${playerId}"`
);
return result;
async getPlayerStats(playerId: string, viewType: 'all' | 'mainline' | 'regional' = 'all'): Promise<PlayerStats> {
try {
const collectionMap = {
all: 'player_stats',
mainline: 'player_mainline_stats',
regional: 'player_regional_stats',
};
const result = await pb.collection(collectionMap[viewType]).getFirstListItem<PlayerStats>(
`player_id = "${playerId}"`
);
return result;
} catch (error) {
return {
id: "",
player_id: playerId,
player_name: "",
matches: 0,
tournaments: 0,
wins: 0,
losses: 0,
total_cups_made: 0,
total_cups_against: 0,
win_percentage: 0,
avg_cups_per_match: 0,
margin_of_victory: 0,
margin_of_loss: 0,
};
}
},
async getAllPlayerStats(): Promise<PlayerStats[]> {
const result = await pb.collection("player_stats").getFullList<PlayerStats>({
async getAllPlayerStats(viewType: 'all' | 'mainline' | 'regional' = 'all'): Promise<PlayerStats[]> {
const collectionMap = {
all: 'player_stats',
mainline: 'player_mainline_stats',
regional: 'player_regional_stats',
};
const result = await pb.collection(collectionMap[viewType]).getFullList<PlayerStats>({
sort: "-win_percentage,-total_cups_made",
});
return result;
@@ -148,5 +177,13 @@ export function createPlayersService(pb: PocketBase) {
return allPlayers.map(transformPlayer);
}
},
async getPlayersActivity(): Promise<Player[]> {
const result = await pb.collection("players").getFullList<Player>({
sort: "-last_activity",
fields: "id,first_name,last_name,auth_id,last_activity",
});
return result.map(transformPlayer);
},
};
}

View File

@@ -34,7 +34,7 @@ export function createTournamentsService(pb: PocketBase) {
.getFirstListItem('',
{
expand: "teams, teams.players, matches, matches.tournament, matches.home, matches.away, matches.home.players, matches.away.players",
sort: "-created",
sort: "-start_time",
}
);
@@ -52,7 +52,7 @@ export function createTournamentsService(pb: PocketBase) {
.collection("tournaments")
.getFullList({
expand: "teams,teams.players,matches",
sort: "-created",
sort: "-start_time",
});
const tournamentsWithStats = await Promise.all(result.map(async (tournament) => {

View File

@@ -1,4 +1,3 @@
import { Reaction } from "@/features/matches/server";
import { Match } from "@/features/matches/types";
import { Player, PlayerInfo } from "@/features/players/types";
import { Team, TeamInfo } from "@/features/teams/types";
@@ -25,7 +24,8 @@ export function transformTeamInfo(record: any): TeamInfo {
primary_color: record.primary_color,
accent_color: record.accent_color,
players,
logo: record.logo
logo: record.logo,
private: record.private || false,
};
}
@@ -107,6 +107,7 @@ export const transformTournamentInfo = (record: any): TournamentInfo => {
end_time: record.end_time,
logo: record.logo,
glitch_logo: record.glitch_logo,
regional: record.regional || false,
first_place,
second_place,
third_place,
@@ -116,6 +117,7 @@ export const transformTournamentInfo = (record: any): TournamentInfo => {
export function transformPlayer(record: any): Player {
const teams =
record.expand?.teams
?.filter((team: any) => !team.private)
?.sort((a: any, b: any) =>
new Date(a.created) < new Date(b.created) ? -1 : 0
)
@@ -128,19 +130,13 @@ export function transformPlayer(record: any): Player {
auth_id: record.auth_id,
created: record.created,
updated: record.updated,
last_activity: record.last_activity,
teams,
};
}
export function transformFreeAgent(record: any) {
const player = record.expand?.player ? transformPlayerInfo(record.expand.player) : undefined;
const tournaments =
record.expand?.tournaments
?.sort((a: any, b: any) =>
new Date(a.created!) < new Date(b.created!) ? -1 : 0
)
?.map(transformTournamentInfo) ?? [];
return {
id: record.id as string,
phone: record.phone as string,
@@ -179,6 +175,7 @@ export function transformTeam(record: any): Team {
updated: record.updated,
players,
tournaments,
private: record.private || false,
};
}
@@ -263,6 +260,7 @@ export function transformTournament(record: any, isAdmin: boolean = false): Tour
end_time: record.end_time,
created: record.created,
updated: record.updated,
regional: record.regional || false,
teams,
matches,
first_place,