gg
Deploy Iddaai Backend / build-and-deploy (push) Failing after 2m15s

This commit is contained in:
2026-05-11 20:50:31 +03:00
parent 70fdc066c7
commit 4dcc4ced50
8 changed files with 718 additions and 192 deletions
+4
View File
@@ -1,4 +1,5 @@
import { Controller, Post, Body, HttpCode } from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
import { I18n, I18nContext } from "nestjs-i18n";
import { ApiTags, ApiOperation, ApiOkResponse } from "@nestjs/swagger";
import { AuthService } from "./auth.service";
@@ -21,6 +22,7 @@ export class AuthController {
@Post("register")
@Public()
@Throttle({ default: { limit: 10, ttl: 60000 } })
@HttpCode(200)
@ApiOperation({ summary: "Register a new user" })
@ApiOkResponse({
@@ -37,6 +39,7 @@ export class AuthController {
@Post("login")
@Public()
@Throttle({ default: { limit: 5, ttl: 60000 } })
@HttpCode(200)
@ApiOperation({ summary: "Login with email and password" })
@ApiOkResponse({ description: "Login successful", type: TokenResponseDto })
@@ -50,6 +53,7 @@ export class AuthController {
@Post("refresh")
@Public()
@Throttle({ default: { limit: 10, ttl: 60000 } })
@HttpCode(200)
@ApiOperation({ summary: "Refresh access token" })
@ApiOkResponse({
@@ -1,4 +1,5 @@
import { Injectable, Logger } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../../../database/prisma.service";
// ─────────────────────────────────────────────────────────────
@@ -108,16 +109,21 @@ export class FrequencyEngineService {
venue: "home" | "away",
oddsBand: string,
): Promise<TeamFrequencyRow | null> {
const venueColumn = venue === "home" ? "m.home_team_id" : "m.away_team_id";
const oddsSelection = venue === "home" ? "'1'" : "'2'";
// venue is a typed literal ("home"|"away") — safe to use with Prisma.raw()
const venueColumnRaw = Prisma.raw(
venue === "home" ? "m.home_team_id" : "m.away_team_id",
);
const oddsSelectionValue = venue === "home" ? "1" : "2";
const winConditionRaw = Prisma.raw(
venue === "home" ? "score_home > score_away" : "score_away > score_home",
);
const bandRange = this.parseBandRange(oddsBand);
if (!bandRange) {
return null;
}
const rows = await this.prisma.$queryRawUnsafe<TeamFrequencyRow[]>(
`
const rows = await this.prisma.$queryRaw<TeamFrequencyRow[]>(Prisma.sql`
WITH team_matches AS (
SELECT
m.id AS match_id,
@@ -127,32 +133,26 @@ export class FrequencyEngineService {
CAST(os.odd_value AS DECIMAL) AS team_odds
FROM matches m
JOIN odd_categories oc ON oc.match_id = m.id AND oc.name = 'Maç Sonucu'
JOIN odd_selections os ON os.odd_category_db_id = oc.db_id AND os.name = ${oddsSelection}
JOIN odd_selections os ON os.odd_category_db_id = oc.db_id AND os.name = ${oddsSelectionValue}
WHERE m.status = 'FT'
AND m.score_home IS NOT NULL
AND ${venueColumn} = $1
AND CAST(os.odd_value AS DECIMAL) >= $2
AND CAST(os.odd_value AS DECIMAL) < $3
AND ${venueColumnRaw} = ${teamId}
AND CAST(os.odd_value AS DECIMAL) >= ${bandRange.min}
AND CAST(os.odd_value AS DECIMAL) < ${bandRange.max}
)
SELECT
$1::text AS team_id,
$4::text AS venue,
$5::text AS odds_band,
${teamId}::text AS team_id,
${venue}::text AS venue,
${oddsBand}::text AS odds_band,
COUNT(*)::int AS total_matches,
COALESCE(AVG(CASE WHEN total_goals > 1 THEN 1.0 ELSE 0.0 END), 0)::float AS ou15_rate,
COALESCE(AVG(CASE WHEN total_goals > 2 THEN 1.0 ELSE 0.0 END), 0)::float AS ou25_rate,
COALESCE(AVG(CASE WHEN total_goals > 3 THEN 1.0 ELSE 0.0 END), 0)::float AS ou35_rate,
COALESCE(AVG(CASE WHEN score_home > 0 AND score_away > 0 THEN 1.0 ELSE 0.0 END), 0)::float AS btts_rate,
COALESCE(AVG(CASE WHEN ${venue === "home" ? "score_home > score_away" : "score_away > score_home"} THEN 1.0 ELSE 0.0 END), 0)::float AS win_rate,
COALESCE(AVG(CASE WHEN ${winConditionRaw} THEN 1.0 ELSE 0.0 END), 0)::float AS win_rate,
COALESCE(AVG(total_goals), 0)::float AS avg_goals
FROM team_matches
`,
teamId,
bandRange.min,
bandRange.max,
venue,
oddsBand,
);
`);
if (!rows.length || rows[0].total_matches < MIN_MATCHES) {
return null;
@@ -335,8 +335,7 @@ export class FrequencyEngineService {
if (matchIds && matchIds.length > 0) {
// Belirli maçlar istendi
return this.prisma.$queryRawUnsafe<UpcomingMatchRow[]>(
`
return this.prisma.$queryRaw<UpcomingMatchRow[]>(Prisma.sql`
SELECT
lm.id AS match_id,
lm.home_team_id,
@@ -359,18 +358,15 @@ export class FrequencyEngineService {
LEFT JOIN teams ht ON lm.home_team_id = ht.id
LEFT JOIN teams at ON lm.away_team_id = at.id
LEFT JOIN leagues l ON lm.league_id = l.id
WHERE lm.id = ANY($1)
WHERE lm.id = ANY(${matchIds})
AND lm.odds IS NOT NULL
AND lm.odds != 'null'::jsonb
ORDER BY lm.mst_utc ASC
`,
matchIds,
);
`);
}
// Otomatik: yaklaşan tüm maçlar
return this.prisma.$queryRawUnsafe<UpcomingMatchRow[]>(
`
return this.prisma.$queryRaw<UpcomingMatchRow[]>(Prisma.sql`
SELECT
lm.id AS match_id,
lm.home_team_id,
@@ -393,26 +389,22 @@ export class FrequencyEngineService {
LEFT JOIN teams ht ON lm.home_team_id = ht.id
LEFT JOIN teams at ON lm.away_team_id = at.id
LEFT JOIN leagues l ON lm.league_id = l.id
WHERE lm.mst_utc >= $1
WHERE lm.mst_utc >= ${BigInt(nowMs)}
AND lm.sport = 'football'
AND lm.odds IS NOT NULL
AND lm.odds != 'null'::jsonb
AND (lm.status IS NULL OR lm.status NOT IN ('FT', 'AET', 'PEN', 'ABD', 'CANC', 'PST', 'SUSP', 'INT', 'AWD', 'WO'))
AND (lm.state IS NULL OR lm.state NOT IN ('after', 'postponed', 'cancelled', 'abandoned'))
ORDER BY lm.mst_utc ASC
LIMIT $2
`,
BigInt(nowMs),
limit,
);
LIMIT ${limit}
`);
}
/**
* Lig bazlı gol profili.
*/
async getLeagueProfile(leagueId: string): Promise<LeagueProfileRow | null> {
const rows = await this.prisma.$queryRawUnsafe<LeagueProfileRow[]>(
`
const rows = await this.prisma.$queryRaw<LeagueProfileRow[]>(Prisma.sql`
SELECT
m.league_id,
l.name AS league_name,
@@ -424,12 +416,10 @@ export class FrequencyEngineService {
JOIN leagues l ON m.league_id = l.id
WHERE m.status = 'FT'
AND m.score_home IS NOT NULL
AND m.league_id = $1
AND m.league_id = ${leagueId}
GROUP BY m.league_id, l.name
HAVING COUNT(*) >= 20
`,
leagueId,
);
`);
return rows.length > 0 ? rows[0] : null;
}
@@ -6,6 +6,7 @@
*/
import { Injectable, Logger } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../../database/prisma.service";
import {
Sport,
@@ -858,11 +859,11 @@ export class FeederPersistenceService {
// Use raw SQL for performance — Prisma's { some: {} } relation filters
// generate heavy correlated subqueries that hang on Raspberry Pi with
// large tables (15M+ odd_selections, 3M+ participations).
const result = await this.prisma.$queryRawUnsafe<Array<{ id: string }>>(
`
const result = await this.prisma.$queryRaw<Array<{ id: string }>>(
Prisma.sql`
SELECT m.id
FROM matches m
WHERE m.id = ANY($1::text[])
WHERE m.id = ANY(${matchIds}::text[])
AND EXISTS (SELECT 1 FROM odd_categories oc WHERE oc.match_id = m.id)
AND (
(m.sport = 'football'
@@ -875,7 +876,6 @@ export class FeederPersistenceService {
AND EXISTS (SELECT 1 FROM basketball_player_stats bps WHERE bps.match_id = m.id))
)
`,
matchIds,
);
return result.map((r) => r.id);