From c752cf3f9eafe04b87b58b3b787a5385012de762 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 23 Mar 2026 21:51:02 -0600 Subject: [PATCH] feat(libsql): implement libsql service schema and update related components - Created a new SQL type for 'libsql' and established a corresponding table with necessary fields and constraints. - Updated existing tables (backup, mount, volume_backup) to include foreign key references to 'libsql'. - Enhanced the libsql schema in the application to support additional fields such as stopGracePeriodSwarm and endpointSpecSwarm. - Adjusted form handling and validation to accommodate the new libsql service type, improving overall integration and functionality. --- .../cluster/modify-swarm-settings.tsx | 109 ------------------ ...ang.sql => 0153_motionless_mastermind.sql} | 2 + apps/dokploy/drizzle/meta/0153_snapshot.json | 14 ++- apps/dokploy/drizzle/meta/_journal.json | 4 +- apps/dokploy/server/api/routers/backup.ts | 1 + apps/dokploy/server/api/routers/mount.ts | 3 + .../server/api/routers/volume-backups.ts | 9 +- apps/schedules/src/utils.ts | 11 ++ packages/server/src/db/schema/libsql.ts | 15 ++- .../server/src/utils/volume-backups/backup.ts | 3 +- .../server/src/utils/volume-backups/utils.ts | 2 + 11 files changed, 58 insertions(+), 115 deletions(-) rename apps/dokploy/drizzle/{0153_green_boomerang.sql => 0153_motionless_mastermind.sql} (97%) diff --git a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx index 21e3cf3de..81a09ec0f 100644 --- a/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/cluster/modify-swarm-settings.tsx @@ -17,7 +17,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; -import { api } from "@/utils/api"; import { EndpointSpecForm, HealthCheckForm, @@ -111,13 +110,6 @@ const menuItems: MenuItem[] = [ }, ]; -const hasStopGracePeriodSwarm = ( - value: unknown, -): value is { stopGracePeriodSwarm: bigint | number | string | null } => - typeof value === "object" && - value !== null && - "stopGracePeriodSwarm" in value; - interface Props { id: string; type: @@ -131,107 +123,6 @@ interface Props { } export const AddSwarmSettings = ({ id, type }: Props) => { - const queryMap = { - application: () => - api.application.one.useQuery({ applicationId: id }, { enabled: !!id }), - libsql: () => api.libsql.one.useQuery({ libsqlId: id }, { enabled: !!id }), - mariadb: () => - api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }), - mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }), - mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }), - postgres: () => - api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }), - redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }), - }; - const { data, refetch } = queryMap[type] - ? queryMap[type]() - : api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }); - - const mutationMap = { - application: () => api.application.update.useMutation(), - libsql: () => api.libsql.update.useMutation(), - mariadb: () => api.mariadb.update.useMutation(), - mongo: () => api.mongo.update.useMutation(), - mysql: () => api.mysql.update.useMutation(), - postgres: () => api.postgres.update.useMutation(), - redis: () => api.redis.update.useMutation(), - }; - - const { mutateAsync, isError, error, isLoading } = mutationMap[type] - ? mutationMap[type]() - : api.mongo.update.useMutation(); - - const form = useForm({ - defaultValues: { - healthCheckSwarm: null, - restartPolicySwarm: null, - placementSwarm: null, - updateConfigSwarm: null, - rollbackConfigSwarm: null, - modeSwarm: null, - labelsSwarm: null, - networkSwarm: null, - }, - resolver: zodResolver(addSwarmSettings), - }); - - useEffect(() => { - if (data) { - form.reset({ - healthCheckSwarm: data.healthCheckSwarm - ? JSON.stringify(data.healthCheckSwarm, null, 2) - : null, - restartPolicySwarm: data.restartPolicySwarm - ? JSON.stringify(data.restartPolicySwarm, null, 2) - : null, - placementSwarm: data.placementSwarm - ? JSON.stringify(data.placementSwarm, null, 2) - : null, - updateConfigSwarm: data.updateConfigSwarm - ? JSON.stringify(data.updateConfigSwarm, null, 2) - : null, - rollbackConfigSwarm: data.rollbackConfigSwarm - ? JSON.stringify(data.rollbackConfigSwarm, null, 2) - : null, - modeSwarm: data.modeSwarm - ? JSON.stringify(data.modeSwarm, null, 2) - : null, - labelsSwarm: data.labelsSwarm - ? JSON.stringify(data.labelsSwarm, null, 2) - : null, - networkSwarm: data.networkSwarm - ? JSON.stringify(data.networkSwarm, null, 2) - : null, - }); - } - }, [form, form.reset, data]); - - const onSubmit = async (data: AddSwarmSettings) => { - await mutateAsync({ - applicationId: id || "", - libsqlId: id || "", - mariadbId: id || "", - mongoId: id || "", - mysqlId: id || "", - postgresId: id || "", - redisId: id || "", - healthCheckSwarm: data.healthCheckSwarm, - restartPolicySwarm: data.restartPolicySwarm, - placementSwarm: data.placementSwarm, - updateConfigSwarm: data.updateConfigSwarm, - rollbackConfigSwarm: data.rollbackConfigSwarm, - modeSwarm: data.modeSwarm, - labelsSwarm: data.labelsSwarm, - networkSwarm: data.networkSwarm, - }) - .then(async () => { - toast.success("Swarm settings updated"); - refetch(); - }) - .catch(() => { - toast.error("Error updating the swarm settings"); - }); - }; const [activeMenu, setActiveMenu] = useState("health-check"); const [open, setOpen] = useState(false); return ( diff --git a/apps/dokploy/drizzle/0153_green_boomerang.sql b/apps/dokploy/drizzle/0153_motionless_mastermind.sql similarity index 97% rename from apps/dokploy/drizzle/0153_green_boomerang.sql rename to apps/dokploy/drizzle/0153_motionless_mastermind.sql index 39a454926..27aa00135 100644 --- a/apps/dokploy/drizzle/0153_green_boomerang.sql +++ b/apps/dokploy/drizzle/0153_motionless_mastermind.sql @@ -30,6 +30,8 @@ CREATE TABLE "libsql" ( "modeSwarm" json, "labelsSwarm" json, "networkSwarm" json, + "stopGracePeriodSwarm" bigint, + "endpointSpecSwarm" json, "replicas" integer DEFAULT 1 NOT NULL, "createdAt" text NOT NULL, "environmentId" text NOT NULL, diff --git a/apps/dokploy/drizzle/meta/0153_snapshot.json b/apps/dokploy/drizzle/meta/0153_snapshot.json index 86cc71627..0878c33c1 100644 --- a/apps/dokploy/drizzle/meta/0153_snapshot.json +++ b/apps/dokploy/drizzle/meta/0153_snapshot.json @@ -1,5 +1,5 @@ { - "id": "72bb48c9-d724-458b-bd91-3ae50e9afe0f", + "id": "2819200c-c7c0-474e-8d78-b96ee4785b2a", "prevId": "68c71185-6e91-4b8d-8f91-795457e75ab2", "version": "7", "dialect": "postgresql", @@ -3759,6 +3759,18 @@ "primaryKey": false, "notNull": false }, + "stopGracePeriodSwarm": { + "name": "stopGracePeriodSwarm", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "endpointSpecSwarm": { + "name": "endpointSpecSwarm", + "type": "json", + "primaryKey": false, + "notNull": false + }, "replicas": { "name": "replicas", "type": "integer", diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json index 4078b4093..61386a043 100644 --- a/apps/dokploy/drizzle/meta/_journal.json +++ b/apps/dokploy/drizzle/meta/_journal.json @@ -1076,8 +1076,8 @@ { "idx": 153, "version": "7", - "when": 1774303955927, - "tag": "0153_green_boomerang", + "when": 1774322599182, + "tag": "0153_motionless_mastermind", "breakpoints": true } ] diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts index e365973a5..6d358f631 100644 --- a/apps/dokploy/server/api/routers/backup.ts +++ b/apps/dokploy/server/api/routers/backup.ts @@ -238,6 +238,7 @@ export const backupRouter = createTRPCRouter({ backup.mysqlId || backup.mariadbId || backup.mongoId || + backup.libsqlId || backup.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { diff --git a/apps/dokploy/server/api/routers/mount.ts b/apps/dokploy/server/api/routers/mount.ts index ca165ffd0..9d9e0a06e 100644 --- a/apps/dokploy/server/api/routers/mount.ts +++ b/apps/dokploy/server/api/routers/mount.ts @@ -100,6 +100,7 @@ export const mountRouter = createTRPCRouter({ mount.mongoId || mount.mysqlId || mount.redisId || + mount.libsqlId || mount.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { @@ -125,6 +126,7 @@ export const mountRouter = createTRPCRouter({ mount.mongoId || mount.mysqlId || mount.redisId || + mount.libsqlId || mount.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { @@ -144,6 +146,7 @@ export const mountRouter = createTRPCRouter({ mount.mongoId || mount.mysqlId || mount.redisId || + mount.libsqlId || mount.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { diff --git a/apps/dokploy/server/api/routers/volume-backups.ts b/apps/dokploy/server/api/routers/volume-backups.ts index f9675456b..5b50219d2 100644 --- a/apps/dokploy/server/api/routers/volume-backups.ts +++ b/apps/dokploy/server/api/routers/volume-backups.ts @@ -15,6 +15,7 @@ import { updateVolumeBackupSchema, volumeBackups, } from "@dokploy/server/db/schema"; +import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission"; import { execAsyncRemote, execAsyncStream, @@ -25,7 +26,6 @@ import { desc, eq } from "drizzle-orm"; import { z } from "zod"; import { audit } from "@/server/api/utils/audit"; import { removeJob, schedule, updateJob } from "@/server/utils/backup"; -import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission"; import { createTRPCRouter, protectedProcedure, withPermission } from "../trpc"; export const volumeBackupsRouter = createTRPCRouter({ @@ -41,6 +41,7 @@ export const volumeBackupsRouter = createTRPCRouter({ "mongo", "redis", "compose", + "libsql", ]), }), ) @@ -58,6 +59,7 @@ export const volumeBackupsRouter = createTRPCRouter({ mongo: true, redis: true, compose: true, + libsql: true, }, orderBy: [desc(volumeBackups.createdAt)], }); @@ -72,6 +74,7 @@ export const volumeBackupsRouter = createTRPCRouter({ input.mariadbId || input.mongoId || input.redisId || + input.libsqlId || input.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { @@ -113,6 +116,7 @@ export const volumeBackupsRouter = createTRPCRouter({ vb.mariadbId || vb.mongoId || vb.redisId || + vb.libsqlId || vb.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { @@ -136,6 +140,7 @@ export const volumeBackupsRouter = createTRPCRouter({ vb.mariadbId || vb.mongoId || vb.redisId || + vb.libsqlId || vb.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { @@ -161,6 +166,7 @@ export const volumeBackupsRouter = createTRPCRouter({ existingVb.mariadbId || existingVb.mongoId || existingVb.redisId || + existingVb.libsqlId || existingVb.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { @@ -220,6 +226,7 @@ export const volumeBackupsRouter = createTRPCRouter({ vb.mariadbId || vb.mongoId || vb.redisId || + vb.libsqlId || vb.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { diff --git a/apps/schedules/src/utils.ts b/apps/schedules/src/utils.ts index f09f79be8..d7d2d8eed 100644 --- a/apps/schedules/src/utils.ts +++ b/apps/schedules/src/utils.ts @@ -8,6 +8,7 @@ import { keepLatestNBackups, runCommand, runComposeBackup, + runLibsqlBackup, runMariadbBackup, runMongoBackup, runMySqlBackup, @@ -38,6 +39,7 @@ export const runJobs = async (job: QueueJob) => { mysql, mongo, mariadb, + libsql, compose, backupType, } = backup; @@ -75,6 +77,14 @@ export const runJobs = async (job: QueueJob) => { } await runMariadbBackup(mariadb, backup); await keepLatestNBackups(backup, server.serverId); + } else if (databaseType === "libsql" && libsql) { + const server = await findServerById(libsql.serverId as string); + if (server.serverStatus === "inactive") { + logger.info("Server is inactive"); + return; + } + await runLibsqlBackup(libsql, backup); + await keepLatestNBackups(backup, server.serverId); } } else if (backupType === "compose" && compose) { const server = await findServerById(compose.serverId as string); @@ -141,6 +151,7 @@ export const initializeJobs = async () => { mysql: true, postgres: true, mongo: true, + libsql: true, compose: true, }, }); diff --git a/packages/server/src/db/schema/libsql.ts b/packages/server/src/db/schema/libsql.ts index 5759d6697..94f81e6a8 100644 --- a/packages/server/src/db/schema/libsql.ts +++ b/packages/server/src/db/schema/libsql.ts @@ -1,5 +1,12 @@ import { relations } from "drizzle-orm"; -import { boolean, integer, json, pgTable, text } from "drizzle-orm/pg-core"; +import { + bigint, + boolean, + integer, + json, + pgTable, + text, +} from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { nanoid } from "nanoid"; import { z } from "zod"; @@ -9,6 +16,8 @@ import { mounts } from "./mount"; import { server } from "./server"; import { applicationStatus, + type EndpointSpecSwarm, + EndpointSpecSwarmSchema, type HealthCheckSwarm, HealthCheckSwarmSchema, type LabelsSwarm, @@ -66,6 +75,8 @@ export const libsql = pgTable("libsql", { modeSwarm: json("modeSwarm").$type(), labelsSwarm: json("labelsSwarm").$type(), networkSwarm: json("networkSwarm").$type(), + stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }), + endpointSpecSwarm: json("endpointSpecSwarm").$type(), replicas: integer("replicas").default(1).notNull(), createdAt: text("createdAt") .notNull() @@ -131,6 +142,8 @@ const createSchema = createInsertSchema(libsql, { modeSwarm: ServiceModeSwarmSchema.nullable(), labelsSwarm: LabelsSwarmSchema.nullable(), networkSwarm: NetworkSwarmSchema.nullable(), + stopGracePeriodSwarm: z.bigint().nullable(), + endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(), }); export const apiCreateLibsql = createSchema diff --git a/packages/server/src/utils/volume-backups/backup.ts b/packages/server/src/utils/volume-backups/backup.ts index 79c49c81a..d2827a3f0 100644 --- a/packages/server/src/utils/volume-backups/backup.ts +++ b/packages/server/src/utils/volume-backups/backup.ts @@ -18,7 +18,8 @@ export const getVolumeServiceAppName = ( volumeBackup.mysql?.appName || volumeBackup.mariadb?.appName || volumeBackup.mongo?.appName || - volumeBackup.redis?.appName; + volumeBackup.redis?.appName || + volumeBackup.libsql?.appName; return serviceAppName || volumeBackup.appName; }; diff --git a/packages/server/src/utils/volume-backups/utils.ts b/packages/server/src/utils/volume-backups/utils.ts index 6a51e765d..a1eb0a8f1 100644 --- a/packages/server/src/utils/volume-backups/utils.ts +++ b/packages/server/src/utils/volume-backups/utils.ts @@ -26,6 +26,7 @@ const getProjectName = ( volumeBackup.mariadb, volumeBackup.mongo, volumeBackup.redis, + volumeBackup.libsql, ]; for (const service of services) { @@ -48,6 +49,7 @@ const getOrganizationId = ( volumeBackup.mariadb, volumeBackup.mongo, volumeBackup.redis, + volumeBackup.libsql, ]; for (const service of services) {