mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-05 14:05:30 +02:00
feat(services): add bulk service move functionality across projects
- Implement service move feature for applications, compose, databases, and other services - Add move dialog with project selection for bulk service transfer - Create move mutation endpoints for each service type - Enhance project management with cross-project service relocation - Improve user experience with error handling and success notifications
This commit is contained in:
@@ -668,4 +668,49 @@ export const applicationRouter = createTRPCRouter({
|
||||
|
||||
return stats;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
applicationId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const application = await findApplicationById(input.applicationId);
|
||||
if (
|
||||
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this application",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the application's projectId
|
||||
const updatedApplication = await db
|
||||
.update(applications)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(applications.applicationId, input.applicationId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedApplication) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move application",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedApplication;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
apiFindCompose,
|
||||
apiRandomizeCompose,
|
||||
apiUpdateCompose,
|
||||
compose,
|
||||
compose as composeTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
||||
import { templates } from "@/templates/templates";
|
||||
@@ -24,6 +24,7 @@ import { dump } from "js-yaml";
|
||||
import _ from "lodash";
|
||||
import { nanoid } from "nanoid";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
@@ -157,8 +158,8 @@ export const composeRouter = createTRPCRouter({
|
||||
4;
|
||||
|
||||
const result = await db
|
||||
.delete(compose)
|
||||
.where(eq(compose.composeId, input.composeId))
|
||||
.delete(composeTable)
|
||||
.where(eq(composeTable.composeId, input.composeId))
|
||||
.returning();
|
||||
|
||||
const cleanupOperations = [
|
||||
@@ -501,4 +502,48 @@ export const composeRouter = createTRPCRouter({
|
||||
const uniqueTags = _.uniq(allTags);
|
||||
return uniqueTags;
|
||||
}),
|
||||
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
composeId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this compose",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the compose's projectId
|
||||
const updatedCompose = await db
|
||||
.update(composeTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(composeTable.composeId, input.composeId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedCompose) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move compose",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedCompose;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
apiSaveEnvironmentVariablesMariaDB,
|
||||
apiSaveExternalPortMariaDB,
|
||||
apiUpdateMariaDB,
|
||||
mariadb as mariadbTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
@@ -30,6 +31,9 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
|
||||
export const mariadbRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -322,4 +326,47 @@ export const mariadbRouter = createTRPCRouter({
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
mariadbId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mariadb = await findMariadbById(input.mariadbId);
|
||||
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mariadb",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mariadb's projectId
|
||||
const updatedMariadb = await db
|
||||
.update(mariadbTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(mariadbTable.mariadbId, input.mariadbId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedMariadb) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move mariadb",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedMariadb;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
apiSaveEnvironmentVariablesMongo,
|
||||
apiSaveExternalPortMongo,
|
||||
apiUpdateMongo,
|
||||
mongo as mongoTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
@@ -30,6 +31,9 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
|
||||
export const mongoRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -336,4 +340,47 @@ export const mongoRouter = createTRPCRouter({
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
mongoId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mongo = await findMongoById(input.mongoId);
|
||||
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mongo",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mongo's projectId
|
||||
const updatedMongo = await db
|
||||
.update(mongoTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(mongoTable.mongoId, input.mongoId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedMongo) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move mongo",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedMongo;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
apiSaveEnvironmentVariablesMySql,
|
||||
apiSaveExternalPortMySql,
|
||||
apiUpdateMySql,
|
||||
mysql as mysqlTable,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -32,6 +33,9 @@ import {
|
||||
updateMySqlById,
|
||||
} from "@dokploy/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { z } from "zod";
|
||||
|
||||
export const mysqlRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -332,4 +336,47 @@ export const mysqlRouter = createTRPCRouter({
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
mysqlId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mysql = await findMySqlById(input.mysqlId);
|
||||
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mysql",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mysql's projectId
|
||||
const updatedMysql = await db
|
||||
.update(mysqlTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(mysqlTable.mysqlId, input.mysqlId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedMysql) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move mysql",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedMysql;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
apiSaveEnvironmentVariablesPostgres,
|
||||
apiSaveExternalPortPostgres,
|
||||
apiUpdatePostgres,
|
||||
postgres as postgresTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
@@ -30,6 +31,9 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
|
||||
export const postgresRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -352,4 +356,49 @@ export const postgresRouter = createTRPCRouter({
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
postgresId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const postgres = await findPostgresById(input.postgresId);
|
||||
if (
|
||||
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this postgres",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the postgres's projectId
|
||||
const updatedPostgres = await db
|
||||
.update(postgresTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(postgresTable.postgresId, input.postgresId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedPostgres) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move postgres",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedPostgres;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
apiSaveEnvironmentVariablesRedis,
|
||||
apiSaveExternalPortRedis,
|
||||
apiUpdateRedis,
|
||||
redis as redisTable,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -30,6 +31,9 @@ import {
|
||||
updateRedisById,
|
||||
} from "@dokploy/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { z } from "zod";
|
||||
|
||||
export const redisRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -316,4 +320,47 @@ export const redisRouter = createTRPCRouter({
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
redisId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const redis = await findRedisById(input.redisId);
|
||||
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this redis",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the redis's projectId
|
||||
const updatedRedis = await db
|
||||
.update(redisTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(redisTable.redisId, input.redisId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedRedis) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move redis",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedRedis;
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user