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; triggerAsync(name: JobGroupKeys): Promise; stopAsync(name: JobGroupKeys): Promise; updateIntervalAsync(name: JobGroupKeys, cron: string): Promise; disableAsync(name: JobGroupKeys): Promise; enableAsync(name: JobGroupKeys): Promise; 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;