feat(drag-n-drop): add support for drag n drop projects via zip #131

This commit is contained in:
Mauricio Siu
2024-07-21 00:44:08 -06:00
parent b4511ca7a2
commit d52692c6a3
31 changed files with 6430 additions and 18 deletions

View File

@@ -1,4 +1,8 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
createTRPCRouter,
protectedProcedure,
uploadProcedure,
} from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateApplication,
@@ -52,6 +56,9 @@ import {
import { removeDeployments } from "../services/deployment";
import { addNewService, checkServiceAccess } from "../services/user";
import { unzipDrop } from "@/server/utils/builders/drop";
import { uploadFileSchema } from "@/utils/schema";
export const applicationRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateApplication)
@@ -324,6 +331,45 @@ export const applicationRouter = createTRPCRouter({
return traefikConfig;
}),
dropDeployment: protectedProcedure
.meta({
openapi: {
path: "/drop-deployment",
method: "POST",
override: true,
enabled: false,
},
})
.use(uploadProcedure)
.input(uploadFileSchema)
.mutation(async ({ input }) => {
const zipFile = input.zip;
updateApplication(input.applicationId as string, {
sourceType: "drop",
dropBuildPath: input.dropBuildPath,
});
const app = await findApplicationById(input.applicationId as string);
await unzipDrop(zipFile, app.appName);
const jobData: DeploymentJob = {
applicationId: app.applicationId,
titleLog: "Manual deployment",
descriptionLog: "",
type: "deploy",
applicationType: "application",
};
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
return true;
}),
updateTraefikConfig: protectedProcedure
.input(z.object({ applicationId: z.string(), traefikConfig: z.string() }))
.mutation(async ({ input }) => {

View File

@@ -157,6 +157,8 @@ export const deployApplication = async ({
} else if (application.sourceType === "git") {
await cloneGitRepository(application, deployment.logPath);
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "drop") {
await buildApplication(application, deployment.logPath);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");
@@ -216,6 +218,8 @@ export const rebuildApplication = async ({
await buildDocker(application, deployment.logPath);
} else if (application.sourceType === "git") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "drop") {
await buildApplication(application, deployment.logPath);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");

View File

@@ -15,10 +15,6 @@ import { type Application, findApplicationById } from "./application";
import { type Compose, findComposeById } from "./compose";
export type Deployment = typeof deployments.$inferSelect;
type CreateDeploymentInput = Omit<
Deployment,
"deploymentId" | "createdAt" | "status" | "logPath"
>;
export const findDeploymentById = async (applicationId: string) => {
const application = await db.query.deployments.findFirst({

View File

@@ -12,6 +12,11 @@ import { db } from "@/server/db";
import type { OpenApiMeta } from "@dokploy/trpc-openapi";
import { TRPCError, initTRPC } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
import {
experimental_createMemoryUploadHandler,
experimental_isMultipartFormDataRequest,
experimental_parseMultipartFormData,
} from "@trpc/server/adapters/node-http/content-type/form-data";
import type { Session, User } from "lucia";
import superjson from "superjson";
import { ZodError } from "zod";
@@ -158,6 +163,24 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
});
});
export const uploadProcedure = async (opts: any) => {
if (!experimental_isMultipartFormDataRequest(opts.ctx.req)) {
return opts.next();
}
const formData = await experimental_parseMultipartFormData(
opts.ctx.req,
experimental_createMemoryUploadHandler({
// 2GB
maxPartSize: 1024 * 1024 * 1024 * 2,
}),
);
return opts.next({
rawInput: formData,
});
};
export const cliProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
throw new TRPCError({ code: "UNAUTHORIZED" });