feat: add libsql database

This commit is contained in:
Oliver Geneser
2025-09-13 10:11:43 +02:00
parent 24729f35ec
commit 4b1f359cb6
53 changed files with 2942 additions and 486 deletions

View File

@@ -35,13 +35,14 @@ export const findEnvironmentById = async (environmentId: string) => {
where: eq(environments.environmentId, environmentId),
with: {
applications: true,
compose: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
compose: true,
project: true,
redis: true,
},
});
if (!environment) {
@@ -59,13 +60,14 @@ export const findEnvironmentsByProjectId = async (projectId: string) => {
orderBy: asc(environments.createdAt),
with: {
applications: true,
compose: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
compose: true,
project: true,
redis: true,
},
});
return projectEnvironments;

View File

@@ -0,0 +1,160 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateLibsql,
buildAppName,
libsql,
} from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates";
import { buildLibsql } from "@dokploy/server/utils/databases/libsql";
import { pullImage } from "@dokploy/server/utils/docker/utils";
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { validUniqueServerAppName } from "./project";
export type Libsql = typeof libsql.$inferSelect;
export const createLibsql = async (input: typeof apiCreateLibsql._type) => {
const appName = buildAppName("libsql", input.appName);
const valid = await validUniqueServerAppName(input.appName);
if (!valid) {
throw new TRPCError({
code: "CONFLICT",
message: "Service with this 'AppName' already exists",
});
}
const newLibsql = await db
.insert(libsql)
.values({
...input,
databasePassword: input.databasePassword
? input.databasePassword
: generatePassword(),
appName,
})
.returning()
.then((value) => value[0]);
if (!newLibsql) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting libsql database",
});
}
return newLibsql;
};
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
export const findLibsqlById = async (libsqlId: string) => {
const result = await db.query.libsql.findFirst({
where: eq(libsql.libsqlId, libsqlId),
with: {
environment: {
with: {
project: true,
},
},
mounts: true,
server: true,
// backups: {
// with: {
// destination: true,
// deployments: true,
// },
// },
},
});
if (!result) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Libsql not found",
});
}
return result;
};
export const updateLibsqlById = async (
libsqlId: string,
libsqlData: Partial<Libsql>,
) => {
const { appName, ...rest } = libsqlData;
const result = await db
.update(libsql)
.set({
...rest,
})
.where(eq(libsql.libsqlId, libsqlId))
.returning();
return result[0];
};
export const removeLibsqlById = async (libsqlId: string) => {
const result = await db
.delete(libsql)
.where(eq(libsql.libsqlId, libsqlId))
.returning();
return result[0];
};
// export const findLibsqlByBackupId = async (backupId: string) => {
// const result = await db
// .select({
// ...getTableColumns(libsql),
// })
// .from(libsql)
// .innerJoin(backups, eq(libsql.libsqlId, backups.libsqlId))
// .where(eq(backups.backupId, backupId))
// .limit(1);
//
// if (!result || !result[0]) {
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "Libsql not found",
// });
// }
// return result[0];
// };
export const deployLibsql = async (
libsqlId: string,
onData?: (data: any) => void,
) => {
const libsql = await findLibsqlById(libsqlId);
try {
await updateLibsqlById(libsqlId, {
applicationStatus: "running",
});
onData?.("Starting libsql deployment...");
if (libsql.serverId) {
await execAsyncRemote(
libsql.serverId,
`docker pull ${libsql.dockerImage}`,
onData,
);
} else {
await pullImage(libsql.dockerImage, onData);
}
await buildLibsql(libsql);
await updateLibsqlById(libsqlId, {
applicationStatus: "done",
});
onData?.("Deployment completed successfully!");
} catch (error) {
onData?.(`Error: ${error}`);
await updateLibsqlById(libsqlId, {
applicationStatus: "error",
});
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Error on deploy libsql${error}`,
});
}
return libsql;
};

View File

@@ -31,8 +31,11 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
...(input.serviceType === "application" && {
applicationId: serviceId,
}),
...(input.serviceType === "postgres" && {
postgresId: serviceId,
...(input.serviceType === "compose" && {
composeId: serviceId,
}),
...(input.serviceType === "libsql" && {
libsqlId: serviceId,
}),
...(input.serviceType === "mariadb" && {
mariadbId: serviceId,
@@ -43,12 +46,12 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
...(input.serviceType === "mysql" && {
mysqlId: serviceId,
}),
...(input.serviceType === "postgres" && {
postgresId: serviceId,
}),
...(input.serviceType === "redis" && {
redisId: serviceId,
}),
...(input.serviceType === "compose" && {
composeId: serviceId,
}),
})
.returning()
.then((value) => value[0]);
@@ -114,7 +117,16 @@ export const findMountById = async (mountId: string) => {
},
},
},
postgres: {
compose: {
with: {
environment: {
with: {
project: true,
},
},
},
},
libsql: {
with: {
environment: {
with: {
@@ -150,7 +162,7 @@ export const findMountById = async (mountId: string) => {
},
},
},
redis: {
postgres: {
with: {
environment: {
with: {
@@ -159,7 +171,7 @@ export const findMountById = async (mountId: string) => {
},
},
},
compose: {
redis: {
with: {
environment: {
with: {
@@ -185,8 +197,11 @@ export const findMountOrganizationId = async (mountId: string) => {
if (mount.application) {
return mount.application.environment.project.organizationId;
}
if (mount.postgres) {
return mount.postgres.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;
@@ -197,13 +212,13 @@ export const findMountOrganizationId = async (mountId: string) => {
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;
}
if (mount.compose) {
return mount.compose.environment.project.organizationId;
}
return null;
};
@@ -247,8 +262,8 @@ export const findMountsByApplicationId = async (
case "application":
sqlChunks.push(eq(mounts.applicationId, serviceId));
break;
case "postgres":
sqlChunks.push(eq(mounts.postgresId, serviceId));
case "libsql":
sqlChunks.push(eq(mounts.libsqlId, serviceId));
break;
case "mariadb":
sqlChunks.push(eq(mounts.mariadbId, serviceId));
@@ -259,6 +274,9 @@ export const findMountsByApplicationId = async (
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;
@@ -334,10 +352,14 @@ export const getBaseFilesPath = async (mountId: string) => {
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);
} 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.postgres.appName;
appName = mount.libsql.appName;
} else if (mount.serviceType === "mariadb" && mount.mariadb) {
const { APPLICATIONS_PATH } = paths(!!mount.mariadb.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
@@ -350,14 +372,14 @@ export const getBaseFilesPath = async (mountId: string) => {
const { APPLICATIONS_PATH } = paths(!!mount.mysql.serverId);
absoluteBasePath = path.resolve(APPLICATIONS_PATH);
appName = mount.mysql.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 === "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);
}
directoryPath = path.join(absoluteBasePath, appName, "files");
@@ -369,8 +391,11 @@ 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 === "compose" && mount?.compose?.serverId) {
return mount.compose.serverId;
}
if (mount.serviceType === "libsql" && mount?.libsql?.serverId) {
return mount.libsql.serverId;
}
if (mount.serviceType === "mariadb" && mount?.mariadb?.serverId) {
return mount.mariadb.serverId;
@@ -381,12 +406,12 @@ export const getServerId = async (mount: MountNested) => {
if (mount.serviceType === "mysql" && mount?.mysql?.serverId) {
return mount.mysql.serverId;
}
if (mount.serviceType === "postgres" && mount?.postgres?.serverId) {
return mount.postgres.serverId;
}
if (mount.serviceType === "redis" && mount?.redis?.serverId) {
return mount.redis.serverId;
}
if (mount.serviceType === "compose" && mount?.compose?.serverId) {
return mount.compose.serverId;
}
return null;
};

View File

@@ -2,6 +2,7 @@ import { db } from "@dokploy/server/db";
import {
type apiCreateProject,
applications,
libsql,
mariadb,
mongo,
mysql,
@@ -52,12 +53,13 @@ export const findProjectById = async (projectId: string) => {
environments: {
with: {
applications: true,
compose: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
compose: true,
},
},
},
@@ -103,6 +105,9 @@ export const validUniqueServerAppName = async (appName: string) => {
applications: {
where: eq(applications.appName, appName),
},
libsql: {
where: eq(libsql.appName, appName),
},
mariadb: {
where: eq(mariadb.appName, appName),
},
@@ -125,6 +130,7 @@ export const validUniqueServerAppName = async (appName: string) => {
const nonEmptyProjects = query.filter(
(project) =>
project.applications.length > 0 ||
project.libsql.length > 0 ||
project.mariadb.length > 0 ||
project.mongo.length > 0 ||
project.mysql.length > 0 ||

View File

@@ -79,11 +79,12 @@ export const haveActiveServices = async (serverId: string) => {
with: {
applications: true,
compose: true,
redis: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
},
});
@@ -94,11 +95,12 @@ export const haveActiveServices = async (serverId: string) => {
const total =
currentServer?.applications?.length +
currentServer?.compose?.length +
currentServer?.redis?.length +
currentServer?.libsql?.length +
currentServer?.mariadb?.length +
currentServer?.mongo?.length +
currentServer?.mysql?.length +
currentServer?.postgres?.length;
currentServer?.postgres?.length +
currentServer?.redis?.length;
if (total === 0) {
return false;

View File

@@ -13,13 +13,14 @@ export const findVolumeBackupById = async (volumeBackupId: string) => {
where: eq(volumeBackups.volumeBackupId, volumeBackupId),
with: {
application: true,
postgres: true,
mysql: true,
mariadb: true,
mongo: true,
redis: true,
compose: true,
destination: true,
libsql: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
},
});