feat(tasks): allow management of job intervals and disabling them (#3408)
This commit is contained in:
20
packages/cron-job-api/src/client.ts
Normal file
20
packages/cron-job-api/src/client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createTRPCClient, httpLink } from "@trpc/client";
|
||||
|
||||
import type { JobRouter } from ".";
|
||||
import { CRON_JOB_API_KEY_HEADER, CRON_JOB_API_PATH, CRON_JOB_API_PORT } from "./constants";
|
||||
import { env } from "./env";
|
||||
|
||||
export const cronJobApi = createTRPCClient<JobRouter>({
|
||||
links: [
|
||||
httpLink({
|
||||
url: `${getBaseUrl()}${CRON_JOB_API_PATH}`,
|
||||
headers: {
|
||||
[CRON_JOB_API_KEY_HEADER]: env.CRON_JOB_API_KEY,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
function getBaseUrl() {
|
||||
return `http://localhost:${CRON_JOB_API_PORT}`;
|
||||
}
|
||||
3
packages/cron-job-api/src/constants.ts
Normal file
3
packages/cron-job-api/src/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const CRON_JOB_API_PORT = 3002;
|
||||
export const CRON_JOB_API_PATH = "/trpc";
|
||||
export const CRON_JOB_API_KEY_HEADER = "cron-job-api-key";
|
||||
11
packages/cron-job-api/src/env.ts
Normal file
11
packages/cron-job-api/src/env.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { env as commonEnv } from "@homarr/common/env";
|
||||
import { createEnv } from "@homarr/env";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
CRON_JOB_API_KEY: commonEnv.NODE_ENV === "development" ? z.string().default("test") : z.string(),
|
||||
},
|
||||
experimental__runtimeEnv: process.env,
|
||||
});
|
||||
82
packages/cron-job-api/src/index.ts
Normal file
82
packages/cron-job-api/src/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import { validate } from "node-cron";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { JobGroupKeys } from "@homarr/cron-jobs";
|
||||
import { jobGroup } from "@homarr/cron-jobs";
|
||||
|
||||
import { env } from "./env";
|
||||
|
||||
export const jobGroupKeys = jobGroup.getKeys();
|
||||
export const jobNameSchema = z.enum(jobGroup.getKeys());
|
||||
|
||||
export interface IJobManager {
|
||||
startAsync(name: JobGroupKeys): Promise<void>;
|
||||
triggerAsync(name: JobGroupKeys): Promise<void>;
|
||||
stopAsync(name: JobGroupKeys): Promise<void>;
|
||||
updateIntervalAsync(name: JobGroupKeys, cron: string): Promise<void>;
|
||||
disableAsync(name: JobGroupKeys): Promise<void>;
|
||||
enableAsync(name: JobGroupKeys): Promise<void>;
|
||||
getAllAsync(): Promise<{ name: JobGroupKeys; cron: string; preventManualExecution: boolean; isEnabled: boolean }[]>;
|
||||
}
|
||||
|
||||
const t = initTRPC
|
||||
.context<{
|
||||
manager: IJobManager;
|
||||
apiKey?: string;
|
||||
}>()
|
||||
.create();
|
||||
|
||||
const createTrpcRouter = t.router;
|
||||
const apiKeyProcedure = t.procedure.use(({ ctx, next }) => {
|
||||
if (ctx.apiKey !== env.CRON_JOB_API_KEY) {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Missing or invalid API key",
|
||||
});
|
||||
}
|
||||
|
||||
return next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
apiKey: undefined, // Clear the API key after checking
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export const cronExpressionSchema = z.string().refine((expression) => validate(expression), {
|
||||
error: "Invalid cron expression",
|
||||
});
|
||||
|
||||
export const jobRouter = createTrpcRouter({
|
||||
start: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
||||
await ctx.manager.startAsync(input);
|
||||
}),
|
||||
trigger: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
||||
await ctx.manager.triggerAsync(input);
|
||||
}),
|
||||
stop: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
||||
await ctx.manager.stopAsync(input);
|
||||
}),
|
||||
updateInterval: apiKeyProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: jobNameSchema,
|
||||
cron: cronExpressionSchema,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await ctx.manager.updateIntervalAsync(input.name, input.cron);
|
||||
}),
|
||||
disable: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
||||
await ctx.manager.disableAsync(input);
|
||||
}),
|
||||
enable: apiKeyProcedure.input(jobNameSchema).mutation(async ({ input, ctx }) => {
|
||||
await ctx.manager.enableAsync(input);
|
||||
}),
|
||||
getAll: apiKeyProcedure.query(({ ctx }) => {
|
||||
return ctx.manager.getAllAsync();
|
||||
}),
|
||||
});
|
||||
|
||||
export type JobRouter = typeof jobRouter;
|
||||
Reference in New Issue
Block a user