mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00: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:
parent
ff8d922f2b
commit
b34987530e
@ -75,6 +75,22 @@ import { useRouter } from "next/router";
|
|||||||
import { type ReactElement, useMemo, useState } from "react";
|
import { type ReactElement, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
export type Services = {
|
export type Services = {
|
||||||
appName: string;
|
appName: string;
|
||||||
@ -205,8 +221,13 @@ const Project = (
|
|||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
|
|
||||||
const { data, isLoading, refetch } = api.project.one.useQuery({ projectId });
|
const { data, isLoading, refetch } = api.project.one.useQuery({ projectId });
|
||||||
|
const { data: allProjects } = api.project.all.useQuery();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false);
|
||||||
|
const [selectedTargetProject, setSelectedTargetProject] =
|
||||||
|
useState<string>("");
|
||||||
|
|
||||||
const emptyServices =
|
const emptyServices =
|
||||||
data?.mariadb?.length === 0 &&
|
data?.mariadb?.length === 0 &&
|
||||||
data?.mongo?.length === 0 &&
|
data?.mongo?.length === 0 &&
|
||||||
@ -254,6 +275,31 @@ const Project = (
|
|||||||
const composeActions = {
|
const composeActions = {
|
||||||
start: api.compose.start.useMutation(),
|
start: api.compose.start.useMutation(),
|
||||||
stop: api.compose.stop.useMutation(),
|
stop: api.compose.stop.useMutation(),
|
||||||
|
move: api.compose.move.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const applicationActions = {
|
||||||
|
move: api.application.move.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const postgresActions = {
|
||||||
|
move: api.postgres.move.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mysqlActions = {
|
||||||
|
move: api.mysql.move.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mariadbActions = {
|
||||||
|
move: api.mariadb.move.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const redisActions = {
|
||||||
|
move: api.redis.move.useMutation(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mongoActions = {
|
||||||
|
move: api.mongo.move.useMutation(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBulkStart = async () => {
|
const handleBulkStart = async () => {
|
||||||
@ -296,6 +342,80 @@ const Project = (
|
|||||||
setIsBulkActionLoading(false);
|
setIsBulkActionLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBulkMove = async () => {
|
||||||
|
if (!selectedTargetProject) {
|
||||||
|
toast.error("Please select a target project");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let success = 0;
|
||||||
|
setIsBulkActionLoading(true);
|
||||||
|
for (const serviceId of selectedServices) {
|
||||||
|
try {
|
||||||
|
const service = filteredServices.find((s) => s.id === serviceId);
|
||||||
|
if (!service) continue;
|
||||||
|
|
||||||
|
switch (service.type) {
|
||||||
|
case "application":
|
||||||
|
await applicationActions.move.mutateAsync({
|
||||||
|
applicationId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "compose":
|
||||||
|
await composeActions.move.mutateAsync({
|
||||||
|
composeId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "postgres":
|
||||||
|
await postgresActions.move.mutateAsync({
|
||||||
|
postgresId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "mysql":
|
||||||
|
await mysqlActions.move.mutateAsync({
|
||||||
|
mysqlId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "mariadb":
|
||||||
|
await mariadbActions.move.mutateAsync({
|
||||||
|
mariadbId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "redis":
|
||||||
|
await redisActions.move.mutateAsync({
|
||||||
|
redisId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "mongo":
|
||||||
|
await mongoActions.move.mutateAsync({
|
||||||
|
mongoId: serviceId,
|
||||||
|
targetProjectId: selectedTargetProject,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
success++;
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(
|
||||||
|
`Error moving service ${serviceId}: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (success > 0) {
|
||||||
|
toast.success(`${success} services moved successfully`);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
setSelectedServices([]);
|
||||||
|
setIsDropdownOpen(false);
|
||||||
|
setIsMoveDialogOpen(false);
|
||||||
|
setIsBulkActionLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
const filteredServices = useMemo(() => {
|
const filteredServices = useMemo(() => {
|
||||||
if (!applications) return [];
|
if (!applications) return [];
|
||||||
return applications.filter(
|
return applications.filter(
|
||||||
@ -445,6 +565,65 @@ const Project = (
|
|||||||
Stop
|
Stop
|
||||||
</Button>
|
</Button>
|
||||||
</DialogAction>
|
</DialogAction>
|
||||||
|
<Dialog
|
||||||
|
open={isMoveDialogOpen}
|
||||||
|
onOpenChange={setIsMoveDialogOpen}
|
||||||
|
>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start"
|
||||||
|
>
|
||||||
|
<FolderInput className="mr-2 h-4 w-4" />
|
||||||
|
Move
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Move Services</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Select the target project to move{" "}
|
||||||
|
{selectedServices.length} services
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Select
|
||||||
|
value={selectedTargetProject}
|
||||||
|
onValueChange={setSelectedTargetProject}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select target project" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{allProjects
|
||||||
|
?.filter((p) => p.projectId !== projectId)
|
||||||
|
.map((project) => (
|
||||||
|
<SelectItem
|
||||||
|
key={project.projectId}
|
||||||
|
value={project.projectId}
|
||||||
|
>
|
||||||
|
{project.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setIsMoveDialogOpen(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleBulkMove}
|
||||||
|
isLoading={isBulkActionLoading}
|
||||||
|
>
|
||||||
|
Move Services
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -668,4 +668,49 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return stats;
|
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,
|
apiFindCompose,
|
||||||
apiRandomizeCompose,
|
apiRandomizeCompose,
|
||||||
apiUpdateCompose,
|
apiUpdateCompose,
|
||||||
compose,
|
compose as composeTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
||||||
import { templates } from "@/templates/templates";
|
import { templates } from "@/templates/templates";
|
||||||
@ -24,6 +24,7 @@ import { dump } from "js-yaml";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
@ -157,8 +158,8 @@ export const composeRouter = createTRPCRouter({
|
|||||||
4;
|
4;
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.delete(compose)
|
.delete(composeTable)
|
||||||
.where(eq(compose.composeId, input.composeId))
|
.where(eq(composeTable.composeId, input.composeId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
const cleanupOperations = [
|
const cleanupOperations = [
|
||||||
@ -501,4 +502,48 @@ export const composeRouter = createTRPCRouter({
|
|||||||
const uniqueTags = _.uniq(allTags);
|
const uniqueTags = _.uniq(allTags);
|
||||||
return uniqueTags;
|
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,
|
apiSaveEnvironmentVariablesMariaDB,
|
||||||
apiSaveExternalPortMariaDB,
|
apiSaveExternalPortMariaDB,
|
||||||
apiUpdateMariaDB,
|
apiUpdateMariaDB,
|
||||||
|
mariadb as mariadbTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { cancelJobs } from "@/server/utils/backup";
|
import { cancelJobs } from "@/server/utils/backup";
|
||||||
import {
|
import {
|
||||||
@ -30,6 +31,9 @@ import {
|
|||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
|
||||||
export const mariadbRouter = createTRPCRouter({
|
export const mariadbRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@ -322,4 +326,47 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
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,
|
apiSaveEnvironmentVariablesMongo,
|
||||||
apiSaveExternalPortMongo,
|
apiSaveExternalPortMongo,
|
||||||
apiUpdateMongo,
|
apiUpdateMongo,
|
||||||
|
mongo as mongoTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { cancelJobs } from "@/server/utils/backup";
|
import { cancelJobs } from "@/server/utils/backup";
|
||||||
import {
|
import {
|
||||||
@ -30,6 +31,9 @@ import {
|
|||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
|
||||||
export const mongoRouter = createTRPCRouter({
|
export const mongoRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@ -336,4 +340,47 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
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,
|
apiSaveEnvironmentVariablesMySql,
|
||||||
apiSaveExternalPortMySql,
|
apiSaveExternalPortMySql,
|
||||||
apiUpdateMySql,
|
apiUpdateMySql,
|
||||||
|
mysql as mysqlTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
@ -32,6 +33,9 @@ import {
|
|||||||
updateMySqlById,
|
updateMySqlById,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const mysqlRouter = createTRPCRouter({
|
export const mysqlRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@ -332,4 +336,47 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
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,
|
apiSaveEnvironmentVariablesPostgres,
|
||||||
apiSaveExternalPortPostgres,
|
apiSaveExternalPortPostgres,
|
||||||
apiUpdatePostgres,
|
apiUpdatePostgres,
|
||||||
|
postgres as postgresTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { cancelJobs } from "@/server/utils/backup";
|
import { cancelJobs } from "@/server/utils/backup";
|
||||||
import {
|
import {
|
||||||
@ -30,6 +31,9 @@ import {
|
|||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
|
||||||
export const postgresRouter = createTRPCRouter({
|
export const postgresRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@ -352,4 +356,49 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
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,
|
apiSaveEnvironmentVariablesRedis,
|
||||||
apiSaveExternalPortRedis,
|
apiSaveExternalPortRedis,
|
||||||
apiUpdateRedis,
|
apiUpdateRedis,
|
||||||
|
redis as redisTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
@ -30,6 +31,9 @@ import {
|
|||||||
updateRedisById,
|
updateRedisById,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { observable } from "@trpc/server/observable";
|
import { observable } from "@trpc/server/observable";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const redisRouter = createTRPCRouter({
|
export const redisRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@ -316,4 +320,47 @@ export const redisRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user