fix(update-checker): cached through updates (#4046)
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/analytics": "workspace:^0.1.0",
|
"@homarr/analytics": "workspace:^0.1.0",
|
||||||
|
"@homarr/auth": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/cron-job-api": "workspace:^0.1.0",
|
"@homarr/cron-job-api": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"@homarr/log": "workspace:^",
|
"@homarr/log": "workspace:^",
|
||||||
"@homarr/ping": "workspace:^0.1.0",
|
"@homarr/ping": "workspace:^0.1.0",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
|
"@homarr/request-handler": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export class JobManager implements IJobManager {
|
|||||||
logger.info(`Updating cron job interval name="${name}" expression="${cron}"`);
|
logger.info(`Updating cron job interval name="${name}" expression="${cron}"`);
|
||||||
const job = this.jobGroup.getJobRegistry().get(name);
|
const job = this.jobGroup.getJobRegistry().get(name);
|
||||||
if (!job) throw new Error(`Job ${name} not found`);
|
if (!job) throw new Error(`Job ${name} not found`);
|
||||||
if (job.cronExpression === "never") throw new Error(`Job ${name} cannot be updated as it is set to "never"`);
|
|
||||||
if (!validateCron(cron)) {
|
if (!validateCron(cron)) {
|
||||||
throw new Error(`Invalid cron expression: ${cron}`);
|
throw new Error(`Invalid cron expression: ${cron}`);
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,6 @@ export class JobManager implements IJobManager {
|
|||||||
logger.info(`Disabling cron job name="${name}"`);
|
logger.info(`Disabling cron job name="${name}"`);
|
||||||
const job = this.jobGroup.getJobRegistry().get(name);
|
const job = this.jobGroup.getJobRegistry().get(name);
|
||||||
if (!job) throw new Error(`Job ${name} not found`);
|
if (!job) throw new Error(`Job ${name} not found`);
|
||||||
if (job.cronExpression === "never") throw new Error(`Job ${name} cannot be disabled as it is set to "never"`);
|
|
||||||
|
|
||||||
await this.updateConfigurationAsync(name, { isEnabled: false });
|
await this.updateConfigurationAsync(name, { isEnabled: false });
|
||||||
await this.jobGroup.stopAsync(name);
|
await this.jobGroup.stopAsync(name);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { db } from "@homarr/db";
|
|||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
import { JobManager } from "./job-manager";
|
import { JobManager } from "./job-manager";
|
||||||
|
import { onStartAsync } from "./on-start";
|
||||||
|
|
||||||
const server = fastify({
|
const server = fastify({
|
||||||
maxParamLength: 5000,
|
maxParamLength: 5000,
|
||||||
@@ -32,6 +33,7 @@ server.register(fastifyTRPCPlugin, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
await onStartAsync();
|
||||||
await jobGroup.initializeAsync();
|
await jobGroup.initializeAsync();
|
||||||
await jobGroup.startAllAsync();
|
await jobGroup.startAllAsync();
|
||||||
|
|
||||||
|
|||||||
7
apps/tasks/src/on-start/index.ts
Normal file
7
apps/tasks/src/on-start/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { invalidateUpdateCheckerCacheAsync } from "./invalidate-update-checker-cache";
|
||||||
|
import { cleanupSessionsAsync } from "./session-cleanup";
|
||||||
|
|
||||||
|
export async function onStartAsync() {
|
||||||
|
await cleanupSessionsAsync();
|
||||||
|
await invalidateUpdateCheckerCacheAsync();
|
||||||
|
}
|
||||||
18
apps/tasks/src/on-start/invalidate-update-checker-cache.ts
Normal file
18
apps/tasks/src/on-start/invalidate-update-checker-cache.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { logger } from "@homarr/log";
|
||||||
|
import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker";
|
||||||
|
|
||||||
|
const localLogger = logger.child({ module: "invalidateUpdateCheckerCache" });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the update checker cache on startup to ensure fresh data.
|
||||||
|
* It is important as we want to avoid showing pending updates after the update to latest version.
|
||||||
|
*/
|
||||||
|
export async function invalidateUpdateCheckerCacheAsync() {
|
||||||
|
try {
|
||||||
|
const handler = updateCheckerRequestHandler.handler({});
|
||||||
|
await handler.invalidateAsync();
|
||||||
|
localLogger.debug("Update checker cache invalidated");
|
||||||
|
} catch (error) {
|
||||||
|
localLogger.error(new Error("Failed to invalidate update checker cache", { cause: error }));
|
||||||
|
}
|
||||||
|
}
|
||||||
39
apps/tasks/src/on-start/session-cleanup.ts
Normal file
39
apps/tasks/src/on-start/session-cleanup.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { env } from "@homarr/auth/env";
|
||||||
|
import { db, eq, inArray } from "@homarr/db";
|
||||||
|
import { sessions, users } from "@homarr/db/schema";
|
||||||
|
import { supportedAuthProviders } from "@homarr/definitions";
|
||||||
|
import { logger } from "@homarr/log";
|
||||||
|
|
||||||
|
const localLogger = logger.child({ module: "sessionCleanup" });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes sessions for users that have inactive auth providers.
|
||||||
|
* Sessions from other providers are deleted so they can no longer be used.
|
||||||
|
*/
|
||||||
|
export async function cleanupSessionsAsync() {
|
||||||
|
try {
|
||||||
|
const currentAuthProviders = env.AUTH_PROVIDERS;
|
||||||
|
|
||||||
|
const inactiveAuthProviders = supportedAuthProviders.filter((provider) => !currentAuthProviders.includes(provider));
|
||||||
|
const subQuery = db
|
||||||
|
.select({ id: users.id })
|
||||||
|
.from(users)
|
||||||
|
.where(inArray(users.provider, inactiveAuthProviders))
|
||||||
|
.as("sq");
|
||||||
|
const sessionsWithInactiveProviders = await db
|
||||||
|
.select({ userId: sessions.userId })
|
||||||
|
.from(sessions)
|
||||||
|
.rightJoin(subQuery, eq(sessions.userId, subQuery.id));
|
||||||
|
|
||||||
|
const userIds = sessionsWithInactiveProviders.map(({ userId }) => userId).filter((value) => value !== null);
|
||||||
|
await db.delete(sessions).where(inArray(sessions.userId, userIds));
|
||||||
|
|
||||||
|
if (sessionsWithInactiveProviders.length > 0) {
|
||||||
|
localLogger.info(`Deleted sessions for inactive providers count=${userIds.length}`);
|
||||||
|
} else {
|
||||||
|
localLogger.debug("No sessions to delete");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
localLogger.error(new Error("Failed to clean up sessions", { cause: error }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,8 +72,6 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
|
|||||||
where: (cronJobConfigurations, { eq }) => eq(cronJobConfigurations.name, name),
|
where: (cronJobConfigurations, { eq }) => eq(cronJobConfigurations.name, name),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (defaultCronExpression === "never") return null;
|
|
||||||
|
|
||||||
const scheduledTask = createTask(
|
const scheduledTask = createTask(
|
||||||
configuration?.cronExpression ?? defaultCronExpression,
|
configuration?.cronExpression ?? defaultCronExpression,
|
||||||
() => void catchingCallbackAsync(),
|
() => void catchingCallbackAsync(),
|
||||||
@@ -120,7 +118,7 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
|
|||||||
options: CreateCronJobOptions = { runOnStart: false },
|
options: CreateCronJobOptions = { runOnStart: false },
|
||||||
) => {
|
) => {
|
||||||
creatorOptions.logger.logDebug(`Validating cron expression '${defaultCronExpression}' for job: ${name}`);
|
creatorOptions.logger.logDebug(`Validating cron expression '${defaultCronExpression}' for job: ${name}`);
|
||||||
if (defaultCronExpression !== "never" && !validate(defaultCronExpression)) {
|
if (!validate(defaultCronExpression)) {
|
||||||
throw new Error(`Invalid cron expression '${defaultCronExpression}' for job '${name}'`);
|
throw new Error(`Invalid cron expression '${defaultCronExpression}' for job '${name}'`);
|
||||||
}
|
}
|
||||||
creatorOptions.logger.logDebug(`Cron job expression '${defaultCronExpression}' for job ${name} is valid`);
|
creatorOptions.logger.logDebug(`Cron job expression '${defaultCronExpression}' for job ${name} is valid`);
|
||||||
@@ -132,8 +130,6 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
|
|||||||
// This is a type guard to check if the cron expression is valid and give the user a type hint
|
// This is a type guard to check if the cron expression is valid and give the user a type hint
|
||||||
return returnValue as unknown as ValidateCron<TExpression> extends true
|
return returnValue as unknown as ValidateCron<TExpression> extends true
|
||||||
? typeof returnValue
|
? typeof returnValue
|
||||||
: TExpression extends "never"
|
: "Invalid cron expression";
|
||||||
? typeof returnValue
|
|
||||||
: "Invalid cron expression";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ export const EVERY_10_MINUTES = checkCron("*/10 * * * *") satisfies string;
|
|||||||
export const EVERY_HOUR = checkCron("0 * * * *") satisfies string;
|
export const EVERY_HOUR = checkCron("0 * * * *") satisfies string;
|
||||||
export const EVERY_DAY = checkCron("0 0 * * */1") satisfies string;
|
export const EVERY_DAY = checkCron("0 0 * * */1") satisfies string;
|
||||||
export const EVERY_WEEK = checkCron("0 0 * * 1") satisfies string;
|
export const EVERY_WEEK = checkCron("0 0 * * 1") satisfies string;
|
||||||
export const NEVER = "never";
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const scheduledTask = await job.createTaskAsync();
|
const scheduledTask = await job.createTaskAsync();
|
||||||
if (!scheduledTask) continue;
|
|
||||||
|
|
||||||
tasks.set(job.name, scheduledTask);
|
tasks.set(job.name, scheduledTask);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { refreshNotificationsJob } from "./jobs/integrations/notifications";
|
|||||||
import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
|
import { minecraftServerStatusJob } from "./jobs/minecraft-server-status";
|
||||||
import { pingJob } from "./jobs/ping";
|
import { pingJob } from "./jobs/ping";
|
||||||
import { rssFeedsJob } from "./jobs/rss-feeds";
|
import { rssFeedsJob } from "./jobs/rss-feeds";
|
||||||
import { sessionCleanupJob } from "./jobs/session-cleanup";
|
|
||||||
import { updateCheckerJob } from "./jobs/update-checker";
|
import { updateCheckerJob } from "./jobs/update-checker";
|
||||||
import { createCronJobGroup } from "./lib";
|
import { createCronJobGroup } from "./lib";
|
||||||
|
|
||||||
@@ -39,7 +38,6 @@ export const jobGroup = createCronJobGroup({
|
|||||||
rssFeeds: rssFeedsJob,
|
rssFeeds: rssFeedsJob,
|
||||||
indexerManager: indexerManagerJob,
|
indexerManager: indexerManagerJob,
|
||||||
healthMonitoring: healthMonitoringJob,
|
healthMonitoring: healthMonitoringJob,
|
||||||
sessionCleanup: sessionCleanupJob,
|
|
||||||
updateChecker: updateCheckerJob,
|
updateChecker: updateCheckerJob,
|
||||||
mediaTranscoding: mediaTranscodingJob,
|
mediaTranscoding: mediaTranscodingJob,
|
||||||
minecraftServerStatus: minecraftServerStatusJob,
|
minecraftServerStatus: minecraftServerStatusJob,
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { env } from "@homarr/auth/env";
|
|
||||||
import { NEVER } from "@homarr/cron-jobs-core/expressions";
|
|
||||||
import { db, eq, inArray } from "@homarr/db";
|
|
||||||
import { sessions, users } from "@homarr/db/schema";
|
|
||||||
import { supportedAuthProviders } from "@homarr/definitions";
|
|
||||||
import { logger } from "@homarr/log";
|
|
||||||
|
|
||||||
import { createCronJob } from "../lib";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes sessions for users that have inactive auth providers.
|
|
||||||
* Sessions from other providers are deleted so they can no longer be used.
|
|
||||||
*/
|
|
||||||
export const sessionCleanupJob = createCronJob("sessionCleanup", NEVER, {
|
|
||||||
runOnStart: true,
|
|
||||||
}).withCallback(async () => {
|
|
||||||
const currentAuthProviders = env.AUTH_PROVIDERS;
|
|
||||||
|
|
||||||
const inactiveAuthProviders = supportedAuthProviders.filter((provider) => !currentAuthProviders.includes(provider));
|
|
||||||
const subQuery = db
|
|
||||||
.select({ id: users.id })
|
|
||||||
.from(users)
|
|
||||||
.where(inArray(users.provider, inactiveAuthProviders))
|
|
||||||
.as("sq");
|
|
||||||
const sessionsWithInactiveProviders = await db
|
|
||||||
.select({ userId: sessions.userId })
|
|
||||||
.from(sessions)
|
|
||||||
.rightJoin(subQuery, eq(sessions.userId, subQuery.id));
|
|
||||||
|
|
||||||
const userIds = sessionsWithInactiveProviders.map(({ userId }) => userId).filter((value) => value !== null);
|
|
||||||
await db.delete(sessions).where(inArray(sessions.userId, userIds));
|
|
||||||
|
|
||||||
if (sessionsWithInactiveProviders.length > 0) {
|
|
||||||
logger.info(`Deleted sessions for inactive providers count=${userIds.length}`);
|
|
||||||
} else {
|
|
||||||
logger.debug("No sessions to delete");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -3255,9 +3255,6 @@
|
|||||||
"dnsHole": {
|
"dnsHole": {
|
||||||
"label": "DNS Hole Data"
|
"label": "DNS Hole Data"
|
||||||
},
|
},
|
||||||
"sessionCleanup": {
|
|
||||||
"label": "Session Cleanup"
|
|
||||||
},
|
|
||||||
"updateChecker": {
|
"updateChecker": {
|
||||||
"label": "Update checker"
|
"label": "Update checker"
|
||||||
},
|
},
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -373,6 +373,9 @@ importers:
|
|||||||
'@homarr/analytics':
|
'@homarr/analytics':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/analytics
|
version: link:../../packages/analytics
|
||||||
|
'@homarr/auth':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../../packages/auth
|
||||||
'@homarr/common':
|
'@homarr/common':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/common
|
version: link:../../packages/common
|
||||||
@@ -406,6 +409,9 @@ importers:
|
|||||||
'@homarr/redis':
|
'@homarr/redis':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/redis
|
version: link:../../packages/redis
|
||||||
|
'@homarr/request-handler':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../../packages/request-handler
|
||||||
'@homarr/server-settings':
|
'@homarr/server-settings':
|
||||||
specifier: workspace:^0.1.0
|
specifier: workspace:^0.1.0
|
||||||
version: link:../../packages/server-settings
|
version: link:../../packages/server-settings
|
||||||
|
|||||||
Reference in New Issue
Block a user