From 61d4d3bcd23ddc852e0753ea65cc681056579339 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Thu, 14 May 2026 14:54:55 +0800 Subject: [PATCH] feat: add API endpoints to list and create scheduled tasks Co-Authored-By: Claude Opus 4.7 --- server/api/scheduler/tasks/index.get.ts | 20 +++++++++++ server/api/scheduler/tasks/index.post.ts | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 server/api/scheduler/tasks/index.get.ts create mode 100644 server/api/scheduler/tasks/index.post.ts diff --git a/server/api/scheduler/tasks/index.get.ts b/server/api/scheduler/tasks/index.get.ts new file mode 100644 index 0000000..d8ced1a --- /dev/null +++ b/server/api/scheduler/tasks/index.get.ts @@ -0,0 +1,20 @@ +import { listTasks } from "../../../service/scheduler"; +import { listRegisteredTasks } from "../../../scheduler/registry"; + +export default defineWrappedResponseHandler(async (event) => { + const query = getQuery(event); + const page = query.page ? Number(query.page) : 1; + const pageSize = query.pageSize ? Number(query.pageSize) : 20; + + const result = await listTasks({ + page, + pageSize, + type: query.type as string | undefined, + enabled: query.enabled !== undefined ? Number(query.enabled) : undefined, + }); + + return R.success({ + ...result, + registeredFunctions: listRegisteredTasks(), + }); +}); diff --git a/server/api/scheduler/tasks/index.post.ts b/server/api/scheduler/tasks/index.post.ts new file mode 100644 index 0000000..2fd1590 --- /dev/null +++ b/server/api/scheduler/tasks/index.post.ts @@ -0,0 +1,59 @@ +import { z } from "zod"; +import { createTask } from "../../../service/scheduler"; +import { addTask } from "../../../scheduler/engine"; +import { Cron } from "croner"; + +const createTaskSchema = z.object({ + name: z.string().min(1), + cronExpression: z.string().min(1), + type: z.enum(["function", "http"]), + functionName: z.string().optional(), + functionPayload: z.string().optional(), + httpMethod: z.enum(["GET", "POST", "PUT", "DELETE"]).optional(), + httpUrl: z.string().url().optional(), + httpHeaders: z.string().optional(), + httpBody: z.string().optional(), + catchUp: z.union([z.boolean(), z.number()]).optional(), + enabled: z.union([z.boolean(), z.number()]).optional(), + maxRetries: z.number().int().min(0).optional(), + retryDelaySeconds: z.number().int().min(1).optional(), + timeoutSeconds: z.number().int().min(1).optional(), +}); + +export default defineWrappedResponseHandler(async (event) => { + const body = await readBody(event); + const parsed = createTaskSchema.safeParse(body); + + if (!parsed.success) { + return R.throwError(422, "Validation failed", parsed.error.issues); + } + + // Validate cron expression + try { + new Cron(parsed.data.cronExpression); + } catch { + return R.throwError(422, "Invalid cron expression", null); + } + + // function type must specify functionName + if (parsed.data.type === "function" && !parsed.data.functionName) { + return R.throwError(422, "functionName required for function type", null); + } + + // http type must specify httpUrl + if (parsed.data.type === "http" && !parsed.data.httpUrl) { + return R.throwError(422, "httpUrl required for http type", null); + } + + const task = await createTask({ + ...parsed.data, + catchUp: parsed.data.catchUp ? 1 : 0, + enabled: parsed.data.enabled !== undefined ? (parsed.data.enabled ? 1 : 0) : 1, + }); + + if (task) { + addTask(task.id); + } + + return R.success(task); +});