Skip to content

Commit f43aa6e

Browse files
author
Valentin FROMENT
committed
fix: share global schedules across org admins and owners
Global (dokploy-server) schedules were scoped to the creator's user ID, making them invisible to other admins and owners in the same organization. This was inconsistent with server and application schedules which are org-scoped. - schedule.list: admins/owners now see all dokploy-server schedules from every member in the org, not just their own - schedule.update/delete/one/runManually: admins/owners can now manage dokploy-server schedules created by other org members
1 parent 0e5fc58 commit f43aa6e

1 file changed

Lines changed: 110 additions & 39 deletions

File tree

apps/dokploy/server/api/routers/schedule.ts

Lines changed: 110 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IS_CLOUD, removeScheduleJob, scheduleJob } from "@dokploy/server";
22
import { db } from "@dokploy/server/db";
3+
import { member } from "@dokploy/server/db/schema";
34
import { deployments } from "@dokploy/server/db/schema/deployment";
45
import {
56
createScheduleSchema,
@@ -20,7 +21,7 @@ import {
2021
} from "@dokploy/server/services/schedule";
2122
import { findServerById } from "@dokploy/server/services/server";
2223
import { TRPCError } from "@trpc/server";
23-
import { asc, desc, eq } from "drizzle-orm";
24+
import { and, asc, desc, eq, inArray } from "drizzle-orm";
2425
import { z } from "zod";
2526
import { audit } from "@/server/api/utils/audit";
2627
import { removeJob, schedule } from "@/server/utils/backup";
@@ -163,16 +164,30 @@ export const scheduleRouter = createTRPCRouter({
163164
}
164165
}
165166

167+
if (
168+
existingSchedule.scheduleType === "dokploy-server" &&
169+
existingSchedule.userId &&
170+
existingSchedule.userId !== ctx.user.id
171+
) {
172+
const currentMember = await findMemberByUserId(
173+
ctx.user.id,
174+
ctx.session.activeOrganizationId,
175+
);
166176
if (
167-
existingSchedule.scheduleType === "dokploy-server" &&
168-
existingSchedule.userId &&
169-
existingSchedule.userId !== ctx.user.id
177+
currentMember.role !== "owner" &&
178+
currentMember.role !== "admin"
170179
) {
171180
throw new TRPCError({
172181
code: "UNAUTHORIZED",
173-
message: "You can only manage your own host-level schedules.",
182+
message:
183+
"You can only manage your own host-level schedules.",
174184
});
175185
}
186+
await findMemberByUserId(
187+
existingSchedule.userId,
188+
ctx.session.activeOrganizationId,
189+
);
190+
}
176191
}
177192
const updatedSchedule = await updateSchedule(input);
178193

@@ -262,10 +277,24 @@ export const scheduleRouter = createTRPCRouter({
262277
scheduleItem.userId &&
263278
scheduleItem.userId !== ctx.user.id
264279
) {
265-
throw new TRPCError({
266-
code: "UNAUTHORIZED",
267-
message: "You can only manage your own host-level schedules.",
268-
});
280+
const currentMember = await findMemberByUserId(
281+
ctx.user.id,
282+
ctx.session.activeOrganizationId,
283+
);
284+
if (
285+
currentMember.role !== "owner" &&
286+
currentMember.role !== "admin"
287+
) {
288+
throw new TRPCError({
289+
code: "UNAUTHORIZED",
290+
message:
291+
"You can only manage your own host-level schedules.",
292+
});
293+
}
294+
await findMemberByUserId(
295+
scheduleItem.userId,
296+
ctx.session.activeOrganizationId,
297+
);
269298
}
270299
}
271300
await deleteSchedule(input.scheduleId);
@@ -322,36 +351,51 @@ export const scheduleRouter = createTRPCRouter({
322351
});
323352
}
324353
}
354+
}
325355

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-
}
356+
let listWhere;
357+
if (input.scheduleType === "dokploy-server") {
358+
const currentMember = await findMemberByUserId(
359+
ctx.user.id,
360+
ctx.session.activeOrganizationId,
361+
);
362+
if (
363+
currentMember.role === "owner" ||
364+
currentMember.role === "admin"
365+
) {
366+
const orgMembers = await db.query.member.findMany({
367+
where: eq(member.organizationId, ctx.session.activeOrganizationId),
368+
columns: { userId: true },
369+
});
370+
const userIds = orgMembers.map((m) => m.userId);
371+
listWhere = and(
372+
eq(schedules.scheduleType, "dokploy-server"),
373+
inArray(schedules.userId, userIds),
374+
);
375+
} else {
376+
listWhere = eq(schedules.userId, ctx.user.id);
335377
}
378+
} else {
336379
const where = {
337380
application: eq(schedules.applicationId, input.id),
338381
compose: eq(schedules.composeId, input.id),
339382
server: eq(schedules.serverId, input.id),
340-
"dokploy-server": eq(schedules.userId, input.id),
341383
};
342-
return db.query.schedules.findMany({
343-
where: where[input.scheduleType],
344-
orderBy: [asc(schedules.createdAt)],
345-
with: {
346-
application: true,
347-
server: true,
348-
compose: true,
349-
deployments: {
350-
orderBy: [desc(deployments.createdAt)],
351-
},
384+
listWhere = where[input.scheduleType];
385+
}
386+
return db.query.schedules.findMany({
387+
where: listWhere,
388+
orderBy: [asc(schedules.createdAt)],
389+
with: {
390+
application: true,
391+
server: true,
392+
compose: true,
393+
deployments: {
394+
orderBy: [desc(deployments.createdAt)],
352395
},
353-
});
354-
}),
396+
},
397+
});
398+
}),
355399

356400
one: protectedProcedure
357401
.input(z.object({ scheduleId: z.string() }))
@@ -382,10 +426,23 @@ export const scheduleRouter = createTRPCRouter({
382426
schedule.userId &&
383427
schedule.userId !== ctx.user.id
384428
) {
385-
throw new TRPCError({
386-
code: "UNAUTHORIZED",
387-
message: "You don't have access to this schedule.",
388-
});
429+
const currentMember = await findMemberByUserId(
430+
ctx.user.id,
431+
ctx.session.activeOrganizationId,
432+
);
433+
if (
434+
currentMember.role !== "owner" &&
435+
currentMember.role !== "admin"
436+
) {
437+
throw new TRPCError({
438+
code: "UNAUTHORIZED",
439+
message: "You don't have access to this schedule.",
440+
});
441+
}
442+
await findMemberByUserId(
443+
schedule.userId,
444+
ctx.session.activeOrganizationId,
445+
);
389446
}
390447
}
391448
return schedule;
@@ -445,10 +502,24 @@ export const scheduleRouter = createTRPCRouter({
445502
scheduleItem.userId &&
446503
scheduleItem.userId !== ctx.user.id
447504
) {
448-
throw new TRPCError({
449-
code: "UNAUTHORIZED",
450-
message: "You can only manage your own host-level schedules.",
451-
});
505+
const currentMember = await findMemberByUserId(
506+
ctx.user.id,
507+
ctx.session.activeOrganizationId,
508+
);
509+
if (
510+
currentMember.role !== "owner" &&
511+
currentMember.role !== "admin"
512+
) {
513+
throw new TRPCError({
514+
code: "UNAUTHORIZED",
515+
message:
516+
"You can only manage your own host-level schedules.",
517+
});
518+
}
519+
await findMemberByUserId(
520+
scheduleItem.userId,
521+
ctx.session.activeOrganizationId,
522+
);
452523
}
453524
}
454525
try {

0 commit comments

Comments
 (0)