cr
This commit is contained in:
+941
-1210
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import { FeederService } from '../modules/feeder/feeder.service';
|
||||
import { HistoricalResultsSyncTask } from './historical-results-sync.task';
|
||||
import { FeederService } from "../modules/feeder/feeder.service";
|
||||
import { HistoricalResultsSyncTask } from "./historical-results-sync.task";
|
||||
|
||||
describe('HistoricalResultsSyncTask', () => {
|
||||
describe("HistoricalResultsSyncTask", () => {
|
||||
const runPreviousDayCompletedMatchesScan = jest.fn();
|
||||
let task: HistoricalResultsSyncTask;
|
||||
|
||||
@@ -18,14 +18,14 @@ describe('HistoricalResultsSyncTask', () => {
|
||||
delete process.env.FEEDER_MODE;
|
||||
});
|
||||
|
||||
it('calls feeder service in normal mode', async () => {
|
||||
it("calls feeder service in normal mode", async () => {
|
||||
await task.syncPreviousDayCompletedMatches();
|
||||
|
||||
expect(runPreviousDayCompletedMatchesScan).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('skips execution in historical feeder mode', async () => {
|
||||
process.env.FEEDER_MODE = 'historical';
|
||||
it("skips execution in historical feeder mode", async () => {
|
||||
process.env.FEEDER_MODE = "historical";
|
||||
|
||||
await task.syncPreviousDayCompletedMatches();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { FeederService } from '../modules/feeder/feeder.service';
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Cron } from "@nestjs/schedule";
|
||||
import { FeederService } from "../modules/feeder/feeder.service";
|
||||
|
||||
@Injectable()
|
||||
export class HistoricalResultsSyncTask {
|
||||
@@ -9,7 +9,7 @@ export class HistoricalResultsSyncTask {
|
||||
constructor(private readonly feederService: FeederService) {}
|
||||
|
||||
private shouldSkipInHistoricalMode(jobName: string): boolean {
|
||||
if (process.env.FEEDER_MODE === 'historical') {
|
||||
if (process.env.FEEDER_MODE === "historical") {
|
||||
this.logger.debug(`Skipping ${jobName} in historical feeder mode`);
|
||||
return true;
|
||||
}
|
||||
@@ -19,19 +19,19 @@ export class HistoricalResultsSyncTask {
|
||||
/**
|
||||
* Pull yesterday's completed matches into the permanent matches table.
|
||||
*/
|
||||
@Cron('0 8 * * *', { timeZone: 'Europe/Istanbul' })
|
||||
@Cron("0 8 * * *", { timeZone: "Europe/Istanbul" })
|
||||
async syncPreviousDayCompletedMatches() {
|
||||
if (this.shouldSkipInHistoricalMode('syncPreviousDayCompletedMatches')) {
|
||||
if (this.shouldSkipInHistoricalMode("syncPreviousDayCompletedMatches")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
'Starting previous-day completed match sync for football and basketball...',
|
||||
"Starting previous-day completed match sync for football and basketball...",
|
||||
);
|
||||
|
||||
try {
|
||||
await this.feederService.runPreviousDayCompletedMatchesScan();
|
||||
this.logger.log('Previous-day completed match sync finished');
|
||||
this.logger.log("Previous-day completed match sync finished");
|
||||
} catch (error: any) {
|
||||
this.logger.error(
|
||||
`Previous-day completed match sync failed: ${error.message}`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Cron } from "@nestjs/schedule";
|
||||
import { PrismaService } from "../database/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class LimitResetterTask {
|
||||
@@ -9,7 +9,7 @@ export class LimitResetterTask {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
private shouldSkipInHistoricalMode(jobName: string): boolean {
|
||||
if (process.env.FEEDER_MODE === 'historical') {
|
||||
if (process.env.FEEDER_MODE === "historical") {
|
||||
this.logger.debug(`Skipping ${jobName} in historical feeder mode`);
|
||||
return true;
|
||||
}
|
||||
@@ -19,10 +19,10 @@ export class LimitResetterTask {
|
||||
/**
|
||||
* Reset usage limits daily at 03:00 (Europe/Istanbul)
|
||||
*/
|
||||
@Cron('0 3 * * *', { timeZone: 'Europe/Istanbul' })
|
||||
@Cron("0 3 * * *", { timeZone: "Europe/Istanbul" })
|
||||
async resetUsageLimits() {
|
||||
if (this.shouldSkipInHistoricalMode('resetUsageLimits')) return;
|
||||
this.logger.log('Starting daily usage limit reset job...');
|
||||
if (this.shouldSkipInHistoricalMode("resetUsageLimits")) return;
|
||||
this.logger.log("Starting daily usage limit reset job...");
|
||||
|
||||
try {
|
||||
const today = new Date();
|
||||
@@ -45,7 +45,7 @@ export class LimitResetterTask {
|
||||
`Usage limits for ${result.count} users have been reset`,
|
||||
);
|
||||
} else {
|
||||
this.logger.log('No user limits needed resetting');
|
||||
this.logger.log("No user limits needed resetting");
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Limit reset job failed: ${error.message}`);
|
||||
@@ -55,10 +55,10 @@ export class LimitResetterTask {
|
||||
/**
|
||||
* Clean up old predictions (older than 30 days)
|
||||
*/
|
||||
@Cron('0 4 * * *', { timeZone: 'Europe/Istanbul' })
|
||||
@Cron("0 4 * * *", { timeZone: "Europe/Istanbul" })
|
||||
async cleanupOldData() {
|
||||
if (this.shouldSkipInHistoricalMode('cleanupOldData')) return;
|
||||
this.logger.log('Starting data cleanup job...');
|
||||
if (this.shouldSkipInHistoricalMode("cleanupOldData")) return;
|
||||
this.logger.log("Starting data cleanup job...");
|
||||
|
||||
try {
|
||||
const thirtyDaysAgo = new Date();
|
||||
@@ -78,7 +78,7 @@ export class LimitResetterTask {
|
||||
|
||||
const deletedLiveMatches = await this.prisma.liveMatch.deleteMany({
|
||||
where: {
|
||||
state: 'Finished',
|
||||
state: "Finished",
|
||||
updatedAt: { lt: oneDayAgo },
|
||||
},
|
||||
});
|
||||
@@ -94,21 +94,21 @@ export class LimitResetterTask {
|
||||
/**
|
||||
* Reset subscription status for expired users
|
||||
*/
|
||||
@Cron('0 0 * * *', { timeZone: 'Europe/Istanbul' })
|
||||
@Cron("0 0 * * *", { timeZone: "Europe/Istanbul" })
|
||||
async checkSubscriptions() {
|
||||
if (this.shouldSkipInHistoricalMode('checkSubscriptions')) return;
|
||||
this.logger.log('Checking expired subscriptions...');
|
||||
if (this.shouldSkipInHistoricalMode("checkSubscriptions")) return;
|
||||
this.logger.log("Checking expired subscriptions...");
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
const result = await this.prisma.user.updateMany({
|
||||
where: {
|
||||
subscriptionStatus: 'active',
|
||||
subscriptionStatus: "active",
|
||||
subscriptionExpiresAt: { lt: now },
|
||||
},
|
||||
data: {
|
||||
subscriptionStatus: 'expired',
|
||||
subscriptionStatus: "expired",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { PrismaService } from '../database/prisma.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class LiveUpdaterTask {
|
||||
private readonly logger = new Logger(LiveUpdaterTask.name);
|
||||
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
private shouldSkipInHistoricalMode(jobName: string): boolean {
|
||||
if (process.env.FEEDER_MODE === 'historical') {
|
||||
this.logger.debug(`Skipping ${jobName} in historical feeder mode`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update live match scores every 5 minutes
|
||||
*/
|
||||
@Cron('*/15 * * * *') // Every 15 minutes
|
||||
async updateLiveScores() {
|
||||
if (this.shouldSkipInHistoricalMode('updateLiveScores')) return;
|
||||
this.logger.debug('Updating live scores...');
|
||||
|
||||
try {
|
||||
// Get all live matches
|
||||
const liveMatches = await this.prisma.liveMatch.findMany({
|
||||
where: {
|
||||
state: {
|
||||
in: ['live', 'firsthalf', 'secondhalf', '1H', '2H', 'HT', 'LIVE'],
|
||||
},
|
||||
},
|
||||
select: { id: true, matchSlug: true },
|
||||
});
|
||||
|
||||
if (liveMatches.length === 0) {
|
||||
this.logger.debug('No live matches to update');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`Updating ${liveMatches.length} live matches`);
|
||||
|
||||
// Fetch scores for each live match
|
||||
for (const match of liveMatches) {
|
||||
try {
|
||||
const url = `https://www.mackolik.com/ajax/football/match-info?matchId=${match.id}`;
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get(url, { timeout: 5000 }),
|
||||
);
|
||||
|
||||
if (response.data?.data) {
|
||||
const matchData = response.data.data;
|
||||
|
||||
await this.prisma.liveMatch.update({
|
||||
where: { id: match.id },
|
||||
data: {
|
||||
scoreHome: matchData.homeScore ?? null,
|
||||
scoreAway: matchData.awayScore ?? null,
|
||||
state: matchData.state || matchData.status,
|
||||
status: matchData.status,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Individual match update failed, continue with others
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log('Live score update complete');
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Live update failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update finished match results every 30 minutes
|
||||
*/
|
||||
@Cron('*/30 * * * *')
|
||||
async finalizeFinishedMatches() {
|
||||
if (this.shouldSkipInHistoricalMode('finalizeFinishedMatches')) return;
|
||||
this.logger.log('Finalizing finished matches...');
|
||||
|
||||
try {
|
||||
// Find recently finished matches that need final data
|
||||
const finishedMatches = await this.prisma.liveMatch.findMany({
|
||||
where: {
|
||||
state: 'Finished',
|
||||
updatedAt: {
|
||||
gte: new Date(Date.now() - 60 * 60 * 1000), // Last hour
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
matchSlug: true,
|
||||
homeTeamId: true,
|
||||
awayTeamId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (finishedMatches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`Finalizing ${finishedMatches.length} matches`);
|
||||
|
||||
for (const liveMatch of finishedMatches) {
|
||||
try {
|
||||
// Check if permanent match record exists
|
||||
const existingMatch = await this.prisma.match.findUnique({
|
||||
where: { id: liveMatch.id },
|
||||
});
|
||||
|
||||
if (!existingMatch) {
|
||||
// Create permanent match record from live match
|
||||
const liveData = await this.prisma.liveMatch.findUnique({
|
||||
where: { id: liveMatch.id },
|
||||
});
|
||||
|
||||
if (liveData) {
|
||||
await this.prisma.match.create({
|
||||
data: {
|
||||
id: liveData.id,
|
||||
matchName: liveData.matchName,
|
||||
matchSlug: liveData.matchSlug,
|
||||
sport: (liveData.sport || 'football') as any,
|
||||
leagueId: liveData.leagueId,
|
||||
homeTeamId: liveData.homeTeamId,
|
||||
awayTeamId: liveData.awayTeamId,
|
||||
mstUtc: liveData.mstUtc ?? BigInt(Date.now()),
|
||||
scoreHome: liveData.scoreHome,
|
||||
scoreAway: liveData.scoreAway,
|
||||
state: 'Finished',
|
||||
status: 'Finished',
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Migrated match ${liveData.id} to permanent storage`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Update existing match with final score
|
||||
const liveData = await this.prisma.liveMatch.findUnique({
|
||||
where: { id: liveMatch.id },
|
||||
});
|
||||
|
||||
if (liveData) {
|
||||
await this.prisma.match.update({
|
||||
where: { id: liveMatch.id },
|
||||
data: {
|
||||
scoreHome: liveData.scoreHome,
|
||||
scoreAway: liveData.scoreAway,
|
||||
state: 'Finished',
|
||||
status: 'Finished',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
this.logger.warn(
|
||||
`Failed to finalize match ${liveMatch.id}: ${err.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Finalize job failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-23
@@ -1,12 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { DataFetcherTask } from './data-fetcher.task';
|
||||
import { HistoricalResultsSyncTask } from './historical-results-sync.task';
|
||||
import { LimitResetterTask } from './limit-resetter.task';
|
||||
import { LiveUpdaterTask } from './live-updater.task';
|
||||
import { DatabaseModule } from '../database/database.module';
|
||||
import { FeederModule } from '../modules/feeder/feeder.module';
|
||||
import { Module } from "@nestjs/common";
|
||||
import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { HttpModule } from "@nestjs/axios";
|
||||
import { DataFetcherTask } from "./data-fetcher.task";
|
||||
import { HistoricalResultsSyncTask } from "./historical-results-sync.task";
|
||||
import { LimitResetterTask } from "./limit-resetter.task";
|
||||
import { DatabaseModule } from "../database/database.module";
|
||||
import { FeederModule } from "../modules/feeder/feeder.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -14,24 +13,14 @@ import { FeederModule } from '../modules/feeder/feeder.module';
|
||||
HttpModule.register({
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
||||
},
|
||||
}),
|
||||
DatabaseModule,
|
||||
FeederModule,
|
||||
],
|
||||
providers: [
|
||||
DataFetcherTask,
|
||||
HistoricalResultsSyncTask,
|
||||
LimitResetterTask,
|
||||
LiveUpdaterTask,
|
||||
],
|
||||
exports: [
|
||||
DataFetcherTask,
|
||||
HistoricalResultsSyncTask,
|
||||
LimitResetterTask,
|
||||
LiveUpdaterTask,
|
||||
],
|
||||
providers: [DataFetcherTask, HistoricalResultsSyncTask, LimitResetterTask],
|
||||
exports: [DataFetcherTask, HistoricalResultsSyncTask, LimitResetterTask],
|
||||
})
|
||||
export class TasksModule {}
|
||||
|
||||
Reference in New Issue
Block a user