Skip to content

Commit fec8e0a

Browse files
committed
fix(schedules): stop the API accepting actions it no longer handles
Adversarial pass on the scheduled-task removal found a real regression in PUT /api/schedules/[id]. Removing the job-only `update` and `exclude_occurrence` handlers left them in `scheduleUpdateSchema`, so those bodies still parsed. The handler chain is `disable` first and then an unguarded fall-through to reactivate, so an `action: 'update'` request would have silently REACTIVATED the schedule instead of being rejected. Both actions are dropped from the discriminated union, so `parseRequest` now rejects them with a 400. Their bodies, response types and the orphaned `createScheduleContract` (its POST route is gone, and nothing imported it) go with them.
1 parent 94ce869 commit fec8e0a

1 file changed

Lines changed: 0 additions & 86 deletions

File tree

apps/sim/lib/api/contracts/schedules.ts

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -102,38 +102,6 @@ export const workspaceScheduleRowSchema = workflowScheduleRowSchema.extend({
102102

103103
export type WorkspaceScheduleRow = z.output<typeof workspaceScheduleRowSchema>
104104

105-
export const createScheduleBodySchema = z
106-
.object({
107-
workspaceId: z.string().min(1, 'Workspace ID is required'),
108-
title: z.string().min(1, 'Title is required'),
109-
prompt: z.string().min(1, 'Prompt is required'),
110-
/** Recurring cadence. Omit (with `time` set) for a one-time task. */
111-
cronExpression: z.string().min(1).optional(),
112-
/** One-time launch instant (ISO 8601). Omit (with `cronExpression` set) for a recurring task. */
113-
time: z.string().min(1).optional(),
114-
timezone: z.string().optional().default('UTC'),
115-
lifecycle: scheduleLifecycleSchema.optional().default('persistent'),
116-
/** Recurrence end after N runs (gcal "ends after N occurrences"). */
117-
maxRuns: z.number().int().positive().optional(),
118-
/** Recurrence end on a date (ISO 8601; gcal "ends on date"). */
119-
endsAt: z.string().optional(),
120-
startDate: z.string().optional(),
121-
contexts: z.array(scheduleContextSchema).optional(),
122-
secretScope: secretMountScopeSchema.optional(),
123-
mountedSecrets: mountedSecretNamesSchema.optional(),
124-
})
125-
.superRefine((body, ctx) => {
126-
if (!body.cronExpression && !body.time) {
127-
ctx.addIssue({
128-
code: z.ZodIssueCode.custom,
129-
path: ['time'],
130-
message: 'Provide a cron expression for a recurring task or a time for a one-time task',
131-
})
132-
}
133-
})
134-
135-
export type CreateScheduleBody = z.input<typeof createScheduleBodySchema>
136-
137105
export const reactivateScheduleBodySchema = z.object({
138106
action: z.literal('reactivate'),
139107
})
@@ -146,41 +114,9 @@ export const disableScheduleBodySchema = z.object({
146114

147115
export type DisableScheduleBody = z.input<typeof disableScheduleBodySchema>
148116

149-
export const updateScheduleBodySchema = z.object({
150-
action: z.literal('update'),
151-
title: z.string().min(1).optional(),
152-
prompt: z.string().min(1).optional(),
153-
cronExpression: z.string().nullable().optional(),
154-
/** One-time launch instant (ISO 8601). Switches a task to one-time when set alongside a null `cronExpression`. */
155-
time: z.string().min(1).optional(),
156-
timezone: z.string().optional(),
157-
lifecycle: scheduleLifecycleSchema.optional(),
158-
maxRuns: z.number().int().positive().nullable().optional(),
159-
endsAt: z.string().nullable().optional(),
160-
contexts: z.array(scheduleContextSchema).optional(),
161-
secretScope: secretMountScopeSchema.optional(),
162-
mountedSecrets: mountedSecretNamesSchema.optional(),
163-
})
164-
165-
export type UpdateScheduleBody = z.input<typeof updateScheduleBodySchema>
166-
167-
/**
168-
* Deletes a single occurrence of a recurring task (gcal "this event"): the
169-
* occurrence's instant is added to the schedule's exclusion list and the next
170-
* run advances past it. Deleting the whole series uses `DELETE /api/schedules/[id]`.
171-
*/
172-
export const excludeOccurrenceBodySchema = z.object({
173-
action: z.literal('exclude_occurrence'),
174-
occurrence: z.string().min(1, 'Occurrence timestamp is required'),
175-
})
176-
177-
export type ExcludeOccurrenceBody = z.input<typeof excludeOccurrenceBodySchema>
178-
179117
export const scheduleUpdateSchema = z.discriminatedUnion('action', [
180118
reactivateScheduleBodySchema,
181119
disableScheduleBodySchema,
182-
updateScheduleBodySchema,
183-
excludeOccurrenceBodySchema,
184120
])
185121

186122
export type ScheduleUpdate = z.input<typeof scheduleUpdateSchema>
@@ -248,28 +184,6 @@ export const getScheduleByIdContract = defineRouteContract({
248184
* the route synthesizes server-side; everything else is filled in on
249185
* subsequent reads.
250186
*/
251-
export const createScheduleResponseSchema = z.object({
252-
schedule: z.object({
253-
id: z.string(),
254-
status: scheduleStatusSchema,
255-
/** Null for one-time tasks, which carry no recurring cadence. */
256-
cronExpression: z.string().nullable(),
257-
nextRunAt: z.string(),
258-
}),
259-
})
260-
261-
export type CreateScheduleResponse = z.output<typeof createScheduleResponseSchema>
262-
263-
export const createScheduleContract = defineRouteContract({
264-
method: 'POST',
265-
path: '/api/schedules',
266-
body: createScheduleBodySchema,
267-
response: {
268-
mode: 'json',
269-
schema: createScheduleResponseSchema,
270-
},
271-
})
272-
273187
export const reactivateScheduleContract = defineRouteContract({
274188
method: 'PUT',
275189
path: '/api/schedules/[id]',

0 commit comments

Comments
 (0)