@@ -7,19 +7,25 @@ import {
77 updateScheduleSchema ,
88} from "@dokploy/server/db/schema/schedule" ;
99import { runCommand } from "@dokploy/server/index" ;
10- import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission" ;
10+ import {
11+ checkPermission ,
12+ checkServicePermissionAndAccess ,
13+ findMemberByUserId ,
14+ } from "@dokploy/server/services/permission" ;
1115import {
1216 createSchedule ,
1317 deleteSchedule ,
1418 findScheduleById ,
1519 updateSchedule ,
1620} from "@dokploy/server/services/schedule" ;
21+ import { findServerById } from "@dokploy/server/services/server" ;
1722import { TRPCError } from "@trpc/server" ;
1823import { asc , desc , eq } from "drizzle-orm" ;
1924import { z } from "zod" ;
2025import { audit } from "@/server/api/utils/audit" ;
2126import { removeJob , schedule } from "@/server/utils/backup" ;
2227import { createTRPCRouter , protectedProcedure } from "../trpc" ;
28+
2329export const scheduleRouter = createTRPCRouter ( {
2430 create : protectedProcedure
2531 . input ( createScheduleSchema )
@@ -29,6 +35,45 @@ export const scheduleRouter = createTRPCRouter({
2935 await checkServicePermissionAndAccess ( ctx , serviceId , {
3036 schedule : [ "create" ] ,
3137 } ) ;
38+ } else {
39+ if ( input . scheduleType === "dokploy-server" && IS_CLOUD ) {
40+ throw new TRPCError ( {
41+ code : "FORBIDDEN" ,
42+ message :
43+ "Host-level schedules are not available in the cloud version." ,
44+ } ) ;
45+ }
46+
47+ await checkPermission ( ctx , { schedule : [ "create" ] } ) ;
48+
49+ if (
50+ input . scheduleType === "server" ||
51+ input . scheduleType === "dokploy-server"
52+ ) {
53+ const member = await findMemberByUserId (
54+ ctx . user . id ,
55+ ctx . session . activeOrganizationId ,
56+ ) ;
57+ if ( member . role !== "owner" && member . role !== "admin" ) {
58+ throw new TRPCError ( {
59+ code : "FORBIDDEN" ,
60+ message :
61+ "Only owners and admins can manage server-level schedules." ,
62+ } ) ;
63+ }
64+ }
65+
66+ if ( input . scheduleType === "server" && input . serverId ) {
67+ const targetServer = await findServerById ( input . serverId ) ;
68+ if (
69+ targetServer . organizationId !== ctx . session . activeOrganizationId
70+ ) {
71+ throw new TRPCError ( {
72+ code : "UNAUTHORIZED" ,
73+ message : "You don't have access to this server." ,
74+ } ) ;
75+ }
76+ }
3277 }
3378 const newSchedule = await createSchedule ( input ) ;
3479
@@ -57,12 +102,77 @@ export const scheduleRouter = createTRPCRouter({
57102 . input ( updateScheduleSchema )
58103 . mutation ( async ( { input, ctx } ) => {
59104 const existingSchedule = await findScheduleById ( input . scheduleId ) ;
105+
106+ if (
107+ IS_CLOUD &&
108+ input . scheduleType &&
109+ input . scheduleType !== existingSchedule . scheduleType
110+ ) {
111+ throw new TRPCError ( {
112+ code : "FORBIDDEN" ,
113+ message : "Changing scheduleType is not allowed in the cloud version." ,
114+ } ) ;
115+ }
116+
60117 const serviceId =
61118 existingSchedule . applicationId || existingSchedule . composeId ;
62119 if ( serviceId ) {
63120 await checkServicePermissionAndAccess ( ctx , serviceId , {
64121 schedule : [ "update" ] ,
65122 } ) ;
123+ } else {
124+ if ( existingSchedule . scheduleType === "dokploy-server" && IS_CLOUD ) {
125+ throw new TRPCError ( {
126+ code : "FORBIDDEN" ,
127+ message :
128+ "Host-level schedules are not available in the cloud version." ,
129+ } ) ;
130+ }
131+
132+ await checkPermission ( ctx , { schedule : [ "update" ] } ) ;
133+
134+ if (
135+ existingSchedule . scheduleType === "server" ||
136+ existingSchedule . scheduleType === "dokploy-server"
137+ ) {
138+ const member = await findMemberByUserId (
139+ ctx . user . id ,
140+ ctx . session . activeOrganizationId ,
141+ ) ;
142+ if ( member . role !== "owner" && member . role !== "admin" ) {
143+ throw new TRPCError ( {
144+ code : "FORBIDDEN" ,
145+ message :
146+ "Only owners and admins can manage server-level schedules." ,
147+ } ) ;
148+ }
149+ }
150+
151+ if (
152+ existingSchedule . scheduleType === "server" &&
153+ existingSchedule . serverId
154+ ) {
155+ const targetServer = await findServerById ( existingSchedule . serverId ) ;
156+ if (
157+ targetServer . organizationId !== ctx . session . activeOrganizationId
158+ ) {
159+ throw new TRPCError ( {
160+ code : "UNAUTHORIZED" ,
161+ message : "You don't have access to this server." ,
162+ } ) ;
163+ }
164+ }
165+
166+ if (
167+ existingSchedule . scheduleType === "dokploy-server" &&
168+ existingSchedule . userId &&
169+ existingSchedule . userId !== ctx . user . id
170+ ) {
171+ throw new TRPCError ( {
172+ code : "UNAUTHORIZED" ,
173+ message : "You can only manage your own host-level schedules." ,
174+ } ) ;
175+ }
66176 }
67177 const updatedSchedule = await updateSchedule ( input ) ;
68178
@@ -107,6 +217,56 @@ export const scheduleRouter = createTRPCRouter({
107217 await checkServicePermissionAndAccess ( ctx , serviceId , {
108218 schedule : [ "delete" ] ,
109219 } ) ;
220+ } else {
221+ if ( scheduleItem . scheduleType === "dokploy-server" && IS_CLOUD ) {
222+ throw new TRPCError ( {
223+ code : "FORBIDDEN" ,
224+ message :
225+ "Host-level schedules are not available in the cloud version." ,
226+ } ) ;
227+ }
228+
229+ await checkPermission ( ctx , { schedule : [ "delete" ] } ) ;
230+
231+ if (
232+ scheduleItem . scheduleType === "server" ||
233+ scheduleItem . scheduleType === "dokploy-server"
234+ ) {
235+ const member = await findMemberByUserId (
236+ ctx . user . id ,
237+ ctx . session . activeOrganizationId ,
238+ ) ;
239+ if ( member . role !== "owner" && member . role !== "admin" ) {
240+ throw new TRPCError ( {
241+ code : "FORBIDDEN" ,
242+ message :
243+ "Only owners and admins can manage server-level schedules." ,
244+ } ) ;
245+ }
246+ }
247+
248+ if ( scheduleItem . scheduleType === "server" && scheduleItem . serverId ) {
249+ const targetServer = await findServerById ( scheduleItem . serverId ) ;
250+ if (
251+ targetServer . organizationId !== ctx . session . activeOrganizationId
252+ ) {
253+ throw new TRPCError ( {
254+ code : "UNAUTHORIZED" ,
255+ message : "You don't have access to this server." ,
256+ } ) ;
257+ }
258+ }
259+
260+ if (
261+ scheduleItem . scheduleType === "dokploy-server" &&
262+ scheduleItem . userId &&
263+ scheduleItem . userId !== ctx . user . id
264+ ) {
265+ throw new TRPCError ( {
266+ code : "UNAUTHORIZED" ,
267+ message : "You can only manage your own host-level schedules." ,
268+ } ) ;
269+ }
110270 }
111271 await deleteSchedule ( input . scheduleId ) ;
112272
@@ -148,6 +308,30 @@ export const scheduleRouter = createTRPCRouter({
148308 await checkServicePermissionAndAccess ( ctx , input . id , {
149309 schedule : [ "read" ] ,
150310 } ) ;
311+ } else {
312+ await checkPermission ( ctx , { schedule : [ "read" ] } ) ;
313+
314+ if ( input . scheduleType === "server" ) {
315+ const targetServer = await findServerById ( input . id ) ;
316+ if (
317+ targetServer . organizationId !== ctx . session . activeOrganizationId
318+ ) {
319+ throw new TRPCError ( {
320+ code : "UNAUTHORIZED" ,
321+ message : "You don't have access to this server." ,
322+ } ) ;
323+ }
324+ }
325+
326+ if (
327+ input . scheduleType === "dokploy-server" &&
328+ input . id !== ctx . user . id
329+ ) {
330+ throw new TRPCError ( {
331+ code : "UNAUTHORIZED" ,
332+ message : "You can only list your own host-level schedules." ,
333+ } ) ;
334+ }
151335 }
152336 const where = {
153337 application : eq ( schedules . applicationId , input . id ) ,
@@ -178,6 +362,31 @@ export const scheduleRouter = createTRPCRouter({
178362 await checkServicePermissionAndAccess ( ctx , serviceId , {
179363 schedule : [ "read" ] ,
180364 } ) ;
365+ } else {
366+ await checkPermission ( ctx , { schedule : [ "read" ] } ) ;
367+
368+ if ( schedule . scheduleType === "server" && schedule . serverId ) {
369+ const targetServer = await findServerById ( schedule . serverId ) ;
370+ if (
371+ targetServer . organizationId !== ctx . session . activeOrganizationId
372+ ) {
373+ throw new TRPCError ( {
374+ code : "UNAUTHORIZED" ,
375+ message : "You don't have access to this schedule." ,
376+ } ) ;
377+ }
378+ }
379+
380+ if (
381+ schedule . scheduleType === "dokploy-server" &&
382+ schedule . userId &&
383+ schedule . userId !== ctx . user . id
384+ ) {
385+ throw new TRPCError ( {
386+ code : "UNAUTHORIZED" ,
387+ message : "You don't have access to this schedule." ,
388+ } ) ;
389+ }
181390 }
182391 return schedule ;
183392 } ) ,
@@ -191,6 +400,56 @@ export const scheduleRouter = createTRPCRouter({
191400 await checkServicePermissionAndAccess ( ctx , serviceId , {
192401 schedule : [ "create" ] ,
193402 } ) ;
403+ } else {
404+ if ( scheduleItem . scheduleType === "dokploy-server" && IS_CLOUD ) {
405+ throw new TRPCError ( {
406+ code : "FORBIDDEN" ,
407+ message :
408+ "Host-level schedules are not available in the cloud version." ,
409+ } ) ;
410+ }
411+
412+ await checkPermission ( ctx , { schedule : [ "create" ] } ) ;
413+
414+ if (
415+ scheduleItem . scheduleType === "server" ||
416+ scheduleItem . scheduleType === "dokploy-server"
417+ ) {
418+ const member = await findMemberByUserId (
419+ ctx . user . id ,
420+ ctx . session . activeOrganizationId ,
421+ ) ;
422+ if ( member . role !== "owner" && member . role !== "admin" ) {
423+ throw new TRPCError ( {
424+ code : "FORBIDDEN" ,
425+ message :
426+ "Only owners and admins can manage server-level schedules." ,
427+ } ) ;
428+ }
429+ }
430+
431+ if ( scheduleItem . scheduleType === "server" && scheduleItem . serverId ) {
432+ const targetServer = await findServerById ( scheduleItem . serverId ) ;
433+ if (
434+ targetServer . organizationId !== ctx . session . activeOrganizationId
435+ ) {
436+ throw new TRPCError ( {
437+ code : "UNAUTHORIZED" ,
438+ message : "You don't have access to this server." ,
439+ } ) ;
440+ }
441+ }
442+
443+ if (
444+ scheduleItem . scheduleType === "dokploy-server" &&
445+ scheduleItem . userId &&
446+ scheduleItem . userId !== ctx . user . id
447+ ) {
448+ throw new TRPCError ( {
449+ code : "UNAUTHORIZED" ,
450+ message : "You can only manage your own host-level schedules." ,
451+ } ) ;
452+ }
194453 }
195454 try {
196455 await runCommand ( input . scheduleId ) ;
0 commit comments