421 lines · 10.3 KB · TypeScript
RawDownload
import path from "node:path";import { paths } from "@dokploy/server/constants";import { db } from "@dokploy/server/db";import {	type apiCreateMount,	mounts,	type ServiceType,} from "@dokploy/server/db/schema";import {	createFile,	encodeBase64,	getCreateFileCommand,} from "@dokploy/server/utils/docker/utils";import { removeFileOrDirectory } from "@dokploy/server/utils/filesystem/directory";import {	execAsync,	execAsyncRemote,} from "@dokploy/server/utils/process/execAsync";import { TRPCError } from "@trpc/server";import { eq, type SQL, sql } from "drizzle-orm";import type { z } from "zod"; export type Mount = typeof mounts.$inferSelect; export const createMount = async (input: z.infer<typeof apiCreateMount>) => {	try {		const { serviceId, ...rest } = input;		const value = await db			.insert(mounts)			.values({				...rest,				...(input.serviceType === "application" && {					applicationId: serviceId,				}),				...(input.serviceType === "compose" && {					composeId: serviceId,				}),				...(input.serviceType === "libsql" && {					libsqlId: serviceId,				}),				...(input.serviceType === "mariadb" && {					mariadbId: serviceId,				}),				...(input.serviceType === "mongo" && {					mongoId: serviceId,				}),				...(input.serviceType === "mysql" && {					mysqlId: serviceId,				}),				...(input.serviceType === "postgres" && {					postgresId: serviceId,				}),				...(input.serviceType === "redis" && {					redisId: serviceId,				}),			})			.returning()			.then((value) => value[0]); 		if (!value) {			throw new TRPCError({				code: "BAD_REQUEST",				message: "Error inserting mount",			});		} 		if (value.type === "file") {			await createFileMount(value.mountId);		}		return value;	} catch (error) {		console.log(error);		throw new TRPCError({			code: "BAD_REQUEST",			message: `Error ${error instanceof Error ? error.message : error}`,			cause: error,		});	}}; export const createFileMount = async (mountId: string) => {	try {		const mount = await findMountById(mountId);		const baseFilePath = await getBaseFilesPath(mountId); 		const serverId = await getServerId(mount); 		if (serverId) {			const command = getCreateFileCommand(				baseFilePath,				mount.filePath || "",				mount.content || "",			);			await execAsyncRemote(serverId, command);		} else {			await createFile(baseFilePath, mount.filePath || "", mount.content || "");		}	} catch (error) {		console.log(`Error creating the file mount: ${error}`);		throw new TRPCError({			code: "BAD_REQUEST",			message: `Error creating the mount ${error instanceof Error ? error.message : error}`,			cause: error,		});	}}; export const findMountById = async (mountId: string) => {	const mount = await db.query.mounts.findFirst({		where: eq(mounts.mountId, mountId),		with: {			application: {				with: {					environment: {						with: {							project: true,						},					},				},			},			compose: {				with: {					environment: {						with: {							project: true,						},					},				},			},			libsql: {				with: {					environment: {						with: {							project: true,						},					},				},			},			mariadb: {				with: {					environment: {						with: {							project: true,						},					},				},			},			mongo: {				with: {					environment: {						with: {							project: true,						},					},				},			},			mysql: {				with: {					environment: {						with: {							project: true,						},					},				},			},			postgres: {				with: {					environment: {						with: {							project: true,						},					},				},			},			redis: {				with: {					environment: {						with: {							project: true,						},					},				},			},		},	});	if (!mount) {		throw new TRPCError({			code: "NOT_FOUND",			message: "Mount not found",		});	}	return mount;}; export const findMountOrganizationId = async (mountId: string) => {	const mount = await findMountById(mountId); 	if (mount.application) {		return mount.application.environment.project.organizationId;	}	if (mount.compose) {		return mount.compose.environment.project.organizationId;	}	if (mount.libsql) {		return mount.libsql.environment.project.organizationId;	}	if (mount.mariadb) {		return mount.mariadb.environment.project.organizationId;	}	if (mount.mongo) {		return mount.mongo.environment.project.organizationId;	}	if (mount.mysql) {		return mount.mysql.environment.project.organizationId;	}	if (mount.postgres) {		return mount.postgres.environment.project.organizationId;	}	if (mount.redis) {		return mount.redis.environment.project.organizationId;	} 	return null;}; export const updateMount = async (	mountId: string,	mountData: Partial<Mount>,) => {	const mount = await db.transaction(async (tx) => {		const mount = await tx			.update(mounts)			.set({				...mountData,			})			.where(eq(mounts.mountId, mountId))			.returning()			.then((value) => value[0]); 		if (!mount) {			throw new TRPCError({				code: "NOT_FOUND",				message: "Mount not found",			});		} 		return await findMountById(mountId);	}); 	if (mount.type === "file") {		await updateFileMount(mountId);	}	return mount;}; export const findMountsByApplicationId = async (	serviceId: string,	serviceType: ServiceType,) => {	const sqlChunks: SQL[] = []; 	switch (serviceType) {		case "application":			sqlChunks.push(eq(mounts.applicationId, serviceId));			break;		case "libsql":			sqlChunks.push(eq(mounts.libsqlId, serviceId));			break;		case "mariadb":			sqlChunks.push(eq(mounts.mariadbId, serviceId));			break;		case "mongo":			sqlChunks.push(eq(mounts.mongoId, serviceId));			break;		case "mysql":			sqlChunks.push(eq(mounts.mysqlId, serviceId));			break;		case "postgres":			sqlChunks.push(eq(mounts.postgresId, serviceId));			break;		case "redis":			sqlChunks.push(eq(mounts.redisId, serviceId));			break;		case "compose":			sqlChunks.push(eq(mounts.composeId, serviceId));			break;		default:			throw new Error(`Unknown service type: ${serviceType}`);	}	const mount = await db.query.mounts.findMany({		where: sql.join(sqlChunks, sql.raw(" ")),	}); 	return mount;}; export const deleteMount = async (mountId: string) => {	const { type } = await findMountById(mountId); 	if (type === "file") {		await deleteFileMount(mountId);	} 	const deletedMount = await db		.delete(mounts)		.where(eq(mounts.mountId, mountId))		.returning();	return deletedMount[0];}; export const updateFileMount = async (mountId: string) => {	const mount = await findMountById(mountId);	if (!mount || !mount.filePath) return;	const basePath = await getBaseFilesPath(mountId);	const fullPath = path.join(basePath, mount.filePath); 	try {		const serverId = await getServerId(mount);		const encodedContent = encodeBase64(mount.content || "");		const command = `echo "${encodedContent}" | base64 -d > ${fullPath}`;		if (serverId) {			await execAsyncRemote(serverId, command);		} else {			await execAsync(command);		}	} catch {		console.log("Error updating file mount");	}}; export const deleteFileMount = async (mountId: string) => {	const mount = await findMountById(mountId);	if (!mount.filePath) return;	const basePath = await getBaseFilesPath(mountId); 	const fullPath = path.join(basePath, mount.filePath);	try {		const serverId = await getServerId(mount);		if (serverId) {			const command = `rm -rf ${fullPath}`;			await execAsyncRemote(serverId, command);		} else {			await removeFileOrDirectory(fullPath);		}	} catch {}}; export const getBaseFilesPath = async (mountId: string) => {	const mount = await findMountById(mountId); 	let absoluteBasePath = "";	let appName = "";	let directoryPath = ""; 	if (mount.serviceType === "application" && mount.application) {		const { APPLICATIONS_PATH } = paths(!!mount.application.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.application.appName;	} else if (mount.serviceType === "postgres" && mount.postgres) {		const { APPLICATIONS_PATH } = paths(!!mount.postgres.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.postgres.appName;	} else if (mount.serviceType === "mariadb" && mount.mariadb) {		const { APPLICATIONS_PATH } = paths(!!mount.mariadb.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.mariadb.appName;	} else if (mount.serviceType === "mongo" && mount.mongo) {		const { APPLICATIONS_PATH } = paths(!!mount.mongo.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.mongo.appName;	} else if (mount.serviceType === "mysql" && mount.mysql) {		const { APPLICATIONS_PATH } = paths(!!mount.mysql.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.mysql.appName;	} else if (mount.serviceType === "redis" && mount.redis) {		const { APPLICATIONS_PATH } = paths(!!mount.redis.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.redis.appName;	} else if (mount.serviceType === "compose" && mount.compose) {		const { COMPOSE_PATH } = paths(!!mount.compose.serverId);		appName = mount.compose.appName;		absoluteBasePath = path.resolve(COMPOSE_PATH);	} else if (mount.serviceType === "libsql" && mount.libsql) {		const { APPLICATIONS_PATH } = paths(!!mount.libsql.serverId);		absoluteBasePath = path.resolve(APPLICATIONS_PATH);		appName = mount.libsql.appName;	}	directoryPath = path.join(absoluteBasePath, appName, "files"); 	return directoryPath;}; type MountNested = Awaited<ReturnType<typeof findMountById>>;export const getServerId = async (mount: MountNested) => {	if (mount.serviceType === "application" && mount?.application?.serverId) {		return mount.application.serverId;	}	if (mount.serviceType === "postgres" && mount?.postgres?.serverId) {		return mount.postgres.serverId;	}	if (mount.serviceType === "mariadb" && mount?.mariadb?.serverId) {		return mount.mariadb.serverId;	}	if (mount.serviceType === "mongo" && mount?.mongo?.serverId) {		return mount.mongo.serverId;	}	if (mount.serviceType === "mysql" && mount?.mysql?.serverId) {		return mount.mysql.serverId;	}	if (mount.serviceType === "redis" && mount?.redis?.serverId) {		return mount.redis.serverId;	}	if (mount.serviceType === "compose" && mount?.compose?.serverId) {		return mount.compose.serverId;	}	if (mount.serviceType === "libsql" && mount?.libsql?.serverId) {		return mount.libsql.serverId;	} 	return null;};