import path from "node:path";import { TRPCError } from "@trpc/server";import { eq } from "drizzle-orm";import type { z } from "zod";import { paths } from "../constants";import { db } from "../db";import type { createScheduleSchema, updateScheduleSchema,} from "../db/schema/schedule";import { type Schedule, schedules } from "../db/schema/schedule";import { encodeBase64 } from "../utils/docker/utils";import { execAsync, execAsyncRemote } from "../utils/process/execAsync"; export type ScheduleExtended = Awaited<ReturnType<typeof findScheduleById>>; export const createSchedule = async ( input: z.infer<typeof createScheduleSchema>,) => { const { scheduleId, ...rest } = input; const [newSchedule] = await db .insert(schedules) .values(rest as typeof schedules.$inferInsert) .returning(); if ( newSchedule && (newSchedule.scheduleType === "dokploy-server" || newSchedule.scheduleType === "server") ) { await handleScript(newSchedule); } return newSchedule;}; export const findScheduleById = async (scheduleId: string) => { const schedule = await db.query.schedules.findFirst({ where: eq(schedules.scheduleId, scheduleId), with: { application: { with: { environment: { with: { project: true, }, }, }, }, compose: { with: { environment: { with: { project: true, }, }, }, }, server: { with: { organization: true, }, }, }, }); if (!schedule) { throw new TRPCError({ code: "NOT_FOUND", message: "Schedule not found", }); } return schedule;}; export const findScheduleOrganizationId = async (scheduleId: string) => { const schedule = await findScheduleById(scheduleId); if (schedule?.application) { return schedule?.application?.environment?.project?.organizationId; } if (schedule?.compose) { return schedule?.compose?.environment?.project?.organizationId; } if (schedule?.server) { return schedule?.server?.organization?.id; } if (schedule?.organizationId) { return schedule.organizationId; } return null;}; export const deleteSchedule = async (scheduleId: string) => { const schedule = await findScheduleById(scheduleId); const serverId = schedule?.serverId || schedule?.application?.serverId || schedule?.compose?.serverId; const { SCHEDULES_PATH } = paths(!!serverId); const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || ""); const command = `rm -rf ${fullPath}`; if (serverId) { await execAsyncRemote(serverId, command); } else { await execAsync(command); } const scheduleResult = await db .delete(schedules) .where(eq(schedules.scheduleId, scheduleId)); if (!scheduleResult) { throw new TRPCError({ code: "NOT_FOUND", message: "Schedule not found", }); } return true;}; export const updateSchedule = async ( input: z.infer<typeof updateScheduleSchema>,) => { const { scheduleId, ...rest } = input; const [updatedSchedule] = await db .update(schedules) .set(rest as Partial<typeof schedules.$inferInsert>) .where(eq(schedules.scheduleId, scheduleId)) .returning(); if (!updatedSchedule) { throw new TRPCError({ code: "NOT_FOUND", message: "Schedule not found", }); } if ( updatedSchedule?.scheduleType === "dokploy-server" || updatedSchedule?.scheduleType === "server" ) { await handleScript(updatedSchedule); } return updatedSchedule;}; const handleScript = async (schedule: Schedule) => { const { SCHEDULES_PATH } = paths(!!schedule?.serverId); const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || ""); // Add PID and Schedule ID echo by default to all scripts const scriptWithPid = `echo "PID: $$ | Schedule ID: ${schedule.scheduleId}"${schedule?.script || ""}`; const encodedContent = encodeBase64(scriptWithPid); const script = ` mkdir -p ${fullPath} rm -f ${fullPath}/script.sh touch ${fullPath}/script.sh chmod +x ${fullPath}/script.sh echo "${encodedContent}" | base64 -d > ${fullPath}/script.sh `; if (schedule?.scheduleType === "dokploy-server") { await execAsync(script); } else if (schedule?.scheduleType === "server") { await execAsyncRemote(schedule?.serverId || "", script); }};
import path from "node:path";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import type { z } from "zod";
import { paths } from "../constants";
import { db } from "../db";
import type {
createScheduleSchema,
updateScheduleSchema,
} from "../db/schema/schedule";
import { type Schedule, schedules } from "../db/schema/schedule";
import { encodeBase64 } from "../utils/docker/utils";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
export type ScheduleExtended = Awaited<ReturnType<typeof findScheduleById>>;
export const createSchedule = async (
input: z.infer<typeof createScheduleSchema>,
) => {
const { scheduleId, ...rest } = input;
const [newSchedule] = await db
.insert(schedules)
.values(rest as typeof schedules.$inferInsert)
.returning();
if (
newSchedule &&
(newSchedule.scheduleType === "dokploy-server" ||
newSchedule.scheduleType === "server")
) {
await handleScript(newSchedule);
}
return newSchedule;
};
export const findScheduleById = async (scheduleId: string) => {
const schedule = await db.query.schedules.findFirst({
where: eq(schedules.scheduleId, scheduleId),
with: {
application: {
with: {
environment: {
with: {
project: true,
},
},
},
},
compose: {
with: {
environment: {
with: {
project: true,
},
},
},
},
server: {
with: {
organization: true,
},
},
},
});
if (!schedule) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Schedule not found",
});
}
return schedule;
};
export const findScheduleOrganizationId = async (scheduleId: string) => {
const schedule = await findScheduleById(scheduleId);
if (schedule?.application) {
return schedule?.application?.environment?.project?.organizationId;
}
if (schedule?.compose) {
return schedule?.compose?.environment?.project?.organizationId;
}
if (schedule?.server) {
return schedule?.server?.organization?.id;
}
if (schedule?.organizationId) {
return schedule.organizationId;
}
return null;
};
export const deleteSchedule = async (scheduleId: string) => {
const schedule = await findScheduleById(scheduleId);
const serverId =
schedule?.serverId ||
schedule?.application?.serverId ||
schedule?.compose?.serverId;
const { SCHEDULES_PATH } = paths(!!serverId);
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
const command = `rm -rf ${fullPath}`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
const scheduleResult = await db
.delete(schedules)
.where(eq(schedules.scheduleId, scheduleId));
if (!scheduleResult) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Schedule not found",
});
}
return true;
};
export const updateSchedule = async (
input: z.infer<typeof updateScheduleSchema>,
) => {
const { scheduleId, ...rest } = input;
const [updatedSchedule] = await db
.update(schedules)
.set(rest as Partial<typeof schedules.$inferInsert>)
.where(eq(schedules.scheduleId, scheduleId))
.returning();
if (!updatedSchedule) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Schedule not found",
});
}
if (
updatedSchedule?.scheduleType === "dokploy-server" ||
updatedSchedule?.scheduleType === "server"
) {
await handleScript(updatedSchedule);
}
return updatedSchedule;
};
const handleScript = async (schedule: Schedule) => {
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
// Add PID and Schedule ID echo by default to all scripts
const scriptWithPid = `echo "PID: $$ | Schedule ID: ${schedule.scheduleId}"
${schedule?.script || ""}`;
const encodedContent = encodeBase64(scriptWithPid);
const script = `
mkdir -p ${fullPath}
rm -f ${fullPath}/script.sh
touch ${fullPath}/script.sh
chmod +x ${fullPath}/script.sh
echo "${encodedContent}" | base64 -d > ${fullPath}/script.sh
`;
if (schedule?.scheduleType === "dokploy-server") {
await execAsync(script);
} else if (schedule?.scheduleType === "server") {
await execAsyncRemote(schedule?.serverId || "", script);
}
};