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) => {