234 lines
5.5 KiB
TypeScript
Executable File
234 lines
5.5 KiB
TypeScript
Executable File
import { Injectable, Logger } from "@nestjs/common";
|
|
import { PrismaService } from "../../database/prisma.service";
|
|
import { Sport } from "@prisma/client";
|
|
|
|
@Injectable()
|
|
export class LeaguesService {
|
|
private readonly logger = new Logger(LeaguesService.name);
|
|
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
/**
|
|
* Get all countries
|
|
*/
|
|
async findAllCountries() {
|
|
return this.prisma.country.findMany({
|
|
orderBy: { name: "asc" },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get country by ID
|
|
*/
|
|
async findCountryById(id: string) {
|
|
return this.prisma.country.findUnique({
|
|
where: { id },
|
|
include: { leagues: true },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all leagues
|
|
*/
|
|
async findAllLeagues(sport?: Sport) {
|
|
return this.prisma.league.findMany({
|
|
where: sport ? { sport } : undefined,
|
|
include: { country: true },
|
|
orderBy: { name: "asc" },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get league by ID
|
|
*/
|
|
async findLeagueById(id: string) {
|
|
return this.prisma.league.findUnique({
|
|
where: { id },
|
|
include: { country: true },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get leagues by country
|
|
*/
|
|
async findLeaguesByCountry(countryId: string, sport?: Sport) {
|
|
return this.prisma.league.findMany({
|
|
where: {
|
|
countryId,
|
|
...(sport ? { sport } : {}),
|
|
},
|
|
include: { country: true },
|
|
orderBy: { name: "asc" },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all teams
|
|
*/
|
|
async findAllTeams(sport?: Sport, search?: string) {
|
|
return this.prisma.team.findMany({
|
|
where: {
|
|
...(sport ? { sport } : {}),
|
|
...(search ? { name: { contains: search, mode: "insensitive" } } : {}),
|
|
},
|
|
orderBy: { name: "asc" },
|
|
take: 100,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get team by ID
|
|
*/
|
|
async findTeamById(id: string) {
|
|
return this.prisma.team.findUnique({
|
|
where: { id },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Search teams by name
|
|
*/
|
|
async searchTeams(name: string, sport?: Sport) {
|
|
return this.prisma.team.findMany({
|
|
where: {
|
|
name: { contains: name, mode: "insensitive" },
|
|
...(sport ? { sport } : {}),
|
|
},
|
|
take: 20,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get team's matches (past + upcoming) with pagination
|
|
*/
|
|
async getTeamRecentMatches(
|
|
teamId: string,
|
|
page: number = 1,
|
|
limit: number = 20,
|
|
season?: string
|
|
) {
|
|
const skip = (page - 1) * limit;
|
|
const where: any = {
|
|
OR: [{ homeTeamId: teamId }, { awayTeamId: teamId }],
|
|
};
|
|
|
|
if (season) {
|
|
// season format expected: "2024-2025"
|
|
const parts = season.split("-");
|
|
if (parts.length === 2) {
|
|
const startYear = parseInt(parts[0], 10);
|
|
const endYear = parseInt(parts[1], 10);
|
|
|
|
if (!isNaN(startYear) && !isNaN(endYear)) {
|
|
// Season starts August 1st of startYear
|
|
const startDate = new Date(Date.UTC(startYear, 7, 1)).getTime();
|
|
// Season ends July 31st of endYear
|
|
const endDate = new Date(Date.UTC(endYear, 6, 31, 23, 59, 59, 999)).getTime();
|
|
|
|
where.mstUtc = {
|
|
gte: startDate,
|
|
lte: endDate,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
const [data, total] = await this.prisma.$transaction([
|
|
this.prisma.match.findMany({
|
|
where,
|
|
include: {
|
|
homeTeam: true,
|
|
awayTeam: true,
|
|
league: { include: { country: true } },
|
|
},
|
|
orderBy: { mstUtc: "desc" },
|
|
skip,
|
|
take: limit,
|
|
}),
|
|
this.prisma.match.count({ where }),
|
|
]);
|
|
|
|
return {
|
|
data: data.map((m) => ({
|
|
id: m.id,
|
|
matchName: m.matchName,
|
|
matchSlug: m.matchSlug,
|
|
mstUtc: Number(m.mstUtc),
|
|
scoreHome: m.scoreHome,
|
|
scoreAway: m.scoreAway,
|
|
status: m.status,
|
|
state: m.state,
|
|
homeTeamName: m.homeTeam?.name,
|
|
homeTeamLogo: m.homeTeamId
|
|
? `https://file.mackolikfeeds.com/teams/${m.homeTeamId}`
|
|
: null,
|
|
awayTeamName: m.awayTeam?.name,
|
|
awayTeamLogo: m.awayTeamId
|
|
? `https://file.mackolikfeeds.com/teams/${m.awayTeamId}`
|
|
: null,
|
|
leagueName: m.league?.name,
|
|
countryName: m.league?.country?.name,
|
|
})),
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(total / limit),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get head-to-head matches between two teams
|
|
*/
|
|
async getHeadToHead(teamId1: string, teamId2: string, limit: number = 10) {
|
|
const matches = await this.prisma.match.findMany({
|
|
where: {
|
|
OR: [
|
|
{ homeTeamId: teamId1, awayTeamId: teamId2 },
|
|
{ homeTeamId: teamId2, awayTeamId: teamId1 },
|
|
],
|
|
state: "postGame", // Finished matches are stored as "postGame"
|
|
},
|
|
include: {
|
|
homeTeam: true,
|
|
awayTeam: true,
|
|
league: true,
|
|
},
|
|
orderBy: { mstUtc: "desc" },
|
|
take: limit,
|
|
});
|
|
|
|
// Calculate statistics
|
|
let team1Wins = 0;
|
|
let team2Wins = 0;
|
|
let draws = 0;
|
|
|
|
matches.forEach((match) => {
|
|
const homeScore = Number(match.scoreHome ?? -1);
|
|
const awayScore = Number(match.scoreAway ?? -1);
|
|
|
|
// Skip matches without scores
|
|
if (homeScore === -1 || awayScore === -1) return;
|
|
|
|
const isTeam1Home = match.homeTeamId === teamId1;
|
|
|
|
if (homeScore === awayScore) {
|
|
draws++;
|
|
} else if (
|
|
(isTeam1Home && homeScore > awayScore) ||
|
|
(!isTeam1Home && awayScore > homeScore)
|
|
) {
|
|
team1Wins++;
|
|
} else {
|
|
team2Wins++;
|
|
}
|
|
});
|
|
|
|
return {
|
|
matches,
|
|
team1Wins,
|
|
team2Wins,
|
|
draws,
|
|
};
|
|
}
|
|
}
|