Files
homarr/apps/tasks/src/jobs/icons-updater.ts
Meier Lukas 92afd82d22 refactor: add cron job core package (#704)
* refactor: add cron job core package

* docs: add comments to cron validation types

* chore(deps): move node-cron dependencies from tasks app to cron-jobs-core package

* fix: runOnInit is not running on start and rather on job creation

* fix: format issues

* fix: build fails when using top level await in cjs

* chore: update turbo gen package json typescript version to 5.5.2

* fix: format issue

* fix: deepsource issues

* chore: update turbo gen package json eslint version to 9.5.0

* chore: fix frozen lockfile and format
2024-06-22 20:00:20 +02:00

98 lines
3.1 KiB
TypeScript

import { Stopwatch } from "@homarr/common";
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
import type { InferInsertModel } from "@homarr/db";
import { db, inArray } from "@homarr/db";
import { createId } from "@homarr/db/client";
import { iconRepositories, icons } from "@homarr/db/schema/sqlite";
import { fetchIconsAsync } from "@homarr/icons";
import { logger } from "@homarr/log";
import { createCronJob } from "~/lib/jobs";
export const iconsUpdaterJob = createCronJob("iconsUpdater", 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 skippedChecksums: string[] = [];
let countDeleted = 0;
let countInserted = 0;
logger.info("Updating icons in database...");
stopWatch.reset();
const newIconRepositories: InferInsertModel<typeof iconRepositories>[] = [];
const newIcons: InferInsertModel<typeof icons>[] = [];
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) {
newIconRepositories.push({
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;
}
newIcons.push({
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));
await db.transaction(async (transaction) => {
if (newIconRepositories.length >= 1) {
await transaction.insert(iconRepositories).values(newIconRepositories);
}
if (newIcons.length >= 1) {
await transaction.insert(icons).values(newIcons);
}
if (deadIcons.length >= 1) {
await transaction.delete(icons).where(
inArray(
icons.checksum,
deadIcons.map((icon) => icon.checksum),
),
);
}
countDeleted += deadIcons.length;
});
logger.info(`Updated database within ${stopWatch.getElapsedInHumanWords()} (-${countDeleted}, +${countInserted})`);
});