From 9bb8f39bca476c0baa15576cd9ce2e07ce07a080 Mon Sep 17 00:00:00 2001 From: Fahri Can Date: Tue, 5 May 2026 14:06:20 +0300 Subject: [PATCH] gg --- ...ggest-bet-platform.postman_collection.json | 305 ++++++++++++++++++ package-lock.json | 54 +--- package.json | 7 +- src/app.module.ts | 1 + src/common/utils/ai-engine-client.ts | 19 +- .../feeder/feeder-persistence.service.ts | 2 +- src/modules/leagues/leagues.service.ts | 5 +- src/modules/matches/matches.service.ts | 168 ++++------ 8 files changed, 404 insertions(+), 157 deletions(-) diff --git a/mds/suggest-bet-platform.postman_collection.json b/mds/suggest-bet-platform.postman_collection.json index dd06fa1..cd7a918 100644 --- a/mds/suggest-bet-platform.postman_collection.json +++ b/mds/suggest-bet-platform.postman_collection.json @@ -829,6 +829,311 @@ } ] }, + { + "name": "AiProxy", + "item": [ + { + "name": "DELETE /api/ai-engine/{path}", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "", + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ], + "query": [] + } + }, + "response": [ + { + "name": "DELETE /api/ai-engine/{path} - 200", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ] + } + }, + "status": "", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{}" + } + ] + }, + { + "name": "GET /api/ai-engine/{path}", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "", + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ], + "query": [] + } + }, + "response": [ + { + "name": "GET /api/ai-engine/{path} - 200", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ] + } + }, + "status": "", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{}" + } + ] + }, + { + "name": "PATCH /api/ai-engine/{path}", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "", + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ], + "query": [] + } + }, + "response": [ + { + "name": "PATCH /api/ai-engine/{path} - 200", + "originalRequest": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ] + } + }, + "status": "", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{}" + } + ] + }, + { + "name": "POST /api/ai-engine/{path}", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "", + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ], + "query": [] + } + }, + "response": [ + { + "name": "POST /api/ai-engine/{path} - 200", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ] + } + }, + "status": "", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{}" + } + ] + }, + { + "name": "PUT /api/ai-engine/{path}", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "", + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ], + "query": [] + } + }, + "response": [ + { + "name": "PUT /api/ai-engine/{path} - 200", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{beBaseUrl}}/api/ai-engine/{{path}}", + "host": [ + "{{beBaseUrl}}" + ], + "path": [ + "api", + "ai-engine", + "{path}" + ] + } + }, + "status": "", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": "{}" + } + ] + } + ] + }, { "name": "Analysis", "item": [ diff --git a/package-lock.json b/package-lock.json index 4ecf72a..ca766cc 100755 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@nestjs/swagger": "^11.2.4", "@nestjs/terminus": "^11.0.0", "@nestjs/throttler": "^6.5.0", - "@prisma/client": "^5.22.0", + "@prisma/client": "5.22.0", "axios": "^1.13.6", "bcrypt": "^6.0.0", "bullmq": "^5.66.4", @@ -46,7 +46,7 @@ "passport-jwt": "^4.0.1", "pino": "^10.1.0", "pino-http": "^11.0.0", - "prisma": "^5.22.0", + "prisma": "5.22.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "twitter-api-v2": "^1.29.0", @@ -1145,7 +1145,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3001,7 +3000,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz", "integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==", "license": "MIT", - "peer": true, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "axios": "^1.3.1", @@ -3095,7 +3093,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3262,7 +3259,6 @@ "version": "11.1.11", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.11.tgz", "integrity": "sha512-R/+A8XFqLgN8zNs2twhrOaE7dJbRQhdPX3g46am4RT/x8xGLqDphrXkUIno4cGUZHxbczChBAaAPTdPv73wDZA==", - "peer": true, "dependencies": { "file-type": "21.2.0", "iterare": "1.2.1", @@ -3308,7 +3304,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.11.tgz", "integrity": "sha512-H9i+zT3RvHi7tDc+lCmWHJ3ustXveABCr+Vcpl96dNOxgmrx4elQSTC4W93Mlav2opfLV+p0UTHY6L+bpUA4zA==", "hasInstallScript": true, - "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -3388,7 +3383,6 @@ "version": "11.1.11", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.11.tgz", "integrity": "sha512-kyABSskdMRIAMWL0SlbwtDy4yn59RL4HDdwHDz/fxWuv7/53YP8Y2DtV3/sHqY5Er0msMVTZrM38MjqXhYL7gw==", - "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.2.1", @@ -3409,7 +3403,6 @@ "version": "11.1.11", "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.11.tgz", "integrity": "sha512-0z6pLg9CuTXtz7q2lRZoPOU94DN28OTa39f4cQrlZysKA6QrKM7w7z6xqb4g32qjF+LQHFNRmMJtE/pLrxBaig==", - "peer": true, "dependencies": { "socket.io": "4.8.3", "tslib": "2.8.1" @@ -3784,7 +3777,6 @@ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", "hasInstallScript": true, - "peer": true, "engines": { "node": ">=16.13" }, @@ -3849,7 +3841,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -4755,7 +4746,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4877,7 +4867,6 @@ "version": "22.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -5042,7 +5031,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", "dev": true, - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", @@ -5680,7 +5668,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5734,7 +5721,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5926,7 +5912,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -6240,7 +6225,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6313,7 +6297,6 @@ "version": "5.66.4", "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.66.4.tgz", "integrity": "sha512-y2VRk2z7d1YNI2JQDD7iThoD0X/0iZZ3VEp8lqT5s5U0XDl9CIjXp1LQgmE9EKy6ReHtzmYXS1f328PnUbZGtQ==", - "peer": true, "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.8.2", @@ -6387,7 +6370,6 @@ "version": "7.2.7", "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-7.2.7.tgz", "integrity": "sha512-TKeeb9nSybk1e9E5yAiPVJ6YKdX9FYhwqqy8fBfVKAFVTJYZUNmeIvwjURW6+UikNsO6l2ta27thYgo/oumDsw==", - "peer": true, "dependencies": { "@cacheable/utils": "^2.3.2", "keyv": "^5.5.4" @@ -6601,7 +6583,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -6651,14 +6632,12 @@ "node_modules/class-transformer": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "peer": true + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, "node_modules/class-validator": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", - "peer": true, "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", @@ -7497,7 +7476,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -7555,7 +7535,6 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7615,7 +7594,6 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -7846,7 +7824,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -9051,7 +9028,6 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -9895,7 +9871,6 @@ "version": "5.5.5", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.5.tgz", "integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==", - "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -10688,6 +10663,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "peer": true, "engines": { "node": ">= 6" } @@ -10920,7 +10896,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", - "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -11047,7 +11022,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", - "peer": true, "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", @@ -11077,7 +11051,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-11.0.0.tgz", "integrity": "sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==", - "peer": true, "dependencies": { "get-caller-file": "^2.0.5", "pino": "^10.0.0", @@ -11286,7 +11259,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11340,7 +11312,6 @@ "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", "hasInstallScript": true, - "peer": true, "dependencies": { "@prisma/engines": "5.22.0" }, @@ -12479,7 +12450,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12794,7 +12764,6 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -12950,7 +12919,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13298,6 +13266,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -13315,6 +13284,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -13327,6 +13297,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -13340,6 +13311,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "peer": true, "engines": { "node": ">=4.0" } @@ -13348,13 +13320,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": true, + "peer": true }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -13364,6 +13338,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -13376,6 +13351,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/package.json b/package.json index b1d42b3..98e491d 100755 --- a/package.json +++ b/package.json @@ -29,8 +29,7 @@ "feeder:live": "ts-node -r tsconfig-paths/register src/scripts/run-live-feeder.ts", "cleanup:live": "ts-node -r tsconfig-paths/register src/scripts/cleanup-live-matches.ts", "swagger:summary": "ts-node -r tsconfig-paths/register src/scripts/export-swagger-endpoints-summary.ts", - "postman:export": "ts-node -r tsconfig-paths/register src/scripts/export-postman-collection.ts" - , + "postman:export": "ts-node -r tsconfig-paths/register src/scripts/export-postman-collection.ts", "ai:extract:v26": "python3 ai-engine/scripts/extract_training_data_v26.py", "ai:train:v26": "python3 ai-engine/scripts/train_v26_shadow.py", "ai:backtest:v26": "python3 ai-engine/scripts/backtest_v26_shadow.py", @@ -56,7 +55,7 @@ "@nestjs/swagger": "^11.2.4", "@nestjs/terminus": "^11.0.0", "@nestjs/throttler": "^6.5.0", - "@prisma/client": "^5.22.0", + "@prisma/client": "5.22.0", "axios": "^1.13.6", "bcrypt": "^6.0.0", "bullmq": "^5.66.4", @@ -76,7 +75,7 @@ "passport-jwt": "^4.0.1", "pino": "^10.1.0", "pino-http": "^11.0.0", - "prisma": "^5.22.0", + "prisma": "5.22.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "twitter-api-v2": "^1.29.0", diff --git a/src/app.module.ts b/src/app.module.ts index b37aed5..e5a1925 100755 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -77,6 +77,7 @@ const historicalFeederMode = process.env.FEEDER_MODE === "historical"; // Configuration ConfigModule.forRoot({ isGlobal: true, + envFilePath: [".env.local", ".env"], validate: validateEnv, load: [ appConfig, diff --git a/src/common/utils/ai-engine-client.ts b/src/common/utils/ai-engine-client.ts index 75422aa..cd90765 100644 --- a/src/common/utils/ai-engine-client.ts +++ b/src/common/utils/ai-engine-client.ts @@ -134,7 +134,13 @@ export class AiEngineClient { const shouldRetry = attempt < retries && this.isRetriableError(error); if (!shouldRetry) { - this.registerFailure(error); + // Only register circuit breaker failure for server/network errors, not client errors (4xx) + if (this.isServerError(error)) { + this.registerFailure(error); + } else { + // It's a successful contact with the engine (e.g. 404, 422), so reset failures + this.resetFailures(); + } throw this.toRequestError(error); } @@ -220,6 +226,17 @@ export class AiEngineClient { return status >= 500 || status === 429 || error.code === "ECONNABORTED"; } + private isServerError(error: unknown): boolean { + if (!axios.isAxiosError(error)) { + return true; // Not an axios error, assume internal/network error + } + if (!error.response) { + return true; // Network error, timeout, etc. + } + const status = error.response.status; + return status >= 500 || status === 429; + } + private toRequestError(error: unknown): AiEngineRequestError { if (error instanceof AiEngineRequestError) { return error; diff --git a/src/modules/feeder/feeder-persistence.service.ts b/src/modules/feeder/feeder-persistence.service.ts index 6b3d600..088aba3 100755 --- a/src/modules/feeder/feeder-persistence.service.ts +++ b/src/modules/feeder/feeder-persistence.service.ts @@ -167,7 +167,7 @@ export class FeederPersistenceService { const leagueId = this.safeString(league.id); if (leagueId) { - const logoUrl = `https://file.mackolikfeeds.com/areas/${leagueId}`; + const logoUrl = `https://file.mackolikfeeds.com/competitions/${leagueId}`; const localPath = `public/uploads/competitions/${leagueId}.png`; imageDownloads.push( ImageUtils.downloadImage(logoUrl, localPath) diff --git a/src/modules/leagues/leagues.service.ts b/src/modules/leagues/leagues.service.ts index 66a1d96..ec5329a 100755 --- a/src/modules/leagues/leagues.service.ts +++ b/src/modules/leagues/leagues.service.ts @@ -188,7 +188,10 @@ export class LeaguesService { { homeTeamId: teamId1, awayTeamId: teamId2 }, { homeTeamId: teamId2, awayTeamId: teamId1 }, ], - state: "postGame", // Finished matches are stored as "postGame" + AND: [ + { scoreHome: { not: null } }, + { scoreAway: { not: null } }, + ], }, include: { homeTeam: true, diff --git a/src/modules/matches/matches.service.ts b/src/modules/matches/matches.service.ts index 2c37b20..6de27a6 100755 --- a/src/modules/matches/matches.service.ts +++ b/src/modules/matches/matches.service.ts @@ -20,100 +20,53 @@ import { @Injectable() export class MatchesService { private readonly logger = new Logger(MatchesService.name); + private qualifiedLeagueIds: string[] = []; private topLeagueIds: string[] = []; constructor(private readonly prisma: PrismaService) { + this.loadQualifiedLeagues(); this.loadTopLeagues(); } private loadTopLeagues() { try { - const topLeaguesPath = path.join(process.cwd(), "top_leagues.json"); - if (fs.existsSync(topLeaguesPath)) { - this.topLeagueIds = JSON.parse(fs.readFileSync(topLeaguesPath, "utf8")); - this.logger.log( - `Loaded ${this.topLeagueIds.length} top leagues for filtering.`, - ); + const filePath = path.join(process.cwd(), "top_leagues.json"); + if (fs.existsSync(filePath)) { + this.topLeagueIds = JSON.parse(fs.readFileSync(filePath, "utf8")); } } catch (e) { this.logger.warn(`Failed to load top_leagues.json: ${e.message}`); } } - /** - * Generate flag URL from country code or name using flagcdn.com - * Falls back to a name-to-code mapping for Turkish country names - */ - private getCountryFlagUrl( - countryCode?: string | null, - countryName?: string | null, - ): string | undefined { - // If we have a 2-letter ISO code, use it directly - if (countryCode && countryCode.length === 2) { - return `https://flagcdn.com/w40/${countryCode.toLowerCase()}.png`; - } - - // Fallback: map common Turkish country names to ISO codes - const COUNTRY_NAME_TO_CODE: Record = { - "Türkiye": "tr", - "İngiltere": "gb-eng", - "İspanya": "es", - "İtalya": "it", - "Almanya": "de", - "Fransa": "fr", - "Portekiz": "pt", - "Hollanda": "nl", - "Belçika": "be", - "İskoçya": "gb-sct", - "Galler": "gb-wls", - "İrlanda": "ie", - "Avusturya": "at", - "İsviçre": "ch", - "Yunanistan": "gr", - "Polonya": "pl", - "Çekya": "cz", - "Hırvatistan": "hr", - "Sırbistan": "rs", - "Danimarka": "dk", - "Norveç": "no", - "İsveç": "se", - "Finlandiya": "fi", - "Rusya": "ru", - "Ukrayna": "ua", - "Romanya": "ro", - "Macaristan": "hu", - "Bulgaristan": "bg", - "Arjantin": "ar", - "Brezilya": "br", - "Meksika": "mx", - "ABD": "us", - "Japonya": "jp", - "Güney Kore": "kr", - "Çin": "cn", - "Avustralya": "au", - "Suudi Arabistan": "sa", - "BAE": "ae", - "Katar": "qa", - "Mısır": "eg", - "Güney Afrika": "za", - "Kolombiya": "co", - "Şili": "cl", - "Peru": "pe", - "Ekvador": "ec", - "Paraguay": "py", - "Uruguay": "uy", - "Avrupa": "eu", - "Dünya": "un", - }; - - if (countryName) { - const code = COUNTRY_NAME_TO_CODE[countryName]; - if (code) { - return `https://flagcdn.com/w40/${code}.png`; + private loadQualifiedLeagues() { + try { + const filePath = path.join(process.cwd(), "qualified_leagues.json"); + if (fs.existsSync(filePath)) { + this.qualifiedLeagueIds = JSON.parse(fs.readFileSync(filePath, "utf8")); + this.logger.log( + `Loaded ${this.qualifiedLeagueIds.length} qualified leagues for filtering.`, + ); } + } catch (e) { + this.logger.warn(`Failed to load qualified_leagues.json: ${e.message}`); } + } - return undefined; + /** + * Generate URL for the country flag served from Mackolik + */ + private getCountryFlagUrl(countryId?: string | null): string | undefined { + if (!countryId) return undefined; + return `https://file.mackolikfeeds.com/areas/${countryId}`; + } + + /** + * Generate URL for the team logo served from local uploads + */ + private getTeamLogoUrl(teamId?: string | null): string | undefined { + if (!teamId) return undefined; + return `https://file.mackolikfeeds.com/teams/${teamId}`; } private getLiveFilter(): Prisma.LiveMatchWhereInput { @@ -215,10 +168,9 @@ export class MatchesService { if (leagueId) { where.leagueId = leagueId; - } else if (this.topLeagueIds.length > 0) { - // Always filter by top leagues when no specific leagueId is requested - // This ensures match list is consistent with the active leagues sidebar - where.leagueId = { in: this.topLeagueIds }; + } else if (this.qualifiedLeagueIds.length > 0) { + // Only show matches from qualified leagues (leagues with historical data for AI analysis) + where.leagueId = { in: this.qualifiedLeagueIds }; } if (status === "LIVE") { @@ -375,7 +327,9 @@ export class MatchesService { country: { id: match.league?.country?.id || "", name: match.league?.country?.name || "", - flagUrl: match.league?.country?.flagUrl || this.getCountryFlagUrl(null, match.league?.country?.name), + flagUrl: + match.league?.country?.flagUrl || + this.getCountryFlagUrl(match.league?.country?.id), }, sport: sport, matches: [], @@ -430,11 +384,11 @@ export class MatchesService { htScoreAway: undefined, homeTeamName: match.homeTeam?.name || "Unknown", homeTeamLogo: match.homeTeamId - ? `https://file.mackolikfeeds.com/teams/${match.homeTeamId}` + ? this.getTeamLogoUrl(match.homeTeamId) : undefined, awayTeamName: match.awayTeam?.name || "Unknown", awayTeamLogo: match.awayTeamId - ? `https://file.mackolikfeeds.com/teams/${match.awayTeamId}` + ? this.getTeamLogoUrl(match.awayTeamId) : undefined, leagueName: match.league?.name, countryName: match.league?.country?.name, @@ -442,7 +396,15 @@ export class MatchesService { }); } - return Array.from(leaguesMap.values()); + return Array.from(leaguesMap.values()).sort((a, b) => { + const aIdx = this.topLeagueIds.indexOf(a.id); + const bIdx = this.topLeagueIds.indexOf(b.id); + const aPriority = aIdx === -1 ? 999 : aIdx; + const bPriority = bIdx === -1 ? 999 : bIdx; + + if (aPriority !== bPriority) return aPriority - bPriority; + return (a.name || "").localeCompare(b.name || ""); + }); } /** @@ -465,6 +427,7 @@ export class MatchesService { const leagues = await this.prisma.$queryRaw` SELECT l.id, l.name, l.code, + c.id as country_id, c.name as country_name, c.flag_url as country_flag, COUNT(lm.id)::int as match_count, @@ -474,34 +437,21 @@ export class MatchesService { JOIN leagues l ON lm.league_id = l.id LEFT JOIN countries c ON l.country_id = c.id WHERE lm.sport = ${sport} - ${this.topLeagueIds.length > 0 ? Prisma.sql`AND l.id IN (${Prisma.join(this.topLeagueIds)})` : Prisma.empty} + ${this.qualifiedLeagueIds.length > 0 ? Prisma.sql`AND l.id IN (${Prisma.join(this.qualifiedLeagueIds)})` : Prisma.empty} AND ( (lm.mst_utc >= ${todayMs} AND lm.status NOT IN (${Prisma.join(finishedStatuses)}) AND COALESCE(lm.state, '') NOT IN (${Prisma.join(finishedStates)})) OR lm.status IN (${Prisma.join(liveStatuses)}) OR lm.state IN (${Prisma.join(liveStates)}) ) - GROUP BY l.id, l.name, l.code, c.name, c.flag_url + GROUP BY l.id, l.name, l.code, c.id, c.name, c.flag_url ORDER BY l.name ASC `; - // Priority sorting (Mackolik style) - const PRIORITY = [ - "Trendyol Süper Lig", - "Süper Lig", - "Trendyol 1. Lig", - "1. Lig", - "Premier Lig", - "LaLiga", - "Serie A", - "Bundesliga", - "Ligue 1", - ]; - return leagues .filter((l) => l.match_count > 0) .sort((a, b) => { - const aIdx = PRIORITY.findIndex((p) => a.name?.includes(p)); - const bIdx = PRIORITY.findIndex((p) => b.name?.includes(p)); + const aIdx = this.topLeagueIds.indexOf(a.id); + const bIdx = this.topLeagueIds.indexOf(b.id); const aPriority = aIdx === -1 ? 999 : aIdx; const bPriority = bIdx === -1 ? 999 : bIdx; @@ -514,7 +464,7 @@ export class MatchesService { name: l.name, code: l.code, countryName: l.country_name, - countryFlag: l.country_flag || this.getCountryFlagUrl(null, l.country_name), + countryFlag: l.country_flag || this.getCountryFlagUrl(l.country_id), matchCount: l.match_count, liveCount: l.live_count, })); @@ -553,13 +503,9 @@ export class MatchesService { scoreAway: m.scoreAway, status: m.status, homeTeamName: m.homeTeam?.name, - homeTeamLogo: m.homeTeamId - ? `https://file.mackolikfeeds.com/teams/${m.homeTeamId}` - : null, + homeTeamLogo: m.homeTeamId ? this.getTeamLogoUrl(m.homeTeamId) : null, awayTeamName: m.awayTeam?.name, - awayTeamLogo: m.awayTeamId - ? `https://file.mackolikfeeds.com/teams/${m.awayTeamId}` - : null, + awayTeamLogo: m.awayTeamId ? this.getTeamLogoUrl(m.awayTeamId) : null, leagueName: m.league?.name, countryName: m.league?.country?.name, })), @@ -860,13 +806,13 @@ export class MatchesService { homeTeam: { ...match.homeTeam, logo: match.homeTeamId - ? `https://file.mackolikfeeds.com/teams/${match.homeTeamId}` + ? this.getTeamLogoUrl(match.homeTeamId) : match.homeTeam?.logoUrl || null, }, awayTeam: { ...match.awayTeam, logo: match.awayTeamId - ? `https://file.mackolikfeeds.com/teams/${match.awayTeamId}` + ? this.getTeamLogoUrl(match.awayTeamId) : match.awayTeam?.logoUrl || null, }, stats: {