import { Injectable, Logger, OnApplicationBootstrap } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { SpotifyService } from "./spotify/spotify.service"; import { CrawlerSupervisorJob, ICrawlerSupervisorJob, IImportSpotifyJob, ImportSpotifyJob, IUpdateSpotifyLibraryJob, UpdateSpotifyLibraryJob, } from "./jobs"; import { JobService } from "@apricote/nest-pg-boss"; import { Span } from "nestjs-otel"; @Injectable() export class SchedulerService implements OnApplicationBootstrap { private readonly logger = new Logger(this.constructor.name); constructor( private readonly config: ConfigService, private readonly spotifyService: SpotifyService, @CrawlerSupervisorJob.Inject() private readonly superviseImportJobsJobService: JobService, @ImportSpotifyJob.Inject() private readonly importSpotifyJobService: JobService, @UpdateSpotifyLibraryJob.Inject() private readonly updateSpotifyLibraryJobService: JobService, ) {} async onApplicationBootstrap() { await this.setupSpotifyCrawlerSupervisor(); await this.setupSpotifyMusicLibraryUpdater(); } private async setupSpotifyCrawlerSupervisor(): Promise { // await this.superviseImportJobsJobService.schedule("*/1 * * * *", {}, {}); } @Span() @CrawlerSupervisorJob.Handle() async superviseImportJobs(): Promise { this.logger.log("Starting crawler jobs"); const userInfo = await this.spotifyService.getCrawlableUserInfo(); // To save on Spotify API requests we have two different classes of polling intervals: // - all users are polled at least every 10 minutes, this is a safe interval // and no listens will be ever missed // - if a user listened to a song within the last 60 minutes, we poll every // minute to ensure that the UI shows new listens immediately const POLL_RATE_INACTIVE_SEC = 10 * 60; const POLL_RATE_ACTIVE_SEC = 1 * 60; const INACTIVE_CUTOFF_MSEC = 60 * 60 * 1000; await Promise.all( userInfo.map(({ userID, lastListen }) => { let pollRate = POLL_RATE_INACTIVE_SEC; const timeSinceLastListen = new Date().getTime() - lastListen.getTime(); if (timeSinceLastListen < INACTIVE_CUTOFF_MSEC) { pollRate = POLL_RATE_ACTIVE_SEC; } this.importSpotifyJobService.sendThrottled( { userID }, {}, pollRate, userID, ); }), ); } private async setupSpotifyMusicLibraryUpdater() { await this.updateSpotifyLibraryJobService.schedule("*/1 * * * *", {}, {}); } @UpdateSpotifyLibraryJob.Handle() async updateSpotifyLibrary() { this.spotifyService.runUpdaterForAllEntities(); } }