diff --git a/apps/tasks/src/jobs/icons-updater.ts b/apps/tasks/src/jobs/icons-updater.ts index 42ae791f8..bd69415d6 100644 --- a/apps/tasks/src/jobs/icons-updater.ts +++ b/apps/tasks/src/jobs/icons-updater.ts @@ -8,83 +8,81 @@ import { logger } from "@homarr/log"; import { EVERY_WEEK } from "~/lib/cron-job/constants"; import { createCronJob } from "~/lib/cron-job/creator"; -export const iconsUpdaterJob = createCronJob(EVERY_WEEK).withCallback( - async () => { - logger.info(`Updating icon repository cache...`); - const stopWatch = new Stopwatch(); - const repositoryIconGroups = await fetchIconsAsync(); - const countIcons = repositoryIconGroups - .map((group) => group.icons.length) - .reduce((partialSum, arrayLength) => partialSum + arrayLength, 0); - logger.info( - `Successfully fetched ${countIcons} icons from ${repositoryIconGroups.length} repositories within ${stopWatch.getElapsedInHumanWords()}`, - ); +export const iconsUpdaterJob = createCronJob(EVERY_WEEK, { + runOnStart: true, +}).withCallback(async () => { + logger.info("Updating icon repository cache..."); + const stopWatch = new Stopwatch(); + const repositoryIconGroups = await fetchIconsAsync(); + const countIcons = repositoryIconGroups + .map((group) => group.icons.length) + .reduce((partialSum, arrayLength) => partialSum + arrayLength, 0); + logger.info( + `Successfully fetched ${countIcons} icons from ${repositoryIconGroups.length} repositories within ${stopWatch.getElapsedInHumanWords()}`, + ); - const databaseIconGroups = await db.query.iconRepositories.findMany({ - with: { - icons: true, - }, - }); + const databaseIconGroups = await db.query.iconRepositories.findMany({ + with: { + icons: true, + }, + }); - const skippedChecksums: string[] = []; - let countDeleted = 0; - let countInserted = 0; + const skippedChecksums: string[] = []; + let countDeleted = 0; + let countInserted = 0; - logger.info(`Updating icons in database...`); - stopWatch.reset(); + logger.info("Updating icons in database..."); + stopWatch.reset(); - await db.transaction(async (transaction) => { - for (const repositoryIconGroup of repositoryIconGroups) { - if (!repositoryIconGroup.success) { + await db.transaction(async (transaction) => { + for (const repositoryIconGroup of repositoryIconGroups) { + if (!repositoryIconGroup.success) { + continue; + } + + const repositoryInDb = databaseIconGroups.find( + (dbIconGroup) => dbIconGroup.slug === repositoryIconGroup.slug, + ); + const repositoryIconGroupId: string = repositoryInDb?.id ?? createId(); + if (!repositoryInDb?.id) { + await transaction.insert(iconRepositories).values({ + id: repositoryIconGroupId, + slug: repositoryIconGroup.slug, + }); + } + + for (const icon of repositoryIconGroup.icons) { + if ( + databaseIconGroups + .flatMap((group) => group.icons) + .some((dbIcon) => dbIcon.checksum === icon.checksum) + ) { + skippedChecksums.push(icon.checksum); continue; } - const repositoryInDb = databaseIconGroups.find( - (dbIconGroup) => dbIconGroup.slug === repositoryIconGroup.slug, - ); - const repositoryIconGroupId: string = repositoryInDb?.id ?? createId(); - if (!repositoryInDb?.id) { - await transaction.insert(iconRepositories).values({ - id: repositoryIconGroupId, - slug: repositoryIconGroup.slug, - }); - } - - for (const icon of repositoryIconGroup.icons) { - if ( - databaseIconGroups - .flatMap((group) => group.icons) - .some((dbIcon) => dbIcon.checksum == icon.checksum) - ) { - skippedChecksums.push(icon.checksum); - continue; - } - - await transaction.insert(icons).values({ - id: createId(), - checksum: icon.checksum, - name: icon.fileNameWithExtension, - url: icon.imageUrl.href, - iconRepositoryId: repositoryIconGroupId, - }); - countInserted++; - } + await transaction.insert(icons).values({ + id: createId(), + checksum: icon.checksum, + name: icon.fileNameWithExtension, + url: icon.imageUrl.href, + iconRepositoryId: repositoryIconGroupId, + }); + countInserted++; } + } - const deadIcons = databaseIconGroups - .flatMap((group) => group.icons) - .filter((icon) => !skippedChecksums.includes(icon.checksum)); + const deadIcons = databaseIconGroups + .flatMap((group) => group.icons) + .filter((icon) => !skippedChecksums.includes(icon.checksum)); - for (const icon of deadIcons) { - await transaction - .delete(icons) - .where(eq(icons.checksum, icon.checksum)); - countDeleted++; - } - }); + for (const icon of deadIcons) { + await transaction.delete(icons).where(eq(icons.checksum, icon.checksum)); + countDeleted++; + } + }); - logger.info( - `Updated database within ${stopWatch.getElapsedInHumanWords()} (-${countDeleted}, +${countInserted})`, - ); - }, -); + logger.info( + `Updated database within ${stopWatch.getElapsedInHumanWords()} (-${countDeleted}, +${countInserted})`, + ); +}); diff --git a/apps/tasks/src/lib/cron-job/creator.ts b/apps/tasks/src/lib/cron-job/creator.ts index 4a3825088..8f7b304be 100644 --- a/apps/tasks/src/lib/cron-job/creator.ts +++ b/apps/tasks/src/lib/cron-job/creator.ts @@ -2,9 +2,20 @@ import cron from "node-cron"; import type { MaybePromise } from "@homarr/common/types"; -export const createCronJob = (cronExpression: string) => { +interface CreateCronJobOptions { + runOnStart?: boolean; +} + +export const createCronJob = ( + cronExpression: string, + options: CreateCronJobOptions = { runOnStart: false }, +) => { return { withCallback: (callback: () => MaybePromise) => { + if (options.runOnStart) { + void callback(); + } + const task = cron.schedule(cronExpression, () => void callback(), { scheduled: false, });