mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-15 13:21:11 +00:00
02760212f7
Show the services attached to a remote server directly in its delete confirmation modal, with a link to each service and a per-service delete action, instead of only showing a generic 'has active services' blocker.
282 lines
7.1 KiB
TypeScript
282 lines
7.1 KiB
TypeScript
import { db } from "@dokploy/server/db";
|
|
import {
|
|
type apiCreateServer,
|
|
member,
|
|
organization,
|
|
server,
|
|
} from "@dokploy/server/db/schema";
|
|
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
|
import { TRPCError } from "@trpc/server";
|
|
import { and, eq } from "drizzle-orm";
|
|
import type { z } from "zod";
|
|
|
|
export type Server = typeof server.$inferSelect;
|
|
|
|
export const createServer = async (
|
|
input: z.infer<typeof apiCreateServer>,
|
|
organizationId: string,
|
|
) => {
|
|
const newServer = await db
|
|
.insert(server)
|
|
.values({
|
|
...input,
|
|
organizationId: organizationId,
|
|
createdAt: new Date().toISOString(),
|
|
} as typeof server.$inferInsert)
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newServer) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error creating the server",
|
|
});
|
|
}
|
|
|
|
return newServer;
|
|
};
|
|
|
|
export const findServerById = async (serverId: string) => {
|
|
const currentServer = await db.query.server.findFirst({
|
|
where: eq(server.serverId, serverId),
|
|
with: {
|
|
deployments: true,
|
|
sshKey: true,
|
|
},
|
|
});
|
|
if (!currentServer) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Server not found",
|
|
});
|
|
}
|
|
return currentServer;
|
|
};
|
|
|
|
/**
|
|
* Removes the SSH private key material from a server record before it is sent
|
|
* to a client. `findServerById` eagerly loads the `sshKey` relation (needed for
|
|
* server-side SSH operations), but the private key must never leave the server:
|
|
* no client feature consumes it, and returning it exposed it to any member with
|
|
* only `server:read`. Server-side callers keep using `findServerById` directly.
|
|
*/
|
|
export const redactServerSshKey = <
|
|
T extends { sshKey?: { privateKey: string } | null },
|
|
>(
|
|
serverRecord: T,
|
|
): T => {
|
|
if (!serverRecord.sshKey) {
|
|
return serverRecord;
|
|
}
|
|
return {
|
|
...serverRecord,
|
|
sshKey: { ...serverRecord.sshKey, privateKey: "" },
|
|
};
|
|
};
|
|
|
|
export const findServersByUserId = async (userId: string) => {
|
|
const orgs = await db.query.organization.findMany({
|
|
where: eq(organization.ownerId, userId),
|
|
with: {
|
|
servers: true,
|
|
},
|
|
});
|
|
|
|
const servers = orgs.flatMap((org) => org.servers);
|
|
|
|
return servers;
|
|
};
|
|
|
|
export const deleteServer = async (serverId: string) => {
|
|
const currentServer = await db
|
|
.delete(server)
|
|
.where(eq(server.serverId, serverId))
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
return currentServer;
|
|
};
|
|
|
|
export const haveActiveServices = async (serverId: string) => {
|
|
const currentServer = await db.query.server.findFirst({
|
|
where: eq(server.serverId, serverId),
|
|
columns: { serverId: true },
|
|
with: {
|
|
applications: { columns: { applicationId: true } },
|
|
compose: { columns: { composeId: true } },
|
|
libsql: { columns: { libsqlId: true } },
|
|
mariadb: { columns: { mariadbId: true } },
|
|
mongo: { columns: { mongoId: true } },
|
|
mysql: { columns: { mysqlId: true } },
|
|
postgres: { columns: { postgresId: true } },
|
|
redis: { columns: { redisId: true } },
|
|
},
|
|
});
|
|
|
|
if (!currentServer) {
|
|
return false;
|
|
}
|
|
|
|
const total =
|
|
currentServer?.applications?.length +
|
|
currentServer?.compose?.length +
|
|
currentServer?.libsql?.length +
|
|
currentServer?.mariadb?.length +
|
|
currentServer?.mongo?.length +
|
|
currentServer?.mysql?.length +
|
|
currentServer?.postgres?.length +
|
|
currentServer?.redis?.length;
|
|
|
|
if (total === 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
export const SERVICE_TYPES_BY_SERVER = [
|
|
{ type: "application", relation: "applications", idColumn: "applicationId" },
|
|
{ type: "compose", relation: "compose", idColumn: "composeId" },
|
|
{ type: "postgres", relation: "postgres", idColumn: "postgresId" },
|
|
{ type: "mysql", relation: "mysql", idColumn: "mysqlId" },
|
|
{ type: "mariadb", relation: "mariadb", idColumn: "mariadbId" },
|
|
{ type: "mongo", relation: "mongo", idColumn: "mongoId" },
|
|
{ type: "redis", relation: "redis", idColumn: "redisId" },
|
|
{ type: "libsql", relation: "libsql", idColumn: "libsqlId" },
|
|
] as const;
|
|
|
|
export interface ServerService {
|
|
id: string;
|
|
type: (typeof SERVICE_TYPES_BY_SERVER)[number]["type"];
|
|
name: string;
|
|
projectId: string;
|
|
environmentId: string;
|
|
url: string;
|
|
}
|
|
|
|
export const getServicesByServerId = async (
|
|
serverId: string,
|
|
): Promise<ServerService[]> => {
|
|
const currentServer = await db.query.server.findFirst({
|
|
where: eq(server.serverId, serverId),
|
|
columns: { serverId: true },
|
|
with: {
|
|
applications: {
|
|
columns: { applicationId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
compose: {
|
|
columns: { composeId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
postgres: {
|
|
columns: { postgresId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
mysql: {
|
|
columns: { mysqlId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
mariadb: {
|
|
columns: { mariadbId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
mongo: {
|
|
columns: { mongoId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
redis: {
|
|
columns: { redisId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
libsql: {
|
|
columns: { libsqlId: true, name: true },
|
|
with: { environment: { with: { project: true } } },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!currentServer) {
|
|
return [];
|
|
}
|
|
|
|
const services: ServerService[] = [];
|
|
|
|
for (const { type, relation, idColumn } of SERVICE_TYPES_BY_SERVER) {
|
|
const rows = currentServer[relation as keyof typeof currentServer] as Array<
|
|
Record<string, any>
|
|
>;
|
|
|
|
for (const row of rows) {
|
|
const projectId = row.environment.project.projectId as string;
|
|
const environmentId = row.environment.environmentId as string;
|
|
|
|
services.push({
|
|
id: row[idColumn],
|
|
type,
|
|
name: row.name,
|
|
projectId,
|
|
environmentId,
|
|
url: `/dashboard/project/${projectId}/environment/${environmentId}/services/${type}/${row[idColumn]}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
return services;
|
|
};
|
|
|
|
export const updateServerById = async (
|
|
serverId: string,
|
|
serverData: Partial<Server>,
|
|
) => {
|
|
const result = await db
|
|
.update(server)
|
|
.set({
|
|
...serverData,
|
|
})
|
|
.where(eq(server.serverId, serverId))
|
|
.returning()
|
|
.then((res) => res[0]);
|
|
|
|
return result;
|
|
};
|
|
|
|
export const getAllServers = async () => {
|
|
const servers = await db.query.server.findMany();
|
|
return servers;
|
|
};
|
|
|
|
export const getAccessibleServerIds = async (session: {
|
|
userId: string;
|
|
activeOrganizationId: string;
|
|
}): Promise<Set<string>> => {
|
|
const { userId, activeOrganizationId } = session;
|
|
|
|
const allOrgServers = await db.query.server.findMany({
|
|
where: eq(server.organizationId, activeOrganizationId),
|
|
columns: {
|
|
serverId: true,
|
|
},
|
|
});
|
|
|
|
const memberRecord = await db.query.member.findFirst({
|
|
where: and(
|
|
eq(member.userId, userId),
|
|
eq(member.organizationId, activeOrganizationId),
|
|
),
|
|
columns: { accessedServers: true, role: true },
|
|
});
|
|
|
|
if (memberRecord?.role === "owner" || memberRecord?.role === "admin") {
|
|
return new Set(allOrgServers.map((s) => s.serverId));
|
|
}
|
|
|
|
const licensed = await hasValidLicense(activeOrganizationId);
|
|
|
|
if (!licensed) {
|
|
return new Set(allOrgServers.map((s) => s.serverId));
|
|
}
|
|
|
|
return new Set(memberRecord?.accessedServers ?? []);
|
|
};
|