gg65
Deploy Iddaai Backend / build-and-deploy (push) Successful in 1m5s

This commit is contained in:
2026-06-07 22:50:33 +03:00
parent 42b6c7ce43
commit c3e44ee697
3 changed files with 164 additions and 152 deletions
+16
View File
@@ -111,6 +111,22 @@ export class MatchesController {
return this.matchesService.getActiveLeagues(sport || Sport.FOOTBALL);
}
/**
* GET /matches/:id/odds-movement
* Opening→closing odds movement per market/selection (from live_odds_history)
*/
@Public()
@Get(":id/odds-movement")
@ApiOperation({ summary: "Opening→closing odds movement for a match" })
@ApiParam({ name: "id", description: "Match ID" })
@ApiResponse({ status: 200, description: "{ market: { selection: { open, close } } }" })
async getOddsMovement(@Param("id") id: string) {
if (!id) {
throw new BadRequestException("Match ID is required");
}
return this.matchesService.getOddsMovement(id);
}
/**
* GET /matches/:id
* Get full match details
+45
View File
@@ -28,6 +28,51 @@ export class MatchesService {
this.loadTopLeagues();
}
/**
* Per-match odds movement (opening→closing) from live_odds_history.
* Returns { [market]: { [selection]: { open, close } } } with the same
* Turkish market/selection labels used in match.odds, so the UI can line
* them up directly. Returns {} if there is no data or the table is absent.
*/
async getOddsMovement(
matchId: string,
): Promise<Record<string, Record<string, { open: number; close: number }>>> {
try {
const rows = await this.prisma.$queryRawUnsafe<
Array<{
market: string;
selection: string;
open: number | null;
close: number | null;
}>
>(
`SELECT market, selection,
(array_agg(new_value ORDER BY change_time ASC))[1] AS open,
(array_agg(new_value ORDER BY change_time DESC))[1] AS close
FROM live_odds_history
WHERE match_id = $1
GROUP BY market, selection`,
matchId,
);
const out: Record<
string,
Record<string, { open: number; close: number }>
> = {};
for (const r of rows) {
if (r.open == null || r.close == null) continue;
(out[r.market] ??= {})[r.selection] = {
open: Number(r.open),
close: Number(r.close),
};
}
return out;
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
this.logger.warn(`getOddsMovement failed for ${matchId}: ${msg}`);
return {};
}
}
private loadTopLeagues() {
try {
const filePath = path.join(process.cwd(), "top_leagues.json");