mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-20 22:55:22 +02:00
Added the 'name' column to the project API response structure to enhance the data returned for project queries. This change improves the clarity and usability of the API by ensuring that project names are included in the response, facilitating better data handling for clients.
986 lines
23 KiB
TypeScript
986 lines
23 KiB
TypeScript
import {
|
|
addNewEnvironment,
|
|
addNewProject,
|
|
checkProjectAccess,
|
|
createApplication,
|
|
createBackup,
|
|
createCompose,
|
|
createDomain,
|
|
createMariadb,
|
|
createMongo,
|
|
createMount,
|
|
createMysql,
|
|
createPort,
|
|
createPostgres,
|
|
createPreviewDeployment,
|
|
createProject,
|
|
createRedirect,
|
|
createRedis,
|
|
createSecurity,
|
|
deleteProject,
|
|
findApplicationById,
|
|
findComposeById,
|
|
findEnvironmentById,
|
|
findMariadbById,
|
|
findMemberById,
|
|
findMongoById,
|
|
findMySqlById,
|
|
findPostgresById,
|
|
findProjectById,
|
|
findRedisById,
|
|
findUserById,
|
|
IS_CLOUD,
|
|
updateProjectById,
|
|
} from "@dokploy/server";
|
|
import { db } from "@dokploy/server/db";
|
|
import { TRPCError } from "@trpc/server";
|
|
import { and, desc, eq, ilike, or, sql } from "drizzle-orm";
|
|
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
|
import { z } from "zod";
|
|
import {
|
|
adminProcedure,
|
|
createTRPCRouter,
|
|
protectedProcedure,
|
|
} from "@/server/api/trpc";
|
|
import {
|
|
apiCreateProject,
|
|
apiFindOneProject,
|
|
apiRemoveProject,
|
|
apiUpdateProject,
|
|
applications,
|
|
compose,
|
|
environments,
|
|
mariadb,
|
|
mongo,
|
|
mysql,
|
|
postgres,
|
|
projects,
|
|
redis,
|
|
} from "@/server/db/schema";
|
|
|
|
export const projectRouter = createTRPCRouter({
|
|
create: protectedProcedure
|
|
.input(apiCreateProject)
|
|
.mutation(async ({ ctx, input }) => {
|
|
try {
|
|
if (ctx.user.role === "member") {
|
|
await checkProjectAccess(
|
|
ctx.user.id,
|
|
"create",
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
}
|
|
|
|
const admin = await findUserById(ctx.user.ownerId);
|
|
|
|
if (admin.serversQuantity === 0 && IS_CLOUD) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "No servers available, Please subscribe to a plan",
|
|
});
|
|
}
|
|
|
|
const project = await createProject(
|
|
input,
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
if (ctx.user.role === "member") {
|
|
await addNewProject(
|
|
ctx.user.id,
|
|
project.project.projectId,
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
|
|
await addNewEnvironment(
|
|
ctx.user.id,
|
|
project?.environment?.environmentId || "",
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
}
|
|
|
|
return project;
|
|
} catch (error) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: `Error creating the project: ${error instanceof Error ? error.message : error}`,
|
|
cause: error,
|
|
});
|
|
}
|
|
}),
|
|
|
|
one: protectedProcedure
|
|
.input(apiFindOneProject)
|
|
.query(async ({ input, ctx }) => {
|
|
if (ctx.user.role === "member") {
|
|
const { accessedServices } = await findMemberById(
|
|
ctx.user.id,
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
|
|
await checkProjectAccess(
|
|
ctx.user.id,
|
|
"access",
|
|
ctx.session.activeOrganizationId,
|
|
input.projectId,
|
|
);
|
|
|
|
const project = await db.query.projects.findFirst({
|
|
where: and(
|
|
eq(projects.projectId, input.projectId),
|
|
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
|
),
|
|
with: {
|
|
environments: {
|
|
with: {
|
|
applications: {
|
|
where: buildServiceFilter(
|
|
applications.applicationId,
|
|
accessedServices,
|
|
),
|
|
},
|
|
compose: {
|
|
where: buildServiceFilter(
|
|
compose.composeId,
|
|
accessedServices,
|
|
),
|
|
},
|
|
mariadb: {
|
|
where: buildServiceFilter(
|
|
mariadb.mariadbId,
|
|
accessedServices,
|
|
),
|
|
},
|
|
mongo: {
|
|
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
|
},
|
|
mysql: {
|
|
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
|
},
|
|
postgres: {
|
|
where: buildServiceFilter(
|
|
postgres.postgresId,
|
|
accessedServices,
|
|
),
|
|
},
|
|
redis: {
|
|
where: buildServiceFilter(redis.redisId, accessedServices),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Project not found",
|
|
});
|
|
}
|
|
return project;
|
|
}
|
|
const project = await findProjectById(input.projectId);
|
|
|
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "You are not authorized to access this project",
|
|
});
|
|
}
|
|
return project;
|
|
}),
|
|
all: protectedProcedure.query(async ({ ctx }) => {
|
|
if (ctx.user.role === "member") {
|
|
const { accessedProjects, accessedEnvironments, accessedServices } =
|
|
await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
|
|
|
|
if (accessedProjects.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
// Build environment filter
|
|
const environmentFilter =
|
|
accessedEnvironments.length === 0
|
|
? sql`false`
|
|
: sql`${environments.environmentId} IN (${sql.join(
|
|
accessedEnvironments.map((envId) => sql`${envId}`),
|
|
sql`, `,
|
|
)})`;
|
|
|
|
return await db.query.projects.findMany({
|
|
where: and(
|
|
sql`${projects.projectId} IN (${sql.join(
|
|
accessedProjects.map((projectId) => sql`${projectId}`),
|
|
sql`, `,
|
|
)})`,
|
|
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
|
),
|
|
with: {
|
|
environments: {
|
|
where: environmentFilter,
|
|
with: {
|
|
applications: {
|
|
where: buildServiceFilter(
|
|
applications.applicationId,
|
|
accessedServices,
|
|
),
|
|
columns: {
|
|
applicationId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
mariadb: {
|
|
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
|
columns: {
|
|
mariadbId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
mongo: {
|
|
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
|
columns: {
|
|
mongoId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
mysql: {
|
|
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
|
columns: {
|
|
mysqlId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
postgres: {
|
|
where: buildServiceFilter(
|
|
postgres.postgresId,
|
|
accessedServices,
|
|
),
|
|
columns: {
|
|
postgresId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
redis: {
|
|
where: buildServiceFilter(redis.redisId, accessedServices),
|
|
columns: {
|
|
redisId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
compose: {
|
|
where: buildServiceFilter(compose.composeId, accessedServices),
|
|
columns: {
|
|
composeId: true,
|
|
name: true,
|
|
composeStatus: true,
|
|
},
|
|
},
|
|
},
|
|
columns: {
|
|
environmentId: true,
|
|
isDefault: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: desc(projects.createdAt),
|
|
});
|
|
}
|
|
|
|
return await db.query.projects.findMany({
|
|
with: {
|
|
environments: {
|
|
with: {
|
|
applications: {
|
|
columns: {
|
|
applicationId: true,
|
|
name: true,
|
|
applicationStatus: true,
|
|
},
|
|
},
|
|
mariadb: {
|
|
columns: {
|
|
mariadbId: true,
|
|
},
|
|
},
|
|
mongo: {
|
|
columns: {
|
|
mongoId: true,
|
|
},
|
|
},
|
|
mysql: {
|
|
columns: {
|
|
mysqlId: true,
|
|
},
|
|
},
|
|
postgres: {
|
|
columns: {
|
|
postgresId: true,
|
|
},
|
|
},
|
|
redis: {
|
|
columns: {
|
|
redisId: true,
|
|
},
|
|
},
|
|
compose: {
|
|
columns: {
|
|
composeId: true,
|
|
name: true,
|
|
composeStatus: true,
|
|
},
|
|
},
|
|
},
|
|
columns: {
|
|
name: true,
|
|
environmentId: true,
|
|
isDefault: true,
|
|
},
|
|
},
|
|
},
|
|
where: eq(projects.organizationId, ctx.session.activeOrganizationId),
|
|
orderBy: desc(projects.createdAt),
|
|
});
|
|
}),
|
|
|
|
/** All projects with full environments and services for the admin permissions UI. Admin only. */
|
|
allForPermissions: adminProcedure.query(async ({ ctx }) => {
|
|
return await db.query.projects.findMany({
|
|
where: eq(projects.organizationId, ctx.session.activeOrganizationId),
|
|
orderBy: desc(projects.createdAt),
|
|
columns: {
|
|
projectId: true,
|
|
name: true,
|
|
},
|
|
with: {
|
|
environments: {
|
|
columns: {
|
|
environmentId: true,
|
|
name: true,
|
|
isDefault: true,
|
|
},
|
|
with: {
|
|
applications: {
|
|
columns: {
|
|
applicationId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
applicationStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
mariadb: {
|
|
columns: {
|
|
mariadbId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
applicationStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
postgres: {
|
|
columns: {
|
|
postgresId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
applicationStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
mysql: {
|
|
columns: {
|
|
mysqlId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
applicationStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
mongo: {
|
|
columns: {
|
|
mongoId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
applicationStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
redis: {
|
|
columns: {
|
|
redisId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
applicationStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
compose: {
|
|
columns: {
|
|
composeId: true,
|
|
appName: true,
|
|
name: true,
|
|
createdAt: true,
|
|
composeStatus: true,
|
|
description: true,
|
|
serverId: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}),
|
|
|
|
search: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
q: z.string().optional(),
|
|
name: z.string().optional(),
|
|
description: z.string().optional(),
|
|
limit: z.number().min(1).max(100).default(20),
|
|
offset: z.number().min(0).default(0),
|
|
}),
|
|
)
|
|
.query(async ({ ctx, input }) => {
|
|
const baseConditions = [
|
|
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
|
];
|
|
|
|
if (input.q?.trim()) {
|
|
const term = `%${input.q.trim()}%`;
|
|
baseConditions.push(
|
|
or(
|
|
ilike(projects.name, term),
|
|
ilike(projects.description ?? "", term),
|
|
)!,
|
|
);
|
|
}
|
|
|
|
if (input.name?.trim()) {
|
|
baseConditions.push(ilike(projects.name, `%${input.name.trim()}%`));
|
|
}
|
|
if (input.description?.trim()) {
|
|
baseConditions.push(
|
|
ilike(projects.description ?? "", `%${input.description.trim()}%`),
|
|
);
|
|
}
|
|
|
|
if (ctx.user.role === "member") {
|
|
const { accessedProjects } = await findMemberById(
|
|
ctx.user.id,
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
if (accessedProjects.length === 0) return { items: [], total: 0 };
|
|
baseConditions.push(
|
|
sql`${projects.projectId} IN (${sql.join(
|
|
accessedProjects.map((id) => sql`${id}`),
|
|
sql`, `,
|
|
)})`,
|
|
);
|
|
}
|
|
|
|
const where = and(...baseConditions);
|
|
|
|
const [items, countResult] = await Promise.all([
|
|
db.query.projects.findMany({
|
|
where,
|
|
limit: input.limit,
|
|
offset: input.offset,
|
|
orderBy: desc(projects.createdAt),
|
|
columns: {
|
|
projectId: true,
|
|
name: true,
|
|
description: true,
|
|
createdAt: true,
|
|
organizationId: true,
|
|
env: true,
|
|
},
|
|
}),
|
|
db
|
|
.select({ count: sql<number>`count(*)::int` })
|
|
.from(projects)
|
|
.where(where),
|
|
]);
|
|
|
|
return {
|
|
items,
|
|
total: countResult[0]?.count ?? 0,
|
|
};
|
|
}),
|
|
|
|
remove: protectedProcedure
|
|
.input(apiRemoveProject)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
if (ctx.user.role === "member") {
|
|
await checkProjectAccess(
|
|
ctx.user.id,
|
|
"delete",
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
}
|
|
const currentProject = await findProjectById(input.projectId);
|
|
if (
|
|
currentProject.organizationId !== ctx.session.activeOrganizationId
|
|
) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "You are not authorized to delete this project",
|
|
});
|
|
}
|
|
const deletedProject = await deleteProject(input.projectId);
|
|
|
|
return deletedProject;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}),
|
|
update: protectedProcedure
|
|
.input(apiUpdateProject)
|
|
.mutation(async ({ input, ctx }) => {
|
|
try {
|
|
const currentProject = await findProjectById(input.projectId);
|
|
if (
|
|
currentProject.organizationId !== ctx.session.activeOrganizationId
|
|
) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "You are not authorized to update this project",
|
|
});
|
|
}
|
|
const project = await updateProjectById(input.projectId, {
|
|
...input,
|
|
});
|
|
|
|
return project;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}),
|
|
duplicate: protectedProcedure
|
|
.input(
|
|
z.object({
|
|
sourceEnvironmentId: z.string(),
|
|
name: z.string(),
|
|
description: z.string().optional(),
|
|
includeServices: z.boolean().default(true),
|
|
selectedServices: z
|
|
.array(
|
|
z.object({
|
|
id: z.string(),
|
|
type: z.enum([
|
|
"application",
|
|
"postgres",
|
|
"mariadb",
|
|
"mongo",
|
|
"mysql",
|
|
"redis",
|
|
"compose",
|
|
]),
|
|
}),
|
|
)
|
|
.optional(),
|
|
duplicateInSameProject: z.boolean().default(false),
|
|
}),
|
|
)
|
|
.mutation(async ({ ctx, input }) => {
|
|
try {
|
|
if (ctx.user.role === "member") {
|
|
await checkProjectAccess(
|
|
ctx.user.id,
|
|
"create",
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
}
|
|
|
|
// Get source project
|
|
const sourceEnvironment = input.duplicateInSameProject
|
|
? await findEnvironmentById(input.sourceEnvironmentId)
|
|
: null;
|
|
|
|
if (
|
|
input.duplicateInSameProject &&
|
|
sourceEnvironment?.project.organizationId !==
|
|
ctx.session.activeOrganizationId
|
|
) {
|
|
throw new TRPCError({
|
|
code: "UNAUTHORIZED",
|
|
message: "You are not authorized to access this project",
|
|
});
|
|
}
|
|
|
|
// Create new project or use existing one
|
|
const targetProject = input.duplicateInSameProject
|
|
? sourceEnvironment
|
|
: await createProject(
|
|
{
|
|
name: input.name,
|
|
description: input.description,
|
|
env: sourceEnvironment?.project.env,
|
|
},
|
|
ctx.session.activeOrganizationId,
|
|
).then((value) => value.environment);
|
|
|
|
console.log("targetProject", targetProject);
|
|
|
|
if (input.includeServices) {
|
|
const servicesToDuplicate = input.selectedServices || [];
|
|
|
|
// Helper function to duplicate a service
|
|
const duplicateService = async (id: string, type: string) => {
|
|
switch (type) {
|
|
case "application": {
|
|
const {
|
|
applicationId,
|
|
domains,
|
|
security,
|
|
ports,
|
|
registry,
|
|
redirects,
|
|
previewDeployments,
|
|
mounts,
|
|
appName,
|
|
refreshToken,
|
|
...application
|
|
} = await findApplicationById(id);
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newApplication = await createApplication({
|
|
...application,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${application.name} (copy)`
|
|
: application.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const domain of domains) {
|
|
const { domainId, ...rest } = domain;
|
|
await createDomain({
|
|
...rest,
|
|
applicationId: newApplication.applicationId,
|
|
domainType: "application",
|
|
});
|
|
}
|
|
|
|
for (const port of ports) {
|
|
const { portId, ...rest } = port;
|
|
await createPort({
|
|
...rest,
|
|
applicationId: newApplication.applicationId,
|
|
});
|
|
}
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newApplication.applicationId,
|
|
serviceType: "application",
|
|
});
|
|
}
|
|
|
|
for (const redirect of redirects) {
|
|
const { redirectId, ...rest } = redirect;
|
|
await createRedirect({
|
|
...rest,
|
|
applicationId: newApplication.applicationId,
|
|
});
|
|
}
|
|
|
|
for (const secure of security) {
|
|
const { securityId, ...rest } = secure;
|
|
await createSecurity({
|
|
...rest,
|
|
applicationId: newApplication.applicationId,
|
|
});
|
|
}
|
|
|
|
for (const previewDeployment of previewDeployments) {
|
|
const { previewDeploymentId, ...rest } = previewDeployment;
|
|
await createPreviewDeployment({
|
|
...rest,
|
|
applicationId: newApplication.applicationId,
|
|
domainId: undefined,
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "postgres": {
|
|
const { postgresId, mounts, backups, appName, ...postgres } =
|
|
await findPostgresById(id);
|
|
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newPostgres = await createPostgres({
|
|
...postgres,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${postgres.name} (copy)`
|
|
: postgres.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newPostgres.postgresId,
|
|
serviceType: "postgres",
|
|
});
|
|
}
|
|
|
|
for (const backup of backups) {
|
|
const { backupId, appName: _appName, ...rest } = backup;
|
|
await createBackup({
|
|
...rest,
|
|
postgresId: newPostgres.postgresId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "mariadb": {
|
|
const { mariadbId, mounts, backups, appName, ...mariadb } =
|
|
await findMariadbById(id);
|
|
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newMariadb = await createMariadb({
|
|
...mariadb,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${mariadb.name} (copy)`
|
|
: mariadb.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newMariadb.mariadbId,
|
|
serviceType: "mariadb",
|
|
});
|
|
}
|
|
|
|
for (const backup of backups) {
|
|
const { backupId, appName: _appName, ...rest } = backup;
|
|
await createBackup({
|
|
...rest,
|
|
mariadbId: newMariadb.mariadbId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "mongo": {
|
|
const { mongoId, mounts, backups, appName, ...mongo } =
|
|
await findMongoById(id);
|
|
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newMongo = await createMongo({
|
|
...mongo,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${mongo.name} (copy)`
|
|
: mongo.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newMongo.mongoId,
|
|
serviceType: "mongo",
|
|
});
|
|
}
|
|
|
|
for (const backup of backups) {
|
|
const { backupId, appName: _appName, ...rest } = backup;
|
|
await createBackup({
|
|
...rest,
|
|
mongoId: newMongo.mongoId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "mysql": {
|
|
const { mysqlId, mounts, backups, appName, ...mysql } =
|
|
await findMySqlById(id);
|
|
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newMysql = await createMysql({
|
|
...mysql,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${mysql.name} (copy)`
|
|
: mysql.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newMysql.mysqlId,
|
|
serviceType: "mysql",
|
|
});
|
|
}
|
|
|
|
for (const backup of backups) {
|
|
const { backupId, appName: _appName, ...rest } = backup;
|
|
await createBackup({
|
|
...rest,
|
|
mysqlId: newMysql.mysqlId,
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "redis": {
|
|
const { redisId, mounts, appName, ...redis } =
|
|
await findRedisById(id);
|
|
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newRedis = await createRedis({
|
|
...redis,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${redis.name} (copy)`
|
|
: redis.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newRedis.redisId,
|
|
serviceType: "redis",
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "compose": {
|
|
const {
|
|
composeId,
|
|
mounts,
|
|
domains,
|
|
appName,
|
|
refreshToken,
|
|
...compose
|
|
} = await findComposeById(id);
|
|
|
|
const newAppName = appName.substring(
|
|
0,
|
|
appName.lastIndexOf("-"),
|
|
);
|
|
|
|
const newCompose = await createCompose({
|
|
...compose,
|
|
appName: newAppName,
|
|
name: input.duplicateInSameProject
|
|
? `${compose.name} (copy)`
|
|
: compose.name,
|
|
environmentId: targetProject?.environmentId || "",
|
|
});
|
|
|
|
for (const mount of mounts) {
|
|
const { mountId, ...rest } = mount;
|
|
await createMount({
|
|
...rest,
|
|
serviceId: newCompose.composeId,
|
|
serviceType: "compose",
|
|
});
|
|
}
|
|
|
|
for (const domain of domains) {
|
|
const { domainId, ...rest } = domain;
|
|
await createDomain({
|
|
...rest,
|
|
composeId: newCompose.composeId,
|
|
domainType: "compose",
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Duplicate selected services
|
|
for (const service of servicesToDuplicate) {
|
|
await duplicateService(service.id, service.type);
|
|
}
|
|
}
|
|
|
|
if (!input.duplicateInSameProject && ctx.user.role === "member") {
|
|
await addNewProject(
|
|
ctx.user.id,
|
|
targetProject?.projectId || "",
|
|
ctx.session.activeOrganizationId,
|
|
);
|
|
}
|
|
|
|
return targetProject;
|
|
} catch (error) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: `Error duplicating the project: ${error instanceof Error ? error.message : error}`,
|
|
cause: error,
|
|
});
|
|
}
|
|
}),
|
|
});
|
|
|
|
function buildServiceFilter(
|
|
fieldName: AnyPgColumn,
|
|
accessedServices: string[],
|
|
) {
|
|
return accessedServices.length === 0
|
|
? sql`false`
|
|
: sql`${fieldName} IN (${sql.join(
|
|
accessedServices.map((serviceId) => sql`${serviceId}`),
|
|
sql`, `,
|
|
)})`;
|
|
}
|