diff --git a/src/core/game_events/betika/index.ts b/src/core/game_events/betika/index.ts index c212a91..0c26a54 100644 --- a/src/core/game_events/betika/index.ts +++ b/src/core/game_events/betika/index.ts @@ -2,9 +2,13 @@ import { BaseGameEventsProcessor } from ".."; import { getConfig } from "../../.."; import { BetProvider } from "../../../bet_providers"; import { BetikaProvider } from "../../../bet_providers/betika"; +import { PostgresDataSourceSingleton } from "../../../datastores/postgres"; +import { insertThreeWayGameEvent } from "../../../datastores/postgres/queries/three_way_game_event"; +import { insertTwoWayGameEvent } from "../../../datastores/postgres/queries/two_way_game_event"; import { RedisSingleton } from "../../../datastores/redis"; import { getRedisProcessedEventsChannelName } from "../../../utils/redis"; -import { ProcessedGameEvents } from "../../../utils/types/common"; +import { BetTypes, ProcessedGameEvents } from "../../../utils/types/common"; +import { DbThreeWayGameEvent, DbTwoWayGameEvent } from "../../../utils/types/db"; import { Result } from "../../../utils/types/result_type"; const {logger} = getConfig(); @@ -24,13 +28,65 @@ export class BetikaGameEventsProcessor extends BaseGameEventsProcessor { return getBetProviderConfigResult; } + const getPostgresDbResult = await PostgresDataSourceSingleton.getInstance(getConfig()); + if (getPostgresDbResult.result === "error") { + logger.error(`Events processor failed to get postgres connection for provider ${this.betProvider.name} with error: `,getPostgresDbResult.value.message); + return getPostgresDbResult; + } + const getRedisSubscriberResult = await RedisSingleton.getSubscriber(); if (getRedisSubscriberResult.result === "success") { const betProviderConfig = getBetProviderConfigResult.value; const results = betProviderConfig.games.map(async game => { - await getRedisSubscriberResult.value.subscribe(getRedisProcessedEventsChannelName(this.betProvider, game.name, game.betType), message => { + await getRedisSubscriberResult.value.subscribe(getRedisProcessedEventsChannelName(this.betProvider, game.name, game.betType), async message => { const parsedMessage = JSON.parse(message) as ProcessedGameEvents; - logger.trace("redis subscriber message received. ", parsedMessage) + + + /** + * TODO: Payload is really similar to DbTwoWayGameEvent / DbThreeWayGameEvent. Is there any way we can combine them? + * We are already using discriminated unions to correctly type case different game types. + */ + const innerResults = parsedMessage.data.map(async item => { + /** + * TODO: Check if games exists before attempting insert. (We shouldn't get double inserts as now as well due to unique constraints on the database) + */ + switch (item.type) { + case BetTypes.TWO_WAY: + const twoWayEventToDB: DbTwoWayGameEvent = { + betProviderName: parsedMessage.betProviderName, + betProviderId: item.betProviderId, + clubA: item.clubA, + clubB: item.clubB, + oddsAWin: item.oddsAWin, + oddsBWin: item.oddsBWin, + gameName: parsedMessage.gameName, + league: item.league, + metaData: item.meta + } + await insertTwoWayGameEvent(getPostgresDbResult.value, twoWayEventToDB); + break; + case BetTypes.THREE_WAY: + const threeWayEventToDb: DbThreeWayGameEvent = { + betProviderName: parsedMessage.betProviderName, + betProviderId: item.betProviderId, + clubA: item.clubA, + clubB: item.clubB, + oddsAWin: item.oddsAWin, + oddsBWin: item.oddsBWin, + oddsDraw: item.oddsDraw, + gameName: parsedMessage.gameName, + league: item.league, + metaData: item.meta + } + await insertThreeWayGameEvent(getPostgresDbResult.value, threeWayEventToDb); + break; + default: + const message = `Unknown bet type encountered when saving processed game events to database.`; + logger.error(message, item); + throw new Error(message); + } + }); + await Promise.all(innerResults); }); }); await Promise.all(results); diff --git a/src/core/parsers/betika/index.ts b/src/core/parsers/betika/index.ts index 6983a5c..c7cf24e 100644 --- a/src/core/parsers/betika/index.ts +++ b/src/core/parsers/betika/index.ts @@ -66,6 +66,10 @@ export class BetikaParser extends BaseParser { if (results2.result === "success") { parsedResults = results2.value.map(item => { return { + type: BetTypes.TWO_WAY, + betProviderId: item.link, + clubA: item.clubA, + clubB: item.clubB, oddsAWin: item.oddsAWin, oddsBWin: item.oddsBWin, league: item.league, @@ -84,6 +88,10 @@ export class BetikaParser extends BaseParser { if (results2.result === "success") { parsedResults = results2.value.map(item => { return { + type: BetTypes.THREE_WAY, + betProviderId: item.link, + clubA: item.clubA, + clubB: item.clubB, oddsAWin: item.oddsAWin, oddsBWin: item.oddsBWin, oddsDraw: item.oddsDraw, diff --git a/src/core/parsers/betika/parser_types.ts b/src/core/parsers/betika/parser_types.ts index 641b8c4..41a9dfb 100644 --- a/src/core/parsers/betika/parser_types.ts +++ b/src/core/parsers/betika/parser_types.ts @@ -53,7 +53,14 @@ export function processBetikaThreeWayGamesHtml(html: string): Result a").attr("href") }); }); - return {result: "success", value: gameEvents}; + + const filteredGameEvents = gameEvents.filter(event => { + return (event.oddsAWin !== null || event.oddsAWin !== undefined || !isNaN(event.oddsAWin)) && + (event.oddsDraw !== null || event.oddsDraw !== undefined || !isNaN(event.oddsDraw)) && + (event.oddsBWin !== null || event.oddsBWin !== undefined || !isNaN(event.oddsBWin)); + }); + + return {result: "success", value: filteredGameEvents}; } catch (e: any) { logger.error("An error occurred while parsing Betika two way html data: ", e.message); return {result: "error", value: new Error(e.message)}; diff --git a/src/datastores/postgres/entities/index.ts b/src/datastores/postgres/entities/index.ts index a1e6f8b..0ebeb8e 100644 --- a/src/datastores/postgres/entities/index.ts +++ b/src/datastores/postgres/entities/index.ts @@ -12,7 +12,7 @@ export class TwoWayGameEventEntity { id: number @Index("two_way_game_event_provider_id_idx") - @Column("string", {length: 100, nullable: false}) + @Column("varchar", {length: 100, nullable: false}) bet_provider_id: string @Column("varchar", {length: 100, nullable: false}) @@ -59,7 +59,7 @@ export class ThreeWayGameEventEntity { id: number @Index("three_way_game_event_provider_id_idx") - @Column("string", {length: 100, nullable: false}) + @Column("varchar", {length: 100, nullable: false}) bet_provider_id: string @Column("varchar", {length: 100, nullable: false}) @@ -79,7 +79,7 @@ export class ThreeWayGameEventEntity { @Index("three_way_game_event_bet_provider_idx") @Column("varchar", {length: 100, nullable: false}) - bet_provider: BetProviders + bet_provider_name: BetProviders @Column("varchar", {length: 100, nullable: false}) game_name: Games diff --git a/src/datastores/postgres/index.ts b/src/datastores/postgres/index.ts index fefb22a..dbcc601 100644 --- a/src/datastores/postgres/index.ts +++ b/src/datastores/postgres/index.ts @@ -2,6 +2,7 @@ import { DataSource } from "typeorm"; import { Config, getConfig } from "../../index"; import { Result } from "../../utils/types/result_type/index"; +import { ThreeWayGameEventEntity, TwoWayGameEventEntity } from "./entities"; const {logger} = getConfig(); @@ -19,13 +20,14 @@ export class PostgresDataSourceSingleton { username: config.postgresUser, password: config.postgresPassword, database: config.postgresDatabaseName, - synchronize: false, + synchronize: true, logging: false, // TODO: maybe create a different logging structure for database logs? extra: { sss: true }, entities: [ - + TwoWayGameEventEntity, + ThreeWayGameEventEntity ] }); diff --git a/src/datastores/postgres/queries/three_way_game_event/index.ts b/src/datastores/postgres/queries/three_way_game_event/index.ts index f8c32be..a3d5951 100644 --- a/src/datastores/postgres/queries/three_way_game_event/index.ts +++ b/src/datastores/postgres/queries/three_way_game_event/index.ts @@ -1,7 +1,10 @@ import { DataSource, InsertResult, UpdateResult } from "typeorm"; import { BetProviders } from "../../../../utils/types/common"; -import { ThreeWayGameEvent } from "../../../../utils/types/db"; +import { DbThreeWayGameEvent } from "../../../../utils/types/db"; import { ThreeWayGameEventEntity } from "../../entities"; +import { getConfig } from "../../../.."; + +const {logger} = getConfig(); /** * Useful for checking whether a three way game event already exists for a provider. @@ -26,21 +29,24 @@ export const getThreeWayGame = async ( export const insertThreeWayGameEvent = async ( dataSource: DataSource, - data: ThreeWayGameEvent + data: DbThreeWayGameEvent ): Promise => { - const toDataBase = { + logger.trace("Inserting into database: ", data); + return await dataSource.createQueryBuilder() + .insert() + .into(ThreeWayGameEventEntity) + .values({ bet_provider_id: data.betProviderId, bet_provider_name: data.betProviderName, club_a: data.clubA, club_b: data.clubB, odds_a_win: data.oddsAWin, odds_b_win: data.oddsBWin, - game_name: data.gameName - } - return await dataSource.createQueryBuilder() - .insert() - .into(ThreeWayGameEventEntity) - .values(toDataBase) + odds_draw: data.oddsDraw, + game_name: data.gameName, + league: data.league, + meta_data: data.metaData + }) .execute(); }; diff --git a/src/datastores/postgres/queries/two_way_game_event/index.ts b/src/datastores/postgres/queries/two_way_game_event/index.ts index e19d24b..7cbdcbf 100644 --- a/src/datastores/postgres/queries/two_way_game_event/index.ts +++ b/src/datastores/postgres/queries/two_way_game_event/index.ts @@ -1,6 +1,6 @@ import { DataSource, InsertResult, UpdateResult } from "typeorm"; import { BetProviders } from "../../../../utils/types/common"; -import { TwoWayGameEvent } from "../../../../utils/types/db"; +import { DbTwoWayGameEvent } from "../../../../utils/types/db"; import { TwoWayGameEventEntity } from "../../entities"; /** @@ -26,21 +26,22 @@ export const getTwoWayGame = async ( export const insertTwoWayGameEvent = async ( dataSource: DataSource, - data: TwoWayGameEvent + data: DbTwoWayGameEvent ): Promise => { - const toDataBase = { + return await dataSource.createQueryBuilder() + .insert() + .into(TwoWayGameEventEntity) + .values({ bet_provider_id: data.betProviderId, bet_provider_name: data.betProviderName, club_a: data.clubA, club_b: data.clubB, odds_a_win: data.oddsAWin, odds_b_win: data.oddsBWin, - game_name: data.gameName - }; - return await dataSource.createQueryBuilder() - .insert() - .into(TwoWayGameEventEntity) - .values(toDataBase) + game_name: data.gameName, + league: data.league, + meta_data: data.metaData + }) .execute(); }; diff --git a/src/testbed/testbed.ts b/src/testbed/testbed.ts index 569d801..9ed1cb7 100644 --- a/src/testbed/testbed.ts +++ b/src/testbed/testbed.ts @@ -1,4 +1,4 @@ -import { OrbitScrapper } from "../core/scrapping/orbit"; +import { BetikaScrapper } from "../core/scrapping/betika"; -const betikaScrapper = new OrbitScrapper(); +const betikaScrapper = new BetikaScrapper(); betikaScrapper.fetchData(); diff --git a/src/testbed/testbed_2.ts b/src/testbed/testbed_2.ts index 8577af2..cc1fb39 100644 --- a/src/testbed/testbed_2.ts +++ b/src/testbed/testbed_2.ts @@ -1,4 +1,4 @@ -import { OrbitParser } from "../core/parsers/orbit"; +import { BetikaParser } from "../core/parsers/betika"; -const parser = new OrbitParser() +const parser = new BetikaParser() parser.subscribeToChannels(); diff --git a/src/testbed/testbed_3.ts b/src/testbed/testbed_3.ts new file mode 100644 index 0000000..e5d33f3 --- /dev/null +++ b/src/testbed/testbed_3.ts @@ -0,0 +1,4 @@ +import { BetikaGameEventsProcessor } from "../core/game_events/betika"; + +const gameEventsProcessor = new BetikaGameEventsProcessor(); +gameEventsProcessor.subscribeToChannels(); \ No newline at end of file diff --git a/src/utils/types/common/index.ts b/src/utils/types/common/index.ts index cd7c44f..2ea763e 100644 --- a/src/utils/types/common/index.ts +++ b/src/utils/types/common/index.ts @@ -56,6 +56,7 @@ export interface ProcessedHtmlMessage { } export interface BaseProcessedGameEvent { + betProviderId: string; clubA: string, clubB: string, estimatedStartTimeUtc: Date, diff --git a/src/utils/types/db/index.ts b/src/utils/types/db/index.ts index fa946fc..b57139c 100644 --- a/src/utils/types/db/index.ts +++ b/src/utils/types/db/index.ts @@ -6,7 +6,7 @@ import { BetProviders, Games } from "../common"; -export interface TwoWayGameEvent { +export interface DbTwoWayGameEvent { betProviderName: BetProviders; betProviderId: string; clubA: string; @@ -18,7 +18,7 @@ export interface TwoWayGameEvent { metaData: string; } -export interface ThreeWayGameEvent { +export interface DbThreeWayGameEvent { betProviderName: BetProviders; betProviderId: string; clubA: string;