From 530b3f4927fbe4309e0eb829fe0b0a01ab8b9432 Mon Sep 17 00:00:00 2001 From: Charles Kagiri Date: Sun, 1 Dec 2024 21:08:44 +0300 Subject: [PATCH 1/4] update matches scheduler --- .../matches.todayAndMorrow.scheduler.ts | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts index a8200e1..eee8c5b 100644 --- a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts +++ b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts @@ -10,8 +10,8 @@ const DEFAULT_INTERVAL_MILLISECONDS = 6 * 60 * 60 * 1000; // 6H export class TodayAndMorrowScheduler extends BaseScheduler { private scheduleDate?: Date; private nextPoll?: moment.Moment; // ideally what is supposed to be the next poll - private hasLiveMatch = false; - private hasNewLiveMatch = false; + private liveMatchId = null; + private liveMatchHasFinished = false; public static getInstance( todayAndMorrowService = TodayAndMorrowServiceImpl.getInstance(), @@ -30,12 +30,7 @@ export class TodayAndMorrowScheduler extends BaseScheduler { this.scheduleDate = scheduleDate; return; } - const scheduleDateDiffInSeconds = moment.duration(moment(scheduleDate).diff(this.scheduleDate)).asSeconds() - if (scheduleDateDiffInSeconds >= 300) { - console.log(`${this.job.name} publish footballApiMatchUpdatesCompleted`); - this.eventMediator.publish('footballApiMatchUpdatesCompleted'); - } - this.scheduleDate = scheduleDate; + console.log(`${this.job.name} onScheduled ${moment(scheduleDate).format()}`); }) } @@ -45,29 +40,38 @@ export class TodayAndMorrowScheduler extends BaseScheduler { Math.round(moment.duration(this.nextPoll.diff(moment())).asHours()) if (nextPollInHours == undefined || nextPollInHours === 6) { period = PERIOD.TODAY_AND_MORROW - } else if (this.hasLiveMatch && !this.hasNewLiveMatch) { - period = PERIOD.LIVE + } else if (this.liveMatchHasFinished) { + period = PERIOD.TODAY; + } else if (this.liveMatchId != null) { + period = PERIOD.LIVE; } const apiMatches = await this.todayAndMorrowService.syncMatches(period); + if (this.liveMatchId == null && this.liveMatchHasFinished) { + console.log(`${this.job.name} publish footballApiMatchUpdatesCompleted`); + this.eventMediator.publish('footballApiMatchUpdatesCompleted'); + } + + if (this.liveMatchHasFinished) { + this.liveMatchHasFinished = false; + } return apiMatches; } calculateNextInterval(result: any = []): number { const apiMatches: any[] = result; - let hasLiveMatch = false; - let hasNewLiveMatch = false; let nextPoll = moment().add(12, 'hours'); + let liveMatch: any = null; for (const match of apiMatches) { const matchStatus = getMatchStatus(match.status); const matchStart = moment(match.utcDate); if (matchStatus === MatchStatus.LIVE) { - hasLiveMatch = true; - if (Math.abs(matchStart.diff(moment(), 'seconds')) <= 90) { - hasNewLiveMatch = true; - break; + if (liveMatch == null) { + liveMatch = match; + } else if (matchStart.isBefore(moment(liveMatch.utcDate))) { + liveMatch = match; } } else if (matchStatus === MatchStatus.SCHEDULED) { if (matchStart.isBefore(nextPoll)) { @@ -76,18 +80,27 @@ export class TodayAndMorrowScheduler extends BaseScheduler { } } - if (this.hasLiveMatch && !hasLiveMatch) { - this.hasLiveMatch = false; - this.nextPoll = moment().add(3, 'minutes'); - } else if (hasLiveMatch) { - this.hasLiveMatch = true; - this.hasNewLiveMatch = hasNewLiveMatch; - this.nextPoll = moment().add(90, 'seconds'); - } else { - // precautionary handle nextPoll being behind - const diff = nextPoll.diff(moment(), 'minutes'); - this.nextPoll = diff <= 0 ? moment().add(3, 'minutes') : nextPoll; + const liveMatchId = liveMatch?.id; + if (this.liveMatchId == null && liveMatchId != null) { + this.liveMatchId = liveMatchId; + } else if (this.liveMatchId != null && liveMatchId != null) { + if (this.liveMatchId !== liveMatchId) { + this.liveMatchId = liveMatchId; + this.liveMatchHasFinished = true; + } + } else if (this.liveMatchId != null && liveMatchId == null) { + this.liveMatchId = null; + this.liveMatchHasFinished = true; + } + + // precautionary handle nextPoll being behind + const diff = nextPoll.diff(moment(), 'minutes'); + nextPoll = diff <= 0 ? moment().add(1, 'minutes') : nextPoll; + + if (liveMatchId != null || this.liveMatchId != null) { + nextPoll = moment().add(1, 'minutes'); } + this.nextPoll = nextPoll; const nextIntervalInMs = Math.min(this.getDefaultIntervalMs(), this.nextPoll.diff(moment())); const nextIntervalInUTC = new Date(Date.now() + nextIntervalInMs).toUTCString(); From cf7face95e48787e54c8e2cd674570c73102fa25 Mon Sep 17 00:00:00 2001 From: Charles Kagiri Date: Mon, 2 Dec 2024 12:17:10 +0300 Subject: [PATCH 2/4] updates --- .../footballApi/matches.todayAndMorrow.scheduler.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts index eee8c5b..e4d0d99 100644 --- a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts +++ b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts @@ -73,6 +73,9 @@ export class TodayAndMorrowScheduler extends BaseScheduler { } else if (matchStart.isBefore(moment(liveMatch.utcDate))) { liveMatch = match; } + if (liveMatch.id === this.liveMatchId) { + break; + } } else if (matchStatus === MatchStatus.SCHEDULED) { if (matchStart.isBefore(nextPoll)) { nextPoll = matchStart.add(1, 'minutes'); @@ -97,7 +100,7 @@ export class TodayAndMorrowScheduler extends BaseScheduler { const diff = nextPoll.diff(moment(), 'minutes'); nextPoll = diff <= 0 ? moment().add(1, 'minutes') : nextPoll; - if (liveMatchId != null || this.liveMatchId != null) { + if (liveMatchId != null || this.liveMatchId != null || this.liveMatchHasFinished) { nextPoll = moment().add(1, 'minutes'); } this.nextPoll = nextPoll; From fcc430af5763dcb7cfadf17c838c8c303e8f589f Mon Sep 17 00:00:00 2001 From: Charles Kagiri Date: Mon, 2 Dec 2024 13:23:37 +0300 Subject: [PATCH 3/4] update leaderboard when no live matches --- backend/app/schedulers/appSchedule.ts | 4 ++ .../matches.todayAndMorrow.scheduler.ts | 52 ++++++++++--------- .../app/schedulers/leaderboard.scheduler.ts | 39 ++++++++++++++ .../schedulers/predictionPoints.scheduler.ts | 10 ++-- 4 files changed, 73 insertions(+), 32 deletions(-) create mode 100644 backend/app/schedulers/leaderboard.scheduler.ts diff --git a/backend/app/schedulers/appSchedule.ts b/backend/app/schedulers/appSchedule.ts index 35e3ef6..758d431 100644 --- a/backend/app/schedulers/appSchedule.ts +++ b/backend/app/schedulers/appSchedule.ts @@ -4,6 +4,7 @@ import { CurrentRoundMatchesService, CurrentRoundMatchesServiceImpl } from './fo import { TodayAndMorrowScheduler } from "./footballApi/matches.todayAndMorrow.scheduler"; import { SeasonNextRoundScheduler } from "./footballApi/season.nextRound.scheduler"; import { PredictionPointsScheduler } from './predictionPoints.scheduler'; +import { LeaderboardScheduler } from './leaderboard.scheduler'; import { MakePredictionsScheduler } from "./makePredictions.scheduler"; export class AppSchedule { @@ -11,6 +12,7 @@ export class AppSchedule { private readonly todayAndMorrowScheduler: TodayAndMorrowScheduler; private readonly seasonNextRoundScheduler: SeasonNextRoundScheduler; private readonly predictionPointsScheduler: PredictionPointsScheduler; + private readonly leaderboardScheduler: LeaderboardScheduler; private readonly makePredictionsScheduler: MakePredictionsScheduler; static getInstance() { @@ -22,6 +24,7 @@ export class AppSchedule { this.todayAndMorrowScheduler = TodayAndMorrowScheduler.getInstance(); this.seasonNextRoundScheduler = SeasonNextRoundScheduler.getInstance(); this.predictionPointsScheduler = PredictionPointsScheduler.getInstance(); + this.leaderboardScheduler = LeaderboardScheduler.getInstance(); this.makePredictionsScheduler = MakePredictionsScheduler.getInstance(); } @@ -29,6 +32,7 @@ export class AppSchedule { await this.currentRoundMatchesService.updateMatches(); await this.todayAndMorrowScheduler.startJob({ runImmediately: true }); // loop after min/max 90s/6H await this.predictionPointsScheduler.startJob({ runImmediately: true, interval: '0 15 * * * *' }); // minute 15 every H + await this.leaderboardScheduler.startJob({ runImmediately: true }); // loop after 6H await this.seasonNextRoundScheduler.startJob({ interval: '0 0 */2 * * *' }); // minute 0 every 2H await this.makePredictionsScheduler.startJob(); // loop after 3H } diff --git a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts index e4d0d99..14d1feb 100644 --- a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts +++ b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts @@ -10,7 +10,7 @@ const DEFAULT_INTERVAL_MILLISECONDS = 6 * 60 * 60 * 1000; // 6H export class TodayAndMorrowScheduler extends BaseScheduler { private scheduleDate?: Date; private nextPoll?: moment.Moment; // ideally what is supposed to be the next poll - private liveMatchId = null; + private liveMatchId = undefined; private liveMatchHasFinished = false; public static getInstance( @@ -42,12 +42,15 @@ export class TodayAndMorrowScheduler extends BaseScheduler { period = PERIOD.TODAY_AND_MORROW } else if (this.liveMatchHasFinished) { period = PERIOD.TODAY; - } else if (this.liveMatchId != null) { + } else if (this.liveMatchId != undefined) { period = PERIOD.LIVE; } const apiMatches = await this.todayAndMorrowService.syncMatches(period); - if (this.liveMatchId == null && this.liveMatchHasFinished) { + if (this.liveMatchId && this.liveMatchHasFinished) { + console.log(`${this.job.name} publish footballApiLiveMatchUpdatesCompleted`); + this.eventMediator.publish('footballApiLiveMatchUpdatesCompleted'); + } else if (!this.liveMatchId && this.liveMatchHasFinished) { console.log(`${this.job.name} publish footballApiMatchUpdatesCompleted`); this.eventMediator.publish('footballApiMatchUpdatesCompleted'); } @@ -60,39 +63,38 @@ export class TodayAndMorrowScheduler extends BaseScheduler { calculateNextInterval(result: any = []): number { const apiMatches: any[] = result; + let liveMatch: any = apiMatches.find((match: any) => match.id === this.liveMatchId); let nextPoll = moment().add(12, 'hours'); - let liveMatch: any = null; - for (const match of apiMatches) { - const matchStatus = getMatchStatus(match.status); - const matchStart = moment(match.utcDate); - - if (matchStatus === MatchStatus.LIVE) { - if (liveMatch == null) { - liveMatch = match; - } else if (matchStart.isBefore(moment(liveMatch.utcDate))) { - liveMatch = match; - } - if (liveMatch.id === this.liveMatchId) { - break; - } - } else if (matchStatus === MatchStatus.SCHEDULED) { - if (matchStart.isBefore(nextPoll)) { - nextPoll = matchStart.add(1, 'minutes'); + if (!liveMatch) { + for (const match of apiMatches) { + const matchStatus = getMatchStatus(match.status); + const matchStart = moment(match.utcDate); + + if (matchStatus === MatchStatus.LIVE) { + if (!liveMatch) { + liveMatch = match; + } else if (matchStart.isBefore(moment(liveMatch.utcDate))) { + liveMatch = match; + } + } else if (matchStatus === MatchStatus.SCHEDULED) { + if (matchStart.isBefore(nextPoll)) { + nextPoll = matchStart.add(1, 'minutes'); + } } } } const liveMatchId = liveMatch?.id; - if (this.liveMatchId == null && liveMatchId != null) { + if (liveMatchId && !this.liveMatchId) { this.liveMatchId = liveMatchId; - } else if (this.liveMatchId != null && liveMatchId != null) { + } else if (liveMatchId && this.liveMatchId) { if (this.liveMatchId !== liveMatchId) { this.liveMatchId = liveMatchId; this.liveMatchHasFinished = true; } - } else if (this.liveMatchId != null && liveMatchId == null) { - this.liveMatchId = null; + } else if (!liveMatchId && this.liveMatchId) { + this.liveMatchId = undefined; this.liveMatchHasFinished = true; } @@ -100,7 +102,7 @@ export class TodayAndMorrowScheduler extends BaseScheduler { const diff = nextPoll.diff(moment(), 'minutes'); nextPoll = diff <= 0 ? moment().add(1, 'minutes') : nextPoll; - if (liveMatchId != null || this.liveMatchId != null || this.liveMatchHasFinished) { + if (liveMatchId || this.liveMatchId || this.liveMatchHasFinished) { nextPoll = moment().add(1, 'minutes'); } this.nextPoll = nextPoll; diff --git a/backend/app/schedulers/leaderboard.scheduler.ts b/backend/app/schedulers/leaderboard.scheduler.ts new file mode 100644 index 0000000..2da950e --- /dev/null +++ b/backend/app/schedulers/leaderboard.scheduler.ts @@ -0,0 +1,39 @@ +import { LeaderboardService, LeaderboardServiceImpl } from './leaderboard.service'; +import { PredictionService, PredictionServiceImpl } from './prediction.service'; +import { BaseScheduler } from './baseScheduler'; +import { EventMediator, EventMediatorImpl } from '../../common/eventMediator'; + +const DEFAULT_INTERVAL_MILLISECONDS = 6 * 60 * 60 * 1000; // 6H + +export class LeaderboardScheduler extends BaseScheduler { + public static getInstance( + leaderboardService = LeaderboardServiceImpl.getInstance(), + predictionService = PredictionServiceImpl.getInstance(), + eventMediator = EventMediatorImpl.getInstance(), + ) { + return new LeaderboardScheduler(leaderboardService, predictionService, eventMediator); + } + + constructor( + private leaderboardService: LeaderboardService, + private predictionService: PredictionService, + private eventMediator: EventMediator + ) { + super('LeaderboardJob'); + this.eventMediator.addListener( + 'footballApiMatchUpdatesCompleted', async () => { + console.log(`${this.job.name} handle footballApiMatchUpdatesCompleted`); + await this.runJob(); + } + ); + } + + async task() { + await this.predictionService.calculatePredictionPoints(); + await this.leaderboardService.updateGlobalLeaderboards(); + } + + protected getDefaultIntervalMs(): number { + return DEFAULT_INTERVAL_MILLISECONDS; + } +} diff --git a/backend/app/schedulers/predictionPoints.scheduler.ts b/backend/app/schedulers/predictionPoints.scheduler.ts index 525fa37..8799ca8 100644 --- a/backend/app/schedulers/predictionPoints.scheduler.ts +++ b/backend/app/schedulers/predictionPoints.scheduler.ts @@ -1,26 +1,23 @@ import { PredictionService, PredictionServiceImpl } from './prediction.service'; import { BaseScheduler } from './baseScheduler'; import { EventMediator, EventMediatorImpl } from '../../common/eventMediator'; -import { LeaderboardService, LeaderboardServiceImpl } from './leaderboard.service'; export class PredictionPointsScheduler extends BaseScheduler { public static getInstance( predictionsService = PredictionServiceImpl.getInstance(), - leaderboardService = LeaderboardServiceImpl.getInstance(), eventMediator = EventMediatorImpl.getInstance(), ) { - return new PredictionPointsScheduler(predictionsService, leaderboardService, eventMediator); + return new PredictionPointsScheduler(predictionsService, eventMediator); } constructor( private predictionService: PredictionService, - private leaderboardService: LeaderboardService, private eventMediator: EventMediator ) { super('PredictionPointsJob'); this.eventMediator.addListener( - 'footballApiMatchUpdatesCompleted', async () => { - console.log(`${this.job.name} handle footballApiMatchUpdatesCompleted`); + 'footballApiLiveMatchUpdatesCompleted', async () => { + console.log(`${this.job.name} handle footballApiLiveMatchUpdatesCompleted`); await this.runJob(); } ); @@ -28,6 +25,5 @@ export class PredictionPointsScheduler extends BaseScheduler { async task() { await this.predictionService.calculatePredictionPoints(); - await this.leaderboardService.updateGlobalLeaderboards(); } } From 3f6440e550217b4bcd0d2b6d799cc654186152da Mon Sep 17 00:00:00 2001 From: Charles Kagiri Date: Mon, 2 Dec 2024 13:36:27 +0300 Subject: [PATCH 4/4] updates --- .../footballApi/matches.todayAndMorrow.scheduler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts index 14d1feb..55e8fae 100644 --- a/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts +++ b/backend/app/schedulers/footballApi/matches.todayAndMorrow.scheduler.ts @@ -42,7 +42,7 @@ export class TodayAndMorrowScheduler extends BaseScheduler { period = PERIOD.TODAY_AND_MORROW } else if (this.liveMatchHasFinished) { period = PERIOD.TODAY; - } else if (this.liveMatchId != undefined) { + } else if (this.liveMatchId) { period = PERIOD.LIVE; } @@ -102,7 +102,7 @@ export class TodayAndMorrowScheduler extends BaseScheduler { const diff = nextPoll.diff(moment(), 'minutes'); nextPoll = diff <= 0 ? moment().add(1, 'minutes') : nextPoll; - if (liveMatchId || this.liveMatchId || this.liveMatchHasFinished) { + if (this.liveMatchId || this.liveMatchHasFinished) { nextPoll = moment().add(1, 'minutes'); } this.nextPoll = nextPoll;