@@ -155,7 +149,7 @@ export default function Home() {
Home.getLayout = (page: ReactElement) => {
return
{page};
};
-export async function getServerSideProps(context: GetServerSidePropsContext) {
+export async function getServerSideProps(_context: GetServerSidePropsContext) {
if (!IS_CLOUD) {
return {
redirect: {
diff --git a/apps/dokploy/pages/swagger.tsx b/apps/dokploy/pages/swagger.tsx
index b5deb2ac..11ea0731 100644
--- a/apps/dokploy/pages/swagger.tsx
+++ b/apps/dokploy/pages/swagger.tsx
@@ -30,7 +30,41 @@ const Home: NextPage = () => {
return (
-
+ (args: any) => {
+ const result = ori(args);
+ const apiKey = args?.apiKey?.value;
+ if (apiKey) {
+ localStorage.setItem("swagger_api_key", apiKey);
+ }
+ return result;
+ },
+ logout: (ori: any) => (args: any) => {
+ const result = ori(args);
+ localStorage.removeItem("swagger_api_key");
+ return result;
+ },
+ },
+ },
+ },
+ },
+ ]}
+ requestInterceptor={(request: any) => {
+ const apiKey = localStorage.getItem("swagger_api_key");
+ if (apiKey) {
+ request.headers = request.headers || {};
+ request.headers["x-api-key"] = apiKey;
+ }
+ return request;
+ }}
+ />
);
};
@@ -38,7 +72,7 @@ const Home: NextPage = () => {
export default Home;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req, res } = context;
- const { user, session } = await validateRequest(context.req, context.res);
+ const { user, session } = await validateRequest(context.req);
if (!user) {
return {
redirect: {
@@ -53,17 +87,17 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
req: req as any,
res: res as any,
db: null as any,
- session: session,
- user: user,
+ session: session as any,
+ user: user as any,
},
transformer: superjson,
});
- if (user.rol === "user") {
- const result = await helpers.user.byAuthId.fetch({
- authId: user.id,
+ if (user.role === "member") {
+ const userR = await helpers.user.one.fetch({
+ userId: user.id,
});
- if (!result.canAccessToAPI) {
+ if (!userR?.canAccessToAPI) {
return {
redirect: {
permanent: true,
diff --git a/apps/dokploy/public/templates/appwrite.svg b/apps/dokploy/public/templates/appwrite.svg
new file mode 100644
index 00000000..2034a812
--- /dev/null
+++ b/apps/dokploy/public/templates/appwrite.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/convex.svg b/apps/dokploy/public/templates/convex.svg
new file mode 100644
index 00000000..8622c4c0
--- /dev/null
+++ b/apps/dokploy/public/templates/convex.svg
@@ -0,0 +1,5 @@
+
diff --git a/apps/dokploy/public/templates/linkwarden.png b/apps/dokploy/public/templates/linkwarden.png
new file mode 100644
index 00000000..843f681e
Binary files /dev/null and b/apps/dokploy/public/templates/linkwarden.png differ
diff --git a/apps/dokploy/public/templates/mailpit.svg b/apps/dokploy/public/templates/mailpit.svg
new file mode 100644
index 00000000..58675a26
--- /dev/null
+++ b/apps/dokploy/public/templates/mailpit.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/outline.png b/apps/dokploy/public/templates/outline.png
new file mode 100644
index 00000000..b241f01d
Binary files /dev/null and b/apps/dokploy/public/templates/outline.png differ
diff --git a/apps/dokploy/public/templates/pocket-id.svg b/apps/dokploy/public/templates/pocket-id.svg
new file mode 100644
index 00000000..0ee89b14
--- /dev/null
+++ b/apps/dokploy/public/templates/pocket-id.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/registry.png b/apps/dokploy/public/templates/registry.png
new file mode 100644
index 00000000..39418022
Binary files /dev/null and b/apps/dokploy/public/templates/registry.png differ
diff --git a/apps/dokploy/public/templates/trilium.png b/apps/dokploy/public/templates/trilium.png
new file mode 100644
index 00000000..f6afe82f
Binary files /dev/null and b/apps/dokploy/public/templates/trilium.png differ
diff --git a/apps/dokploy/public/templates/wikijs.svg b/apps/dokploy/public/templates/wikijs.svg
new file mode 100644
index 00000000..78073b23
--- /dev/null
+++ b/apps/dokploy/public/templates/wikijs.svg
@@ -0,0 +1,119 @@
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/reset-password.ts b/apps/dokploy/reset-password.ts
index 43b11fdf..32cab433 100644
--- a/apps/dokploy/reset-password.ts
+++ b/apps/dokploy/reset-password.ts
@@ -1,6 +1,8 @@
import { findAdmin } from "@dokploy/server";
-import { updateAuthById } from "@dokploy/server";
import { generateRandomPassword } from "@dokploy/server";
+import { db } from "@dokploy/server/db";
+import { account } from "@dokploy/server/db/schema";
+import { eq } from "drizzle-orm";
(async () => {
try {
@@ -8,9 +10,12 @@ import { generateRandomPassword } from "@dokploy/server";
const result = await findAdmin();
- const update = await updateAuthById(result.authId, {
- password: randomPassword.hashedPassword,
- });
+ const update = await db
+ .update(account)
+ .set({
+ password: randomPassword.hashedPassword,
+ })
+ .where(eq(account.userId, result.userId));
if (update) {
console.log("Password reset successful");
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts
index 68f5e4e0..5f5d0bcc 100644
--- a/apps/dokploy/server/api/root.ts
+++ b/apps/dokploy/server/api/root.ts
@@ -19,6 +19,7 @@ import { mongoRouter } from "./routers/mongo";
import { mountRouter } from "./routers/mount";
import { mysqlRouter } from "./routers/mysql";
import { notificationRouter } from "./routers/notification";
+import { organizationRouter } from "./routers/organization";
import { portRouter } from "./routers/port";
import { postgresRouter } from "./routers/postgres";
import { previewDeploymentRouter } from "./routers/preview-deployment";
@@ -33,7 +34,6 @@ import { sshRouter } from "./routers/ssh-key";
import { stripeRouter } from "./routers/stripe";
import { swarmRouter } from "./routers/swarm";
import { userRouter } from "./routers/user";
-
/**
* This is the primary router for your server.
*
@@ -75,6 +75,7 @@ export const appRouter = createTRPCRouter({
server: serverRouter,
stripe: stripeRouter,
swarm: swarmRouter,
+ organization: organizationRouter,
});
// export type definition of API
diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts
index 31612fe0..47bd9cd9 100644
--- a/apps/dokploy/server/api/routers/admin.ts
+++ b/apps/dokploy/server/api/routers/admin.ts
@@ -1,119 +1,14 @@
-import { db } from "@/server/db";
-import {
- apiAssignPermissions,
- apiCreateUserInvitation,
- apiFindOneToken,
- apiRemoveUser,
- apiUpdateAdmin,
- apiUpdateWebServerMonitoring,
- users,
-} from "@/server/db/schema";
+import { apiUpdateWebServerMonitoring } from "@/server/db/schema";
import {
IS_CLOUD,
- createInvitation,
- findAdminById,
- findUserByAuthId,
findUserById,
- getUserByToken,
- removeUserByAuthId,
setupWebMonitoring,
- updateAdmin,
- updateAdminById,
+ updateUser,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
-import { eq } from "drizzle-orm";
-import { z } from "zod";
-import {
- adminProcedure,
- createTRPCRouter,
- protectedProcedure,
- publicProcedure,
-} from "../trpc";
+import { adminProcedure, createTRPCRouter } from "../trpc";
export const adminRouter = createTRPCRouter({
- one: adminProcedure.query(async ({ ctx }) => {
- const { sshPrivateKey, ...rest } = await findAdminById(ctx.user.adminId);
- return {
- haveSSH: !!sshPrivateKey,
- ...rest,
- };
- }),
- update: adminProcedure
- .input(apiUpdateAdmin)
- .mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to update this admin",
- });
- }
- const { authId } = await findAdminById(ctx.user.adminId);
- // @ts-ignore
- return updateAdmin(authId, input);
- }),
- createUserInvitation: adminProcedure
- .input(apiCreateUserInvitation)
- .mutation(async ({ input, ctx }) => {
- try {
- await createInvitation(input, ctx.user.adminId);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message:
- "Error creating this user\ncheck if the email is not registered",
- cause: error,
- });
- }
- }),
- removeUser: adminProcedure
- .input(apiRemoveUser)
- .mutation(async ({ input, ctx }) => {
- try {
- const user = await findUserByAuthId(input.authId);
-
- if (user.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to delete this user",
- });
- }
- return await removeUserByAuthId(input.authId);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error deleting this user",
- cause: error,
- });
- }
- }),
- getUserByToken: publicProcedure
- .input(apiFindOneToken)
- .query(async ({ input }) => {
- return await getUserByToken(input.token);
- }),
- assignPermissions: adminProcedure
- .input(apiAssignPermissions)
- .mutation(async ({ input, ctx }) => {
- try {
- const user = await findUserById(input.userId);
-
- if (user.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to assign permissions",
- });
- }
- await db
- .update(users)
- .set({
- ...input,
- })
- .where(eq(users.userId, input.userId));
- } catch (error) {
- throw error;
- }
- }),
-
setupMonitoring: adminProcedure
.input(apiUpdateWebServerMonitoring)
.mutation(async ({ input, ctx }) => {
@@ -124,15 +19,15 @@ export const adminRouter = createTRPCRouter({
message: "Feature disabled on cloud",
});
}
- const admin = await findAdminById(ctx.user.adminId);
- if (admin.adminId !== ctx.user.adminId) {
+ const user = await findUserById(ctx.user.ownerId);
+ if (user.id !== ctx.user.ownerId) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not authorized to setup this server",
+ message: "You are not authorized to setup the monitoring",
});
}
- await updateAdminById(admin.adminId, {
+ await updateUser(user.id, {
metricsConfig: {
server: {
type: "Dokploy",
@@ -156,135 +51,11 @@ export const adminRouter = createTRPCRouter({
},
},
});
- const currentServer = await setupWebMonitoring(admin.adminId);
+
+ const currentServer = await setupWebMonitoring(user.id);
return currentServer;
} catch (error) {
throw error;
}
}),
- getMetricsToken: protectedProcedure.query(async ({ ctx }) => {
- const admin = await findAdminById(ctx.user.adminId);
- return {
- serverIp: admin.serverIp,
- enabledFeatures: admin.enablePaidFeatures,
- metricsConfig: admin?.metricsConfig,
- };
- }),
-
- getServerMetrics: protectedProcedure
- .input(
- z.object({
- url: z.string(),
- token: z.string(),
- dataPoints: z.string(),
- }),
- )
- .query(async ({ ctx, input }) => {
- try {
- const url = new URL(input.url);
- url.searchParams.append("limit", input.dataPoints);
- const response = await fetch(url.toString(), {
- headers: {
- Authorization: `Bearer ${input.token}`,
- },
- });
- if (!response.ok) {
- throw new Error(
- `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`,
- );
- }
-
- const data = await response.json();
- if (!Array.isArray(data) || data.length === 0) {
- throw new Error(
- [
- "No monitoring data available. This could be because:",
- "",
- "1. You don't have setup the monitoring service, you can do in web server section.",
- "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.",
- ].join("\n"),
- );
- }
- return data as {
- cpu: string;
- cpuModel: string;
- cpuCores: number;
- cpuPhysicalCores: number;
- cpuSpeed: number;
- os: string;
- distro: string;
- kernel: string;
- arch: string;
- memUsed: string;
- memUsedGB: string;
- memTotal: string;
- uptime: number;
- diskUsed: string;
- totalDisk: string;
- networkIn: string;
- networkOut: string;
- timestamp: string;
- }[];
- } catch (error) {
- throw error;
- }
- }),
- getContainerMetrics: protectedProcedure
- .input(
- z.object({
- url: z.string(),
- token: z.string(),
- appName: z.string(),
- dataPoints: z.string(),
- }),
- )
- .query(async ({ ctx, input }) => {
- try {
- if (!input.appName) {
- throw new Error(
- [
- "No Application Selected:",
- "",
- "Make Sure to select an application to monitor.",
- ].join("\n"),
- );
- }
- const url = new URL(`${input.url}/metrics/containers`);
- url.searchParams.append("limit", input.dataPoints);
- url.searchParams.append("appName", input.appName);
- const response = await fetch(url.toString(), {
- headers: {
- Authorization: `Bearer ${input.token}`,
- },
- });
- if (!response.ok) {
- throw new Error(
- `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`,
- );
- }
-
- const data = await response.json();
- if (!Array.isArray(data) || data.length === 0) {
- throw new Error(
- [
- `No monitoring data available for "${input.appName}". This could be because:`,
- "",
- "1. The container was recently started - wait a few minutes for data to be collected",
- "2. The container is not running - verify its status",
- "3. The service is not included in your monitoring configuration",
- ].join("\n"),
- );
- }
- return data as {
- containerId: string;
- containerName: string;
- containerImage: string;
- containerLabels: string;
- containerCommand: string;
- containerCreated: string;
- }[];
- } catch (error) {
- throw error;
- }
- }),
});
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index 13b7c80d..e1629b4c 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -60,8 +60,13 @@ export const applicationRouter = createTRPCRouter({
.input(apiCreateApplication)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -72,7 +77,7 @@ export const applicationRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -80,8 +85,12 @@ export const applicationRouter = createTRPCRouter({
}
const newApplication = await createApplication(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newApplication.applicationId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newApplication.applicationId,
+ project.organizationId,
+ );
}
return newApplication;
} catch (error: unknown) {
@@ -98,15 +107,18 @@ export const applicationRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
+ if (ctx.user.rol === "member") {
await checkServiceAccess(
- ctx.user.authId,
+ ctx.user.id,
input.applicationId,
+ ctx.session.activeOrganizationId,
"access",
);
}
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -119,7 +131,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiReloadApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this application",
@@ -144,16 +158,19 @@ export const applicationRouter = createTRPCRouter({
delete: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
+ if (ctx.user.rol === "member") {
await checkServiceAccess(
- ctx.user.authId,
+ ctx.user.id,
input.applicationId,
+ ctx.session.activeOrganizationId,
"delete",
);
}
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this application",
@@ -184,7 +201,7 @@ export const applicationRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return result[0];
@@ -194,7 +211,7 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const service = await findApplicationById(input.applicationId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this application",
@@ -214,7 +231,7 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const service = await findApplicationById(input.applicationId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this application",
@@ -235,7 +252,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to redeploy this application",
@@ -268,7 +287,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariables)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -284,7 +305,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveBuildType)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this build type",
@@ -305,7 +328,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveGithubProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this github provider",
@@ -327,7 +352,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveGitlabProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this gitlab provider",
@@ -351,7 +378,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveBitbucketProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this bitbucket provider",
@@ -373,7 +402,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveDockerProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this docker provider",
@@ -394,7 +425,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveGitProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this git provider",
@@ -415,7 +448,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to mark this application as running",
@@ -427,7 +462,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiUpdateApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this application",
@@ -451,7 +488,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to refresh this application",
@@ -466,7 +505,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this application",
@@ -500,7 +541,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to clean this application",
@@ -513,7 +556,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to read this application",
@@ -548,7 +593,7 @@ export const applicationRouter = createTRPCRouter({
const app = await findApplicationById(input.applicationId as string);
- if (app.project.adminId !== ctx.user.adminId) {
+ if (app.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this application",
@@ -590,7 +635,9 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this application",
@@ -610,7 +657,7 @@ export const applicationRouter = createTRPCRouter({
}),
readAppMonitoring: protectedProcedure
.input(apiFindMonitoringStats)
- .query(async ({ input, ctx }) => {
+ .query(async ({ input }) => {
if (IS_CLOUD) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/api/routers/auth.ts b/apps/dokploy/server/api/routers/auth.ts
index f8bbfa9b..2c7469c3 100644
--- a/apps/dokploy/server/api/routers/auth.ts
+++ b/apps/dokploy/server/api/routers/auth.ts
@@ -1,523 +1,326 @@
-import {
- apiCreateAdmin,
- apiCreateUser,
- apiFindOneAuth,
- apiLogin,
- apiUpdateAuth,
- apiVerify2FA,
- apiVerifyLogin2FA,
- auth,
-} from "@/server/db/schema";
-import { WEBSITE_URL } from "@/server/utils/stripe";
-import {
- type Auth,
- IS_CLOUD,
- createAdmin,
- createUser,
- findAuthByEmail,
- findAuthById,
- generate2FASecret,
- getUserByToken,
- lucia,
- luciaToken,
- removeAdminByAuthId,
- removeUserByAuthId,
- sendDiscordNotification,
- sendEmailNotification,
- updateAuthById,
- validateRequest,
- verify2FA,
-} from "@dokploy/server";
-import { TRPCError } from "@trpc/server";
-import * as bcrypt from "bcrypt";
-import { isBefore } from "date-fns";
-import { eq } from "drizzle-orm";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-import { db } from "../../db";
-import {
- adminProcedure,
- createTRPCRouter,
- protectedProcedure,
- publicProcedure,
-} from "../trpc";
+import { createTRPCRouter } from "../trpc";
export const authRouter = createTRPCRouter({
- createAdmin: publicProcedure
- .input(apiCreateAdmin)
- .mutation(async ({ ctx, input }) => {
- try {
- if (!IS_CLOUD) {
- const admin = await db.query.admins.findFirst({});
- if (admin) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Admin already exists",
- });
- }
- }
- const newAdmin = await createAdmin(input);
-
- if (IS_CLOUD) {
- await sendDiscordNotificationWelcome(newAdmin);
- await sendVerificationEmail(newAdmin.id);
- return {
- status: "success",
- type: "cloud",
- };
- }
- const session = await lucia.createSession(newAdmin.id || "", {});
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
- return {
- status: "success",
- type: "selfhosted",
- };
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- // @ts-ignore
- message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
- cause: error,
- });
- }
- }),
- createUser: publicProcedure
- .input(apiCreateUser)
- .mutation(async ({ ctx, input }) => {
- try {
- const token = await getUserByToken(input.token);
- if (token.isExpired) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid token",
- });
- }
-
- const newUser = await createUser(input);
-
- if (IS_CLOUD) {
- await sendVerificationEmail(token.authId);
- return true;
- }
- const session = await lucia.createSession(newUser?.authId || "", {});
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- cause: error,
- });
- }
- }),
-
- login: publicProcedure.input(apiLogin).mutation(async ({ ctx, input }) => {
- try {
- const auth = await findAuthByEmail(input.email);
-
- const correctPassword = bcrypt.compareSync(
- input.password,
- auth?.password || "",
- );
-
- if (!correctPassword) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Credentials do not match",
- });
- }
-
- if (auth?.confirmationToken && IS_CLOUD) {
- await sendVerificationEmail(auth.id);
- throw new TRPCError({
- code: "BAD_REQUEST",
- message:
- "Email not confirmed, we have sent you a confirmation email please check your inbox.",
- });
- }
-
- if (auth?.is2FAEnabled) {
- return {
- is2FAEnabled: true,
- authId: auth.id,
- };
- }
-
- const session = await lucia.createSession(auth?.id || "", {});
-
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
- return {
- is2FAEnabled: false,
- authId: auth?.id,
- };
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
- cause: error,
- });
- }
- }),
-
- get: protectedProcedure.query(async ({ ctx }) => {
- const auth = await findAuthById(ctx.user.authId);
- return auth;
- }),
-
- logout: protectedProcedure.mutation(async ({ ctx }) => {
- const { req, res } = ctx;
- const { session } = await validateRequest(req, res);
- if (!session) return false;
-
- await lucia.invalidateSession(session.id);
- res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
- return true;
- }),
-
- update: protectedProcedure
- .input(apiUpdateAuth)
- .mutation(async ({ ctx, input }) => {
- const currentAuth = await findAuthByEmail(ctx.user.email);
-
- if (input.currentPassword || input.password) {
- const correctPassword = bcrypt.compareSync(
- input.currentPassword || "",
- currentAuth?.password || "",
- );
- if (!correctPassword) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Current password is incorrect",
- });
- }
- }
- const auth = await updateAuthById(ctx.user.authId, {
- ...(input.email && { email: input.email.toLowerCase() }),
- ...(input.password && {
- password: bcrypt.hashSync(input.password, 10),
- }),
- ...(input.image && { image: input.image }),
- });
-
- return auth;
- }),
- removeSelfAccount: protectedProcedure
- .input(
- z.object({
- password: z.string().min(1),
- }),
- )
- .mutation(async ({ ctx, input }) => {
- if (!IS_CLOUD) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "This feature is only available in the cloud version",
- });
- }
- const currentAuth = await findAuthByEmail(ctx.user.email);
-
- const correctPassword = bcrypt.compareSync(
- input.password,
- currentAuth?.password || "",
- );
-
- if (!correctPassword) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Password is incorrect",
- });
- }
- const { req, res } = ctx;
- const { session } = await validateRequest(req, res);
- if (!session) return false;
-
- await lucia.invalidateSession(session.id);
- res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
-
- if (ctx.user.rol === "admin") {
- await removeAdminByAuthId(ctx.user.authId);
- } else {
- await removeUserByAuthId(ctx.user.authId);
- }
-
- return true;
- }),
-
- generateToken: protectedProcedure.mutation(async ({ ctx, input }) => {
- const auth = await findAuthById(ctx.user.authId);
-
- if (auth.token) {
- await luciaToken.invalidateSession(auth.token);
- }
- const session = await luciaToken.createSession(auth?.id || "", {
- expiresIn: 60 * 60 * 24 * 30,
- });
-
- await updateAuthById(auth.id, {
- token: session.id,
- });
-
- return auth;
- }),
- verifyToken: protectedProcedure.mutation(async () => {
- return true;
- }),
- one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
- const auth = await findAuthById(input.id);
- return auth;
- }),
-
- generate2FASecret: protectedProcedure.query(async ({ ctx }) => {
- return await generate2FASecret(ctx.user.authId);
- }),
- verify2FASetup: protectedProcedure
- .input(apiVerify2FA)
- .mutation(async ({ ctx, input }) => {
- const auth = await findAuthById(ctx.user.authId);
-
- await verify2FA(auth, input.secret, input.pin);
- await updateAuthById(auth.id, {
- is2FAEnabled: true,
- secret: input.secret,
- });
- return auth;
- }),
-
- verifyLogin2FA: publicProcedure
- .input(apiVerifyLogin2FA)
- .mutation(async ({ ctx, input }) => {
- const auth = await findAuthById(input.id);
-
- await verify2FA(auth, auth.secret || "", input.pin);
-
- const session = await lucia.createSession(auth.id, {});
-
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
-
- return true;
- }),
- disable2FA: protectedProcedure.mutation(async ({ ctx }) => {
- const auth = await findAuthById(ctx.user.authId);
- await updateAuthById(auth.id, {
- is2FAEnabled: false,
- secret: null,
- });
- return auth;
- }),
- sendResetPasswordEmail: publicProcedure
- .input(
- z.object({
- email: z.string().min(1).email(),
- }),
- )
- .mutation(async ({ ctx, input }) => {
- if (!IS_CLOUD) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "This feature is only available in the cloud version",
- });
- }
- const authR = await db.query.auth.findFirst({
- where: eq(auth.email, input.email),
- });
- if (!authR) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- const token = nanoid();
- await updateAuthById(authR.id, {
- resetPasswordToken: token,
- // Make resetPassword in 24 hours
- resetPasswordExpiresAt: new Date(
- new Date().getTime() + 24 * 60 * 60 * 1000,
- ).toISOString(),
- });
-
- await sendEmailNotification(
- {
- fromAddress: process.env.SMTP_FROM_ADDRESS!,
- toAddresses: [authR.email],
- smtpServer: process.env.SMTP_SERVER!,
- smtpPort: Number(process.env.SMTP_PORT),
- username: process.env.SMTP_USERNAME!,
- password: process.env.SMTP_PASSWORD!,
- },
- "Reset Password",
- `
- Reset your password by clicking the link below:
- The link will expire in 24 hours.
-
- Reset Password
-
-
- `,
- );
- }),
-
- resetPassword: publicProcedure
- .input(
- z.object({
- resetPasswordToken: z.string().min(1),
- password: z.string().min(1),
- }),
- )
- .mutation(async ({ ctx, input }) => {
- if (!IS_CLOUD) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "This feature is only available in the cloud version",
- });
- }
- const authR = await db.query.auth.findFirst({
- where: eq(auth.resetPasswordToken, input.resetPasswordToken),
- });
-
- if (!authR || authR.resetPasswordExpiresAt === null) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Token not found",
- });
- }
-
- const isExpired = isBefore(
- new Date(authR.resetPasswordExpiresAt),
- new Date(),
- );
-
- if (isExpired) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Token expired",
- });
- }
-
- await updateAuthById(authR.id, {
- resetPasswordExpiresAt: null,
- resetPasswordToken: null,
- password: bcrypt.hashSync(input.password, 10),
- });
-
- return true;
- }),
- confirmEmail: adminProcedure
- .input(
- z.object({
- confirmationToken: z.string().min(1),
- }),
- )
- .mutation(async ({ ctx, input }) => {
- if (!IS_CLOUD) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Functionality not available in cloud version",
- });
- }
- const authR = await db.query.auth.findFirst({
- where: eq(auth.confirmationToken, input.confirmationToken),
- });
- if (!authR || authR.confirmationExpiresAt === null) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Token not found",
- });
- }
- if (authR.confirmationToken !== input.confirmationToken) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Confirmation Token not found",
- });
- }
-
- const isExpired = isBefore(
- new Date(authR.confirmationExpiresAt),
- new Date(),
- );
-
- if (isExpired) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Confirmation Token expired",
- });
- }
- 1;
- await updateAuthById(authR.id, {
- confirmationToken: null,
- confirmationExpiresAt: null,
- });
- return true;
- }),
+ // createAdmin: publicProcedure.mutation(async ({ input }) => {
+ // try {
+ // if (!IS_CLOUD) {
+ // const admin = await db.query.admins.findFirst({});
+ // if (admin) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Admin already exists",
+ // });
+ // }
+ // }
+ // const newAdmin = await createAdmin(input);
+ // if (IS_CLOUD) {
+ // await sendDiscordNotificationWelcome(newAdmin);
+ // await sendVerificationEmail(newAdmin.id);
+ // return {
+ // status: "success",
+ // type: "cloud",
+ // };
+ // }
+ // // const session = await lucia.createSession(newAdmin.id || "", {});
+ // // ctx.res.appendHeader(
+ // // "Set-Cookie",
+ // // lucia.createSessionCookie(session.id).serialize(),
+ // // );
+ // return {
+ // status: "success",
+ // type: "selfhosted",
+ // };
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // // @ts-ignore
+ // message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
+ // cause: error,
+ // });
+ // }
+ // }),
+ // createUser: publicProcedure.mutation(async ({ input }) => {
+ // try {
+ // const _token = await getUserByToken(input.token);
+ // // if (token.isExpired) {
+ // // throw new TRPCError({
+ // // code: "BAD_REQUEST",
+ // // message: "Invalid token",
+ // // });
+ // // }
+ // // const newUser = await createUser(input);
+ // // if (IS_CLOUD) {
+ // // await sendVerificationEmail(token.authId);
+ // // return true;
+ // // }
+ // // const session = await lucia.createSession(newUser?.authId || "", {});
+ // // ctx.res.appendHeader(
+ // // "Set-Cookie",
+ // // lucia.createSessionCookie(session.id).serialize(),
+ // // );
+ // return true;
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Error creating the user",
+ // cause: error,
+ // });
+ // }
+ // }),
+ // login: publicProcedure.mutation(async ({ input }) => {
+ // try {
+ // const auth = await findAuthByEmail(input.email);
+ // const correctPassword = bcrypt.compareSync(
+ // input.password,
+ // auth?.password || "",
+ // );
+ // if (!correctPassword) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Credentials do not match",
+ // });
+ // }
+ // if (auth?.confirmationToken && IS_CLOUD) {
+ // await sendVerificationEmail(auth.id);
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message:
+ // "Email not confirmed, we have sent you a confirmation email please check your inbox.",
+ // });
+ // }
+ // if (auth?.is2FAEnabled) {
+ // return {
+ // is2FAEnabled: true,
+ // authId: auth.id,
+ // };
+ // }
+ // // const session = await lucia.createSession(auth?.id || "", {});
+ // // ctx.res.appendHeader(
+ // // "Set-Cookie",
+ // // lucia.createSessionCookie(session.id).serialize(),
+ // // );
+ // return {
+ // is2FAEnabled: false,
+ // authId: auth?.id,
+ // };
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
+ // cause: error,
+ // });
+ // }
+ // }),
+ // get: protectedProcedure.query(async ({ ctx }) => {
+ // const memberResult = await db.query.member.findFirst({
+ // where: and(
+ // eq(member.userId, ctx.user.id),
+ // eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ // ),
+ // with: {
+ // user: true,
+ // },
+ // });
+ // return memberResult;
+ // }),
+ // logout: protectedProcedure.mutation(async ({ ctx }) => {
+ // const { req } = ctx;
+ // const { session } = await validateRequest(req);
+ // if (!session) return false;
+ // // await lucia.invalidateSession(session.id);
+ // // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
+ // return true;
+ // }),
+ // update: protectedProcedure.mutation(async ({ ctx, input }) => {
+ // const currentAuth = await findAuthByEmail(ctx.user.email);
+ // if (input.currentPassword || input.password) {
+ // const correctPassword = bcrypt.compareSync(
+ // input.currentPassword || "",
+ // currentAuth?.password || "",
+ // );
+ // if (!correctPassword) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Current password is incorrect",
+ // });
+ // }
+ // }
+ // // const auth = await updateAuthById(ctx.user.authId, {
+ // // ...(input.email && { email: input.email.toLowerCase() }),
+ // // ...(input.password && {
+ // // password: bcrypt.hashSync(input.password, 10),
+ // // }),
+ // // ...(input.image && { image: input.image }),
+ // // });
+ // return auth;
+ // }),
+ // removeSelfAccount: protectedProcedure
+ // .input(
+ // z.object({
+ // password: z.string().min(1),
+ // }),
+ // )
+ // .mutation(async ({ ctx, input }) => {
+ // if (!IS_CLOUD) {
+ // throw new TRPCError({
+ // code: "NOT_FOUND",
+ // message: "This feature is only available in the cloud version",
+ // });
+ // }
+ // const currentAuth = await findAuthByEmail(ctx.user.email);
+ // const correctPassword = bcrypt.compareSync(
+ // input.password,
+ // currentAuth?.password || "",
+ // );
+ // if (!correctPassword) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Password is incorrect",
+ // });
+ // }
+ // const { req } = ctx;
+ // const { session } = await validateRequest(req);
+ // if (!session) return false;
+ // // await lucia.invalidateSession(session.id);
+ // // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
+ // // if (ctx.user.rol === "owner") {
+ // // await removeAdminByAuthId(ctx.user.authId);
+ // // } else {
+ // // await removeUserByAuthId(ctx.user.authId);
+ // // }
+ // return true;
+ // }),
+ // generateToken: protectedProcedure.mutation(async ({ ctx }) => {
+ // const auth = await findUserById(ctx.user.id);
+ // console.log(auth);
+ // // if (auth.token) {
+ // // await luciaToken.invalidateSession(auth.token);
+ // // }
+ // // const session = await luciaToken.createSession(auth?.id || "", {
+ // // expiresIn: 60 * 60 * 24 * 30,
+ // // });
+ // // await updateUser(auth.id, {
+ // // token: session.id,
+ // // });
+ // return auth;
+ // }),
+ // verifyToken: protectedProcedure.mutation(async () => {
+ // return true;
+ // }),
+ // one: adminProcedure
+ // .input(z.object({ userId: z.string().min(1) }))
+ // .query(async ({ input }) => {
+ // // TODO: Check if the user is admin or member
+ // const user = await findUserById(input.userId);
+ // return user;
+ // }),
+ // sendResetPasswordEmail: publicProcedure
+ // .input(
+ // z.object({
+ // email: z.string().min(1).email(),
+ // }),
+ // )
+ // .mutation(async ({ input }) => {
+ // if (!IS_CLOUD) {
+ // throw new TRPCError({
+ // code: "NOT_FOUND",
+ // message: "This feature is only available in the cloud version",
+ // });
+ // }
+ // const authR = await db.query.auth.findFirst({
+ // where: eq(auth.email, input.email),
+ // });
+ // if (!authR) {
+ // throw new TRPCError({
+ // code: "NOT_FOUND",
+ // message: "User not found",
+ // });
+ // }
+ // const token = nanoid();
+ // await updateAuthById(authR.id, {
+ // resetPasswordToken: token,
+ // // Make resetPassword in 24 hours
+ // resetPasswordExpiresAt: new Date(
+ // new Date().getTime() + 24 * 60 * 60 * 1000,
+ // ).toISOString(),
+ // });
+ // await sendEmailNotification(
+ // {
+ // fromAddress: process.env.SMTP_FROM_ADDRESS!,
+ // toAddresses: [authR.email],
+ // smtpServer: process.env.SMTP_SERVER!,
+ // smtpPort: Number(process.env.SMTP_PORT),
+ // username: process.env.SMTP_USERNAME!,
+ // password: process.env.SMTP_PASSWORD!,
+ // },
+ // "Reset Password",
+ // `
+ // Reset your password by clicking the link below:
+ // The link will expire in 24 hours.
+ //
+ // Reset Password
+ //
+ // `,
+ // );
+ // }),
});
-export const sendVerificationEmail = async (authId: string) => {
- const token = nanoid();
- const result = await updateAuthById(authId, {
- confirmationToken: token,
- confirmationExpiresAt: new Date(
- new Date().getTime() + 24 * 60 * 60 * 1000,
- ).toISOString(),
- });
+// export const sendVerificationEmail = async (authId: string) => {
+// const token = nanoid();
+// const result = await updateAuthById(authId, {
+// confirmationToken: token,
+// confirmationExpiresAt: new Date(
+// new Date().getTime() + 24 * 60 * 60 * 1000,
+// ).toISOString(),
+// });
- if (!result) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "User not found",
- });
- }
- await sendEmailNotification(
- {
- fromAddress: process.env.SMTP_FROM_ADDRESS || "",
- toAddresses: [result?.email],
- smtpServer: process.env.SMTP_SERVER || "",
- smtpPort: Number(process.env.SMTP_PORT),
- username: process.env.SMTP_USERNAME || "",
- password: process.env.SMTP_PASSWORD || "",
- },
- "Confirm your email | Dokploy",
- `
- Welcome to Dokploy!
- Please confirm your email by clicking the link below:
-
- Confirm Email
-
- `,
- );
+// if (!result) {
+// throw new TRPCError({
+// code: "BAD_REQUEST",
+// message: "User not found",
+// });
+// }
+// await sendEmailNotification(
+// {
+// fromAddress: process.env.SMTP_FROM_ADDRESS || "",
+// toAddresses: [result?.email],
+// smtpServer: process.env.SMTP_SERVER || "",
+// smtpPort: Number(process.env.SMTP_PORT),
+// username: process.env.SMTP_USERNAME || "",
+// password: process.env.SMTP_PASSWORD || "",
+// },
+// "Confirm your email | Dokploy",
+// `
+// Welcome to Dokploy!
+// Please confirm your email by clicking the link below:
+//
+// Confirm Email
+//
+// `,
+// );
- return true;
-};
+// return true;
+// };
-export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
- await sendDiscordNotification(
- {
- webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
- },
- {
- title: "New User Registered",
- color: 0x00ff00,
- fields: [
- {
- name: "Email",
- value: newAdmin.email,
- inline: true,
- },
- ],
- timestamp: newAdmin.createdAt,
- footer: {
- text: "Dokploy User Registration Notification",
- },
- },
- );
-};
+// export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
+// await sendDiscordNotification(
+// {
+// webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
+// },
+// {
+// title: "New User Registered",
+// color: 0x00ff00,
+// fields: [
+// {
+// name: "Email",
+// value: newAdmin.email,
+// inline: true,
+// },
+// ],
+// timestamp: newAdmin.createdAt,
+// footer: {
+// text: "Dokploy User Registration Notification",
+// },
+// },
+// );
+// };
diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts
index 0b8d7ab1..8a7a5f22 100644
--- a/apps/dokploy/server/api/routers/backup.ts
+++ b/apps/dokploy/server/api/routers/backup.ts
@@ -30,7 +30,7 @@ import { TRPCError } from "@trpc/server";
export const backupRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateBackup)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
const newBackup = await createBackup(input);
@@ -74,16 +74,14 @@ export const backupRouter = createTRPCRouter({
});
}
}),
- one: protectedProcedure
- .input(apiFindOneBackup)
- .query(async ({ input, ctx }) => {
- const backup = await findBackupById(input.backupId);
+ one: protectedProcedure.input(apiFindOneBackup).query(async ({ input }) => {
+ const backup = await findBackupById(input.backupId);
- return backup;
- }),
+ return backup;
+ }),
update: protectedProcedure
.input(apiUpdateBackup)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
await updateBackupById(input.backupId, input);
const backup = await findBackupById(input.backupId);
@@ -111,15 +109,17 @@ export const backupRouter = createTRPCRouter({
}
}
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this Backup";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this Backup",
+ message,
});
}
}),
remove: protectedProcedure
.input(apiRemoveBackup)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
const value = await removeBackupById(input.backupId);
if (IS_CLOUD && value) {
@@ -133,10 +133,11 @@ export const backupRouter = createTRPCRouter({
}
return value;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error deleting this Backup";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this Backup",
- cause: error,
+ message,
});
}
}),
@@ -149,11 +150,13 @@ export const backupRouter = createTRPCRouter({
await runPostgresBackup(postgres, backup);
return true;
} catch (error) {
- console.log(error);
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error running manual Postgres backup ";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error running manual Postgres backup ",
- cause: error,
+ message,
});
}
}),
diff --git a/apps/dokploy/server/api/routers/bitbucket.ts b/apps/dokploy/server/api/routers/bitbucket.ts
index c66716d3..fa02be8d 100644
--- a/apps/dokploy/server/api/routers/bitbucket.ts
+++ b/apps/dokploy/server/api/routers/bitbucket.ts
@@ -8,7 +8,6 @@ import {
apiUpdateBitbucket,
} from "@/server/db/schema";
import {
- IS_CLOUD,
createBitbucket,
findBitbucketById,
getBitbucketBranches,
@@ -23,7 +22,7 @@ export const bitbucketRouter = createTRPCRouter({
.input(apiCreateBitbucket)
.mutation(async ({ input, ctx }) => {
try {
- return await createBitbucket(input, ctx.user.adminId);
+ return await createBitbucket(input, ctx.session.activeOrganizationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -37,10 +36,9 @@ export const bitbucketRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -58,12 +56,11 @@ export const bitbucketRouter = createTRPCRouter({
},
});
- if (IS_CLOUD) {
- // TODO: mAyBe a rEfaCtoR 🤫
- result = result.filter(
- (provider) => provider.gitProvider.adminId === ctx.user.adminId,
- );
- }
+ result = result.filter(
+ (provider) =>
+ provider.gitProvider.organizationId ===
+ ctx.session.activeOrganizationId,
+ );
return result;
}),
@@ -72,10 +69,9 @@ export const bitbucketRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -90,10 +86,9 @@ export const bitbucketRouter = createTRPCRouter({
input.bitbucketId || "",
);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -107,10 +102,9 @@ export const bitbucketRouter = createTRPCRouter({
try {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -131,10 +125,9 @@ export const bitbucketRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -142,7 +135,7 @@ export const bitbucketRouter = createTRPCRouter({
}
return await updateBitbucket(input.bitbucketId, {
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
}),
});
diff --git a/apps/dokploy/server/api/routers/certificate.ts b/apps/dokploy/server/api/routers/certificate.ts
index 0f8d6fd9..3dc944ac 100644
--- a/apps/dokploy/server/api/routers/certificate.ts
+++ b/apps/dokploy/server/api/routers/certificate.ts
@@ -25,14 +25,14 @@ export const certificateRouter = createTRPCRouter({
message: "Please set a server to create a certificate",
});
}
- return await createCertificate(input, ctx.user.adminId);
+ return await createCertificate(input, ctx.session.activeOrganizationId);
}),
one: adminProcedure
.input(apiFindCertificate)
.query(async ({ input, ctx }) => {
const certificates = await findCertificateById(input.certificateId);
- if (IS_CLOUD && certificates.adminId !== ctx.user.adminId) {
+ if (certificates.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this certificate",
@@ -44,7 +44,7 @@ export const certificateRouter = createTRPCRouter({
.input(apiFindCertificate)
.mutation(async ({ input, ctx }) => {
const certificates = await findCertificateById(input.certificateId);
- if (IS_CLOUD && certificates.adminId !== ctx.user.adminId) {
+ if (certificates.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this certificate",
@@ -55,8 +55,7 @@ export const certificateRouter = createTRPCRouter({
}),
all: adminProcedure.query(async ({ ctx }) => {
return await db.query.certificates.findMany({
- // TODO: Remove this line when the cloud version is ready
- ...(IS_CLOUD && { where: eq(certificates.adminId, ctx.user.adminId) }),
+ where: eq(certificates.organizationId, ctx.session.activeOrganizationId),
});
}),
});
diff --git a/apps/dokploy/server/api/routers/cluster.ts b/apps/dokploy/server/api/routers/cluster.ts
index 7ded632c..0d840757 100644
--- a/apps/dokploy/server/api/routers/cluster.ts
+++ b/apps/dokploy/server/api/routers/cluster.ts
@@ -40,7 +40,7 @@ export const clusterRouter = createTRPCRouter({
});
}
}),
- addWorker: protectedProcedure.query(async ({ input }) => {
+ addWorker: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return {
command: "",
@@ -57,7 +57,7 @@ export const clusterRouter = createTRPCRouter({
version: docker_version.Version,
};
}),
- addManager: protectedProcedure.query(async ({ input }) => {
+ addManager: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return {
command: "",
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index 463d0398..bae926d0 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -39,11 +39,11 @@ import {
createComposeByTemplate,
createDomain,
createMount,
- findAdminById,
findComposeById,
findDomainsByComposeId,
findProjectById,
findServerById,
+ findUserById,
loadServices,
randomizeComposeFile,
randomizeIsolatedDeploymentComposeFile,
@@ -60,8 +60,13 @@ export const composeRouter = createTRPCRouter({
.input(apiCreateCompose)
.mutation(async ({ ctx, input }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -71,7 +76,7 @@ export const composeRouter = createTRPCRouter({
});
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -79,8 +84,12 @@ export const composeRouter = createTRPCRouter({
}
const newService = await createCompose(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newService.composeId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newService.composeId,
+ project.organizationId,
+ );
}
return newService;
@@ -92,12 +101,17 @@ export const composeRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.composeId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.composeId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -110,7 +124,7 @@ export const composeRouter = createTRPCRouter({
.input(apiUpdateCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this compose",
@@ -121,12 +135,20 @@ export const composeRouter = createTRPCRouter({
delete: protectedProcedure
.input(apiDeleteCompose)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.composeId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.composeId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const composeResult = await findComposeById(input.composeId);
- if (composeResult.project.adminId !== ctx.user.adminId) {
+ if (
+ composeResult.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this compose",
@@ -148,7 +170,7 @@ export const composeRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return result[0];
@@ -157,7 +179,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to clean this compose",
@@ -170,7 +192,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFetchServices)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to load this compose",
@@ -184,7 +206,9 @@ export const composeRouter = createTRPCRouter({
try {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (
+ compose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to fetch this compose",
@@ -209,7 +233,7 @@ export const composeRouter = createTRPCRouter({
.input(apiRandomizeCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to randomize this compose",
@@ -221,7 +245,7 @@ export const composeRouter = createTRPCRouter({
.input(apiRandomizeCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to randomize this compose",
@@ -236,7 +260,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to get this compose",
@@ -254,7 +278,7 @@ export const composeRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this compose",
@@ -287,7 +311,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to redeploy this compose",
@@ -319,7 +343,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this compose",
@@ -333,7 +357,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this compose",
@@ -348,7 +372,7 @@ export const composeRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to get this compose",
@@ -361,7 +385,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to refresh this compose",
@@ -375,8 +399,13 @@ export const composeRouter = createTRPCRouter({
deployTemplate: protectedProcedure
.input(apiCreateComposeByTemplate)
.mutation(async ({ ctx, input }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -390,7 +419,7 @@ export const composeRouter = createTRPCRouter({
const generate = await loadTemplateModule(input.id as TemplatesKeys);
- const admin = await findAdminById(ctx.user.adminId);
+ const admin = await findUserById(ctx.user.ownerId);
let serverIp = admin.serverIp || "127.0.0.1";
const project = await findProjectById(input.projectId);
@@ -418,8 +447,12 @@ export const composeRouter = createTRPCRouter({
isolatedDeployment: true,
});
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, compose.composeId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ compose.composeId,
+ project.organizationId,
+ );
}
if (mounts && mounts?.length > 0) {
@@ -463,7 +496,7 @@ export const composeRouter = createTRPCRouter({
return templatesData;
}),
- getTags: protectedProcedure.query(async ({ input }) => {
+ getTags: protectedProcedure.query(async () => {
const allTags = templates.flatMap((template) => template.tags);
const uniqueTags = _.uniq(allTags);
return uniqueTags;
diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts
index bf981c6d..8d95c121 100644
--- a/apps/dokploy/server/api/routers/deployment.ts
+++ b/apps/dokploy/server/api/routers/deployment.ts
@@ -19,7 +19,9 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -32,7 +34,7 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -44,7 +46,7 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByServer)
.query(async ({ input, ctx }) => {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts
index d13928be..f1d582c5 100644
--- a/apps/dokploy/server/api/routers/destination.ts
+++ b/apps/dokploy/server/api/routers/destination.ts
@@ -28,7 +28,10 @@ export const destinationRouter = createTRPCRouter({
.input(apiCreateDestination)
.mutation(async ({ input, ctx }) => {
try {
- return await createDestintation(input, ctx.user.adminId);
+ return await createDestintation(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -84,7 +87,7 @@ export const destinationRouter = createTRPCRouter({
.input(apiFindOneDestination)
.query(async ({ input, ctx }) => {
const destination = await findDestinationById(input.destinationId);
- if (destination.adminId !== ctx.user.adminId) {
+ if (destination.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this destination",
@@ -94,7 +97,7 @@ export const destinationRouter = createTRPCRouter({
}),
all: protectedProcedure.query(async ({ ctx }) => {
return await db.query.destinations.findMany({
- where: eq(destinations.adminId, ctx.user.adminId),
+ where: eq(destinations.organizationId, ctx.session.activeOrganizationId),
});
}),
remove: adminProcedure
@@ -103,7 +106,7 @@ export const destinationRouter = createTRPCRouter({
try {
const destination = await findDestinationById(input.destinationId);
- if (destination.adminId !== ctx.user.adminId) {
+ if (destination.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this destination",
@@ -111,7 +114,7 @@ export const destinationRouter = createTRPCRouter({
}
return await removeDestinationById(
input.destinationId,
- ctx.user.adminId,
+ ctx.session.activeOrganizationId,
);
} catch (error) {
throw error;
@@ -122,7 +125,7 @@ export const destinationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const destination = await findDestinationById(input.destinationId);
- if (destination.adminId !== ctx.user.adminId) {
+ if (destination.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to update this destination",
@@ -130,7 +133,7 @@ export const destinationRouter = createTRPCRouter({
}
return await updateDestinationById(input.destinationId, {
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw error;
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index f122cf86..aac2a016 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -30,7 +30,9 @@ export const domainRouter = createTRPCRouter({
try {
if (input.domainType === "compose" && input.composeId) {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (
+ compose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -38,7 +40,10 @@ export const domainRouter = createTRPCRouter({
}
} else if (input.domainType === "application" && input.applicationId) {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -58,7 +63,9 @@ export const domainRouter = createTRPCRouter({
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -70,7 +77,7 @@ export const domainRouter = createTRPCRouter({
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -83,7 +90,7 @@ export const domainRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
return generateTraefikMeDomain(
input.appName,
- ctx.user.adminId,
+ ctx.user.ownerId,
input.serverId,
);
}),
@@ -95,7 +102,9 @@ export const domainRouter = createTRPCRouter({
if (currentDomain.applicationId) {
const newApp = await findApplicationById(currentDomain.applicationId);
- if (newApp.project.adminId !== ctx.user.adminId) {
+ if (
+ newApp.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -103,7 +112,9 @@ export const domainRouter = createTRPCRouter({
}
} else if (currentDomain.composeId) {
const newCompose = await findComposeById(currentDomain.composeId);
- if (newCompose.project.adminId !== ctx.user.adminId) {
+ if (
+ newCompose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -114,7 +125,8 @@ export const domainRouter = createTRPCRouter({
currentDomain.previewDeploymentId,
);
if (
- newPreviewDeployment.application.project.adminId !== ctx.user.adminId
+ newPreviewDeployment.application.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -143,7 +155,9 @@ export const domainRouter = createTRPCRouter({
const domain = await findDomainById(input.domainId);
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -151,7 +165,7 @@ export const domainRouter = createTRPCRouter({
}
} else if (domain.composeId) {
const compose = await findComposeById(domain.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -166,7 +180,10 @@ export const domainRouter = createTRPCRouter({
const domain = await findDomainById(input.domainId);
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -174,7 +191,9 @@ export const domainRouter = createTRPCRouter({
}
} else if (domain.composeId) {
const compose = await findComposeById(domain.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (
+ compose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
diff --git a/apps/dokploy/server/api/routers/git-provider.ts b/apps/dokploy/server/api/routers/git-provider.ts
index abd93392..ed37869d 100644
--- a/apps/dokploy/server/api/routers/git-provider.ts
+++ b/apps/dokploy/server/api/routers/git-provider.ts
@@ -1,11 +1,7 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
-import {
- IS_CLOUD,
- findGitProviderById,
- removeGitProvider,
-} from "@dokploy/server";
+import { findGitProviderById, removeGitProvider } from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { desc, eq } from "drizzle-orm";
@@ -18,8 +14,7 @@ export const gitProviderRouter = createTRPCRouter({
github: true,
},
orderBy: desc(gitProvider.createdAt),
- ...(IS_CLOUD && { where: eq(gitProvider.adminId, ctx.user.adminId) }),
- //TODO: Remove this line when the cloud version is ready
+ where: eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
});
}),
remove: protectedProcedure
@@ -28,8 +23,7 @@ export const gitProviderRouter = createTRPCRouter({
try {
const gitProvider = await findGitProviderById(input.gitProviderId);
- if (IS_CLOUD && gitProvider.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (gitProvider.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this Git provider",
@@ -37,9 +31,13 @@ export const gitProviderRouter = createTRPCRouter({
}
return await removeGitProvider(input.gitProviderId);
} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this Git provider";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this Git provider",
+ message,
});
}
}),
diff --git a/apps/dokploy/server/api/routers/github.ts b/apps/dokploy/server/api/routers/github.ts
index 56222577..691030e2 100644
--- a/apps/dokploy/server/api/routers/github.ts
+++ b/apps/dokploy/server/api/routers/github.ts
@@ -6,7 +6,6 @@ import {
apiUpdateGithub,
} from "@/server/db/schema";
import {
- IS_CLOUD,
findGithubById,
getGithubBranches,
getGithubRepositories,
@@ -20,8 +19,10 @@ export const githubRouter = createTRPCRouter({
.input(apiFindOneGithub)
.query(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId);
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -33,8 +34,10 @@ export const githubRouter = createTRPCRouter({
.input(apiFindOneGithub)
.query(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId);
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -46,7 +49,10 @@ export const githubRouter = createTRPCRouter({
.input(apiFindGithubBranches)
.query(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId || "");
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
//TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -62,12 +68,11 @@ export const githubRouter = createTRPCRouter({
},
});
- if (IS_CLOUD) {
- // TODO: mAyBe a rEfaCtoR 🤫
- result = result.filter(
- (provider) => provider.gitProvider.adminId === ctx.user.adminId,
- );
- }
+ result = result.filter(
+ (provider) =>
+ provider.gitProvider.organizationId ===
+ ctx.session.activeOrganizationId,
+ );
const filtered = result
.filter((provider) => haveGithubRequirements(provider))
@@ -89,10 +94,9 @@ export const githubRouter = createTRPCRouter({
try {
const githubProvider = await findGithubById(input.githubId);
if (
- IS_CLOUD &&
- githubProvider.gitProvider.adminId !== ctx.user.adminId
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -111,8 +115,10 @@ export const githubRouter = createTRPCRouter({
.input(apiUpdateGithub)
.mutation(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId);
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -120,7 +126,7 @@ export const githubRouter = createTRPCRouter({
}
await updateGitProvider(input.gitProviderId, {
name: input.name,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
}),
});
diff --git a/apps/dokploy/server/api/routers/gitlab.ts b/apps/dokploy/server/api/routers/gitlab.ts
index 6d35f4a2..daae68a5 100644
--- a/apps/dokploy/server/api/routers/gitlab.ts
+++ b/apps/dokploy/server/api/routers/gitlab.ts
@@ -9,7 +9,6 @@ import {
import { db } from "@/server/db";
import {
- IS_CLOUD,
createGitlab,
findGitlabById,
getGitlabBranches,
@@ -26,7 +25,7 @@ export const gitlabRouter = createTRPCRouter({
.input(apiCreateGitlab)
.mutation(async ({ input, ctx }) => {
try {
- return await createGitlab(input, ctx.user.adminId);
+ return await createGitlab(input, ctx.session.activeOrganizationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -39,8 +38,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiFindOneGitlab)
.query(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId);
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -55,12 +56,11 @@ export const gitlabRouter = createTRPCRouter({
},
});
- if (IS_CLOUD) {
- // TODO: mAyBe a rEfaCtoR 🤫
- result = result.filter(
- (provider) => provider.gitProvider.adminId === ctx.user.adminId,
- );
- }
+ result = result.filter(
+ (provider) =>
+ provider.gitProvider.organizationId ===
+ ctx.session.activeOrganizationId,
+ );
const filtered = result
.filter((provider) => haveGitlabRequirements(provider))
.map((provider) => {
@@ -78,8 +78,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiFindOneGitlab)
.query(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId);
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -92,8 +94,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiFindGitlabBranches)
.query(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId || "");
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -107,10 +111,9 @@ export const gitlabRouter = createTRPCRouter({
try {
const gitlabProvider = await findGitlabById(input.gitlabId || "");
if (
- IS_CLOUD &&
- gitlabProvider.gitProvider.adminId !== ctx.user.adminId
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -130,8 +133,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiUpdateGitlab)
.mutation(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId);
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -140,7 +145,7 @@ export const gitlabRouter = createTRPCRouter({
if (input.name) {
await updateGitProvider(input.gitProviderId, {
name: input.name,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
await updateGitlab(input.gitlabId, {
diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts
index 6e85d274..be0ffd39 100644
--- a/apps/dokploy/server/api/routers/mariadb.ts
+++ b/apps/dokploy/server/api/routers/mariadb.ts
@@ -20,7 +20,6 @@ import {
findBackupsByDbId,
findMariadbById,
findProjectById,
- findServerById,
removeMariadbById,
removeService,
startService,
@@ -37,8 +36,13 @@ export const mariadbRouter = createTRPCRouter({
.input(apiCreateMariaDB)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -49,15 +53,19 @@ export const mariadbRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newMariadb = await createMariadb(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newMariadb.mariadbId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newMariadb.mariadbId,
+ project.organizationId,
+ );
}
await createMount({
@@ -79,11 +87,16 @@ export const mariadbRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneMariaDB)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mariadbId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mariadbId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Mariadb",
@@ -96,7 +109,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiFindOneMariaDB)
.mutation(async ({ input, ctx }) => {
const service = await findMariadbById(input.mariadbId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Mariadb",
@@ -133,7 +146,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiSaveExternalPortMariaDB)
.mutation(async ({ input, ctx }) => {
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -149,7 +162,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiDeployMariaDB)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Mariadb",
@@ -170,7 +183,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiDeployMariaDB)
.subscription(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Mariadb",
@@ -187,7 +200,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiChangeMariaDBStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Mariadb status",
@@ -201,12 +214,17 @@ export const mariadbRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneMariaDB)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mariadbId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mariadbId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Mariadb",
@@ -223,7 +241,7 @@ export const mariadbRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return mongo;
@@ -232,7 +250,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMariaDB)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -255,7 +273,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiResetMariadb)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Mariadb",
@@ -285,7 +303,7 @@ export const mariadbRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mariadbId, ...rest } = input;
const mariadb = await findMariadbById(mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this Mariadb",
diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts
index 2bca3ec5..1c3ba6bb 100644
--- a/apps/dokploy/server/api/routers/mongo.ts
+++ b/apps/dokploy/server/api/routers/mongo.ts
@@ -36,8 +36,13 @@ export const mongoRouter = createTRPCRouter({
.input(apiCreateMongo)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -48,15 +53,19 @@ export const mongoRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newMongo = await createMongo(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newMongo.mongoId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newMongo.mongoId,
+ project.organizationId,
+ );
}
await createMount({
@@ -82,12 +91,17 @@ export const mongoRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneMongo)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mongoId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mongoId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this mongo",
@@ -101,7 +115,7 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const service = await findMongoById(input.mongoId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this mongo",
@@ -124,7 +138,7 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this mongo",
@@ -146,7 +160,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiSaveExternalPortMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -162,7 +176,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiDeployMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this mongo",
@@ -182,7 +196,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiDeployMongo)
.subscription(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this mongo",
@@ -199,7 +213,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiChangeMongoStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this mongo status",
@@ -214,7 +228,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiResetMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this mongo",
@@ -242,13 +256,18 @@ export const mongoRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneMongo)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mongoId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mongoId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this mongo",
@@ -265,7 +284,7 @@ export const mongoRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return mongo;
@@ -274,7 +293,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -298,7 +317,7 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mongoId, ...rest } = input;
const mongo = await findMongoById(mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this mongo",
diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts
index 7ebf4623..594403f2 100644
--- a/apps/dokploy/server/api/routers/mysql.ts
+++ b/apps/dokploy/server/api/routers/mysql.ts
@@ -38,8 +38,13 @@ export const mysqlRouter = createTRPCRouter({
.input(apiCreateMySql)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -50,7 +55,7 @@ export const mysqlRouter = createTRPCRouter({
}
1;
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -58,8 +63,12 @@ export const mysqlRouter = createTRPCRouter({
}
const newMysql = await createMysql(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newMysql.mysqlId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newMysql.mysqlId,
+ project.organizationId,
+ );
}
await createMount({
@@ -85,11 +94,16 @@ export const mysqlRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneMySql)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mysqlId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mysqlId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this MySQL",
@@ -102,7 +116,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
const service = await findMySqlById(input.mysqlId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this MySQL",
@@ -124,7 +138,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this MySQL",
@@ -145,7 +159,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiSaveExternalPortMySql)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -161,7 +175,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiDeployMySql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this MySQL",
@@ -181,7 +195,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiDeployMySql)
.subscription(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this MySQL",
@@ -198,7 +212,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiChangeMySqlStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this MySQL status",
@@ -213,7 +227,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiResetMysql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this MySQL",
@@ -240,11 +254,16 @@ export const mysqlRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mysqlId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mysqlId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this MySQL",
@@ -261,7 +280,7 @@ export const mysqlRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return mongo;
@@ -270,7 +289,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMySql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -294,7 +313,7 @@ export const mysqlRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mysqlId, ...rest } = input;
const mysql = await findMySqlById(mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this MySQL",
diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts
index ea346973..23283d97 100644
--- a/apps/dokploy/server/api/routers/notification.ts
+++ b/apps/dokploy/server/api/routers/notification.ts
@@ -6,7 +6,6 @@ import {
} from "@/server/api/trpc";
import { db } from "@/server/db";
import {
- admins,
apiCreateDiscord,
apiCreateEmail,
apiCreateGotify,
@@ -25,6 +24,7 @@ import {
apiUpdateTelegram,
notifications,
server,
+ users_temp,
} from "@/server/db/schema";
import {
IS_CLOUD,
@@ -51,13 +51,15 @@ import { TRPCError } from "@trpc/server";
import { desc, eq, sql } from "drizzle-orm";
import { z } from "zod";
-// TODO: Uncomment the validations when is cloud ready
export const notificationRouter = createTRPCRouter({
createSlack: adminProcedure
.input(apiCreateSlack)
.mutation(async ({ input, ctx }) => {
try {
- return await createSlackNotification(input, ctx.user.adminId);
+ return await createSlackNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -71,8 +73,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -80,7 +81,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateSlackNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw error;
@@ -107,7 +108,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateTelegram)
.mutation(async ({ input, ctx }) => {
try {
- return await createTelegramNotification(input, ctx.user.adminId);
+ return await createTelegramNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -122,8 +126,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -131,7 +134,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateTelegramNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -159,7 +162,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateDiscord)
.mutation(async ({ input, ctx }) => {
try {
- return await createDiscordNotification(input, ctx.user.adminId);
+ return await createDiscordNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -174,8 +180,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -183,7 +188,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateDiscordNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -220,7 +225,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateEmail)
.mutation(async ({ input, ctx }) => {
try {
- return await createEmailNotification(input, ctx.user.adminId);
+ return await createEmailNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -234,8 +242,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -243,7 +250,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateEmailNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -276,8 +283,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this notification",
@@ -285,9 +291,13 @@ export const notificationRouter = createTRPCRouter({
}
return await removeNotificationById(input.notificationId);
} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this notification";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this notification",
+ message,
});
}
}),
@@ -295,8 +305,7 @@ export const notificationRouter = createTRPCRouter({
.input(apiFindOneNotification)
.query(async ({ input, ctx }) => {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this notification",
@@ -314,8 +323,7 @@ export const notificationRouter = createTRPCRouter({
gotify: true,
},
orderBy: desc(notifications.createdAt),
- ...(IS_CLOUD && { where: eq(notifications.adminId, ctx.user.adminId) }),
- // TODO: Remove this line when the cloud version is ready
+ where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
});
}),
receiveNotification: publicProcedure
@@ -332,24 +340,24 @@ export const notificationRouter = createTRPCRouter({
)
.mutation(async ({ input }) => {
try {
- let adminId = "";
+ let organizationId = "";
let ServerName = "";
if (input.ServerType === "Dokploy") {
const result = await db
.select()
- .from(admins)
+ .from(users_temp)
.where(
- sql`${admins.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
+ sql`${users_temp.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
);
- if (!result?.[0]?.adminId) {
+ if (!result?.[0]?.id) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Token not found",
});
}
- adminId = result?.[0]?.adminId;
+ organizationId = result?.[0]?.id;
ServerName = "Dokploy";
} else {
const result = await db
@@ -359,18 +367,18 @@ export const notificationRouter = createTRPCRouter({
sql`${server.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
);
- if (!result?.[0]?.adminId) {
+ if (!result?.[0]?.organizationId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Token not found",
});
}
- adminId = result?.[0]?.adminId;
+ organizationId = result?.[0]?.organizationId;
ServerName = "Remote";
}
- await sendServerThresholdNotifications(adminId, {
+ await sendServerThresholdNotifications(organizationId, {
...input,
ServerName,
});
@@ -386,7 +394,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateGotify)
.mutation(async ({ input, ctx }) => {
try {
- return await createGotifyNotification(input, ctx.user.adminId);
+ return await createGotifyNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -400,7 +411,10 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -408,7 +422,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateGotifyNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw error;
diff --git a/apps/dokploy/server/api/routers/organization.ts b/apps/dokploy/server/api/routers/organization.ts
new file mode 100644
index 00000000..6f7a9c67
--- /dev/null
+++ b/apps/dokploy/server/api/routers/organization.ts
@@ -0,0 +1,148 @@
+import { db } from "@/server/db";
+import { invitation, member, organization } from "@/server/db/schema";
+import { IS_CLOUD } from "@dokploy/server/index";
+import { TRPCError } from "@trpc/server";
+import { and, desc, eq, exists } from "drizzle-orm";
+import { nanoid } from "nanoid";
+import { z } from "zod";
+import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
+export const organizationRouter = createTRPCRouter({
+ create: protectedProcedure
+ .input(
+ z.object({
+ name: z.string(),
+ logo: z.string().optional(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ if (ctx.user.rol !== "owner" && !IS_CLOUD) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can create an organization",
+ });
+ }
+ const result = await db
+ .insert(organization)
+ .values({
+ ...input,
+ slug: nanoid(),
+ createdAt: new Date(),
+ ownerId: ctx.user.id,
+ })
+ .returning()
+ .then((res) => res[0]);
+
+ console.log("result", result);
+
+ if (!result) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to create organization",
+ });
+ }
+
+ await db.insert(member).values({
+ organizationId: result.id,
+ role: "owner",
+ createdAt: new Date(),
+ userId: ctx.user.id,
+ });
+ return result;
+ }),
+ all: protectedProcedure.query(async ({ ctx }) => {
+ const memberResult = await db.query.organization.findMany({
+ where: (organization) =>
+ exists(
+ db
+ .select()
+ .from(member)
+ .where(
+ and(
+ eq(member.organizationId, organization.id),
+ eq(member.userId, ctx.user.id),
+ ),
+ ),
+ ),
+ });
+ return memberResult;
+ }),
+ one: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ return await db.query.organization.findFirst({
+ where: eq(organization.id, input.organizationId),
+ });
+ }),
+ update: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ name: z.string(),
+ logo: z.string().optional(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ if (ctx.user.rol !== "owner" && !IS_CLOUD) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can update it",
+ });
+ }
+ const result = await db
+ .update(organization)
+ .set({
+ name: input.name,
+ logo: input.logo,
+ })
+ .where(eq(organization.id, input.organizationId))
+ .returning();
+ return result[0];
+ }),
+ delete: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ if (ctx.user.rol !== "owner" && !IS_CLOUD) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can delete it",
+ });
+ }
+ const org = await db.query.organization.findFirst({
+ where: eq(organization.id, input.organizationId),
+ });
+
+ if (!org) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Organization not found",
+ });
+ }
+
+ if (org.ownerId !== ctx.user.id) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can delete it",
+ });
+ }
+
+ const result = await db
+ .delete(organization)
+ .where(eq(organization.id, input.organizationId));
+
+ return result;
+ }),
+ allInvitations: adminProcedure.query(async ({ ctx }) => {
+ return await db.query.invitation.findMany({
+ where: eq(invitation.organizationId, ctx.session.activeOrganizationId),
+ orderBy: [desc(invitation.status), desc(invitation.expiresAt)],
+ });
+ }),
+});
diff --git a/apps/dokploy/server/api/routers/port.ts b/apps/dokploy/server/api/routers/port.ts
index bfbc9863..923fea57 100644
--- a/apps/dokploy/server/api/routers/port.ts
+++ b/apps/dokploy/server/api/routers/port.ts
@@ -44,9 +44,11 @@ export const portRouter = createTRPCRouter({
try {
return removePortById(input.portId);
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error input: Deleting port";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error input: Deleting port",
+ message,
});
}
}),
@@ -56,9 +58,11 @@ export const portRouter = createTRPCRouter({
try {
return updatePortById(input.portId, input);
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating the port";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating the port",
+ message,
});
}
}),
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index 92603a61..cf3221b4 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -1,9 +1,4 @@
-import { EventEmitter } from "node:events";
-import {
- createTRPCRouter,
- protectedProcedure,
- publicProcedure,
-} from "@/server/api/trpc";
+import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangePostgresStatus,
apiCreatePostgres,
@@ -35,17 +30,19 @@ import {
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
-import { z } from "zod";
-
-const ee = new EventEmitter();
export const postgresRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePostgres)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -56,15 +53,19 @@ export const postgresRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newPostgres = await createPostgres(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newPostgres.postgresId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newPostgres.postgresId,
+ project.organizationId,
+ );
}
await createMount({
@@ -90,12 +91,19 @@ export const postgresRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOnePostgres)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.postgresId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.postgresId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Postgres",
@@ -109,7 +117,7 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const service = await findPostgresById(input.postgresId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Postgres",
@@ -131,7 +139,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiFindOnePostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this Postgres",
@@ -153,7 +163,9 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -169,7 +181,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiDeployPostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Postgres",
@@ -190,7 +204,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiDeployPostgres)
.subscription(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Postgres",
@@ -207,7 +223,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiChangePostgresStatus)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Postgres status",
@@ -221,12 +239,19 @@ export const postgresRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOnePostgres)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.postgresId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.postgresId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Postgres",
@@ -249,7 +274,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesPostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -272,7 +299,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiResetPostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Postgres",
@@ -302,7 +331,9 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { postgresId, ...rest } = input;
const postgres = await findPostgresById(postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this Postgres",
diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts
index 74b8461a..f833e9f9 100644
--- a/apps/dokploy/server/api/routers/preview-deployment.ts
+++ b/apps/dokploy/server/api/routers/preview-deployment.ts
@@ -14,7 +14,9 @@ export const previewDeploymentRouter = createTRPCRouter({
.input(apiFindAllByApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -28,7 +30,10 @@ export const previewDeploymentRouter = createTRPCRouter({
const previewDeployment = await findPreviewDeploymentById(
input.previewDeploymentId,
);
- if (previewDeployment.application.project.adminId !== ctx.user.adminId) {
+ if (
+ previewDeployment.application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this preview deployment",
@@ -43,7 +48,10 @@ export const previewDeploymentRouter = createTRPCRouter({
const previewDeployment = await findPreviewDeploymentById(
input.previewDeploymentId,
);
- if (previewDeployment.application.project.adminId !== ctx.user.adminId) {
+ if (
+ previewDeployment.application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this preview deployment",
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 35b2669b..438a3f07 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -15,32 +15,34 @@ import {
redis,
} from "@/server/db/schema";
-import { TRPCError } from "@trpc/server";
-import { and, desc, eq, sql } from "drizzle-orm";
-import type { AnyPgColumn } from "drizzle-orm/pg-core";
-
import {
IS_CLOUD,
addNewProject,
checkProjectAccess,
createProject,
deleteProject,
- findAdminById,
+ findMemberById,
findProjectById,
- findUserByAuthId,
+ findUserById,
updateProjectById,
} from "@dokploy/server";
-
+import { TRPCError } from "@trpc/server";
+import { and, desc, eq, sql } from "drizzle-orm";
+import type { AnyPgColumn } from "drizzle-orm/pg-core";
export const projectRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateProject)
.mutation(async ({ ctx, input }) => {
try {
- if (ctx.user.rol === "user") {
- await checkProjectAccess(ctx.user.authId, "create");
+ if (ctx.user.rol === "member") {
+ await checkProjectAccess(
+ ctx.user.id,
+ "create",
+ ctx.session.activeOrganizationId,
+ );
}
- const admin = await findAdminById(ctx.user.adminId);
+ const admin = await findUserById(ctx.user.ownerId);
if (admin.serversQuantity === 0 && IS_CLOUD) {
throw new TRPCError({
@@ -49,9 +51,16 @@ export const projectRouter = createTRPCRouter({
});
}
- const project = await createProject(input, ctx.user.adminId);
- if (ctx.user.rol === "user") {
- await addNewProject(ctx.user.authId, project.projectId);
+ const project = await createProject(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ if (ctx.user.rol === "member") {
+ await addNewProject(
+ ctx.user.id,
+ project.projectId,
+ ctx.session.activeOrganizationId,
+ );
}
return project;
@@ -67,15 +76,23 @@ export const projectRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneProject)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- const { accessedServices } = await findUserByAuthId(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const { accessedServices } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
- await checkProjectAccess(ctx.user.authId, "access", input.projectId);
+ 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.adminId, ctx.user.adminId),
+ eq(projects.organizationId, ctx.session.activeOrganizationId),
),
with: {
compose: {
@@ -115,7 +132,7 @@ export const projectRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -124,9 +141,11 @@ export const projectRouter = createTRPCRouter({
return project;
}),
all: protectedProcedure.query(async ({ ctx }) => {
- if (ctx.user.rol === "user") {
- const { accessedProjects, accessedServices } = await findUserByAuthId(
- ctx.user.authId,
+ // console.log(ctx.user);
+ if (ctx.user.rol === "member") {
+ const { accessedProjects, accessedServices } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
);
if (accessedProjects.length === 0) {
@@ -139,7 +158,7 @@ export const projectRouter = createTRPCRouter({
accessedProjects.map((projectId) => sql`${projectId}`),
sql`, `,
)})`,
- eq(projects.adminId, ctx.user.adminId),
+ eq(projects.organizationId, ctx.session.activeOrganizationId),
),
with: {
applications: {
@@ -193,7 +212,7 @@ export const projectRouter = createTRPCRouter({
},
},
},
- where: eq(projects.adminId, ctx.user.adminId),
+ where: eq(projects.organizationId, ctx.session.activeOrganizationId),
orderBy: desc(projects.createdAt),
});
}),
@@ -202,11 +221,17 @@ export const projectRouter = createTRPCRouter({
.input(apiRemoveProject)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkProjectAccess(ctx.user.authId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkProjectAccess(
+ ctx.user.id,
+ "delete",
+ ctx.session.activeOrganizationId,
+ );
}
const currentProject = await findProjectById(input.projectId);
- if (currentProject.adminId !== ctx.user.adminId) {
+ if (
+ currentProject.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this project",
@@ -224,7 +249,9 @@ export const projectRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const currentProject = await findProjectById(input.projectId);
- if (currentProject.adminId !== ctx.user.adminId) {
+ if (
+ currentProject.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this project",
diff --git a/apps/dokploy/server/api/routers/redirects.ts b/apps/dokploy/server/api/routers/redirects.ts
index bcd7962a..2d520cc4 100644
--- a/apps/dokploy/server/api/routers/redirects.ts
+++ b/apps/dokploy/server/api/routers/redirects.ts
@@ -18,7 +18,9 @@ export const redirectsRouter = createTRPCRouter({
.input(apiCreateRedirect)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -31,7 +33,9 @@ export const redirectsRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -44,7 +48,9 @@ export const redirectsRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -57,7 +63,9 @@ export const redirectsRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts
index 967fb51a..a80660bf 100644
--- a/apps/dokploy/server/api/routers/redis.ts
+++ b/apps/dokploy/server/api/routers/redis.ts
@@ -36,8 +36,13 @@ export const redisRouter = createTRPCRouter({
.input(apiCreateRedis)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -48,15 +53,19 @@ export const redisRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newRedis = await createRedis(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newRedis.redisId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newRedis.redisId,
+ project.organizationId,
+ );
}
await createMount({
@@ -75,12 +84,17 @@ export const redisRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneRedis)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.redisId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.redisId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Redis",
@@ -93,7 +107,7 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Redis",
@@ -115,7 +129,7 @@ export const redisRouter = createTRPCRouter({
.input(apiResetRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Redis",
@@ -145,7 +159,7 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this Redis",
@@ -166,7 +180,7 @@ export const redisRouter = createTRPCRouter({
.input(apiSaveExternalPortRedis)
.mutation(async ({ input, ctx }) => {
const mongo = await findRedisById(input.redisId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -182,7 +196,7 @@ export const redisRouter = createTRPCRouter({
.input(apiDeployRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Redis",
@@ -202,7 +216,7 @@ export const redisRouter = createTRPCRouter({
.input(apiDeployRedis)
.subscription(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Redis",
@@ -218,7 +232,7 @@ export const redisRouter = createTRPCRouter({
.input(apiChangeRedisStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findRedisById(input.redisId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Redis status",
@@ -232,13 +246,18 @@ export const redisRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.redisId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.redisId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Redis",
@@ -252,7 +271,7 @@ export const redisRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return redis;
@@ -261,7 +280,7 @@ export const redisRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
diff --git a/apps/dokploy/server/api/routers/registry.ts b/apps/dokploy/server/api/routers/registry.ts
index f66ed4ae..a9a6be89 100644
--- a/apps/dokploy/server/api/routers/registry.ts
+++ b/apps/dokploy/server/api/routers/registry.ts
@@ -1,34 +1,35 @@
+import { db } from "@/server/db";
import {
apiCreateRegistry,
apiFindOneRegistry,
apiRemoveRegistry,
apiTestRegistry,
apiUpdateRegistry,
+ registry,
} from "@/server/db/schema";
import {
IS_CLOUD,
createRegistry,
execAsync,
execAsyncRemote,
- findAllRegistryByAdminId,
findRegistryById,
removeRegistry,
updateRegistry,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
+import { eq } from "drizzle-orm";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
-
export const registryRouter = createTRPCRouter({
create: adminProcedure
.input(apiCreateRegistry)
.mutation(async ({ ctx, input }) => {
- return await createRegistry(input, ctx.user.adminId);
+ return await createRegistry(input, ctx.session.activeOrganizationId);
}),
remove: adminProcedure
.input(apiRemoveRegistry)
.mutation(async ({ ctx, input }) => {
const registry = await findRegistryById(input.registryId);
- if (registry.adminId !== ctx.user.adminId) {
+ if (registry.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this registry",
@@ -41,7 +42,7 @@ export const registryRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { registryId, ...rest } = input;
const registry = await findRegistryById(registryId);
- if (registry.adminId !== ctx.user.adminId) {
+ if (registry.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to update this registry",
@@ -61,13 +62,16 @@ export const registryRouter = createTRPCRouter({
return true;
}),
all: protectedProcedure.query(async ({ ctx }) => {
- return await findAllRegistryByAdminId(ctx.user.adminId);
+ const registryResponse = await db.query.registry.findMany({
+ where: eq(registry.organizationId, ctx.session.activeOrganizationId),
+ });
+ return registryResponse;
}),
one: adminProcedure
.input(apiFindOneRegistry)
.query(async ({ input, ctx }) => {
const registry = await findRegistryById(input.registryId);
- if (registry.adminId !== ctx.user.adminId) {
+ if (registry.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this registry",
diff --git a/apps/dokploy/server/api/routers/security.ts b/apps/dokploy/server/api/routers/security.ts
index 5318a293..b8e70bbb 100644
--- a/apps/dokploy/server/api/routers/security.ts
+++ b/apps/dokploy/server/api/routers/security.ts
@@ -18,7 +18,9 @@ export const securityRouter = createTRPCRouter({
.input(apiCreateSecurity)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -31,7 +33,9 @@ export const securityRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -44,7 +48,9 @@ export const securityRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -57,7 +63,9 @@ export const securityRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts
index 0094b675..1a9ebc0a 100644
--- a/apps/dokploy/server/api/routers/server.ts
+++ b/apps/dokploy/server/api/routers/server.ts
@@ -12,6 +12,7 @@ import {
mariadb,
mongo,
mysql,
+ organization,
postgres,
redis,
server,
@@ -21,9 +22,9 @@ import {
createServer,
defaultCommand,
deleteServer,
- findAdminById,
findServerById,
- findServersByAdminId,
+ findServersByUserId,
+ findUserById,
getPublicIpWithFallback,
haveActiveServices,
removeDeploymentsByServerId,
@@ -36,21 +37,25 @@ import {
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
+import { z } from "zod";
export const serverRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateServer)
.mutation(async ({ ctx, input }) => {
try {
- const admin = await findAdminById(ctx.user.adminId);
- const servers = await findServersByAdminId(admin.adminId);
- if (IS_CLOUD && servers.length >= admin.serversQuantity) {
+ const user = await findUserById(ctx.user.ownerId);
+ const servers = await findServersByUserId(user.id);
+ if (IS_CLOUD && servers.length >= user.serversQuantity) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "You cannot create more servers",
});
}
- const project = await createServer(input, ctx.user.adminId);
+ const project = await createServer(
+ input,
+ ctx.session.activeOrganizationId,
+ );
return project;
} catch (error) {
throw new TRPCError({
@@ -65,7 +70,7 @@ export const serverRouter = createTRPCRouter({
.input(apiFindOneServer)
.query(async ({ input, ctx }) => {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
@@ -76,7 +81,7 @@ export const serverRouter = createTRPCRouter({
}),
getDefaultCommand: protectedProcedure
.input(apiFindOneServer)
- .query(async ({ input, ctx }) => {
+ .query(async () => {
return defaultCommand();
}),
all: protectedProcedure.query(async ({ ctx }) => {
@@ -93,22 +98,37 @@ export const serverRouter = createTRPCRouter({
.leftJoin(mongo, eq(mongo.serverId, server.serverId))
.leftJoin(mysql, eq(mysql.serverId, server.serverId))
.leftJoin(postgres, eq(postgres.serverId, server.serverId))
- .where(eq(server.adminId, ctx.user.adminId))
+ .where(eq(server.organizationId, ctx.session.activeOrganizationId))
.orderBy(desc(server.createdAt))
.groupBy(server.serverId);
return result;
}),
+ count: protectedProcedure.query(async ({ ctx }) => {
+ const organizations = await db.query.organization.findMany({
+ where: eq(organization.ownerId, ctx.user.id),
+ with: {
+ servers: true,
+ },
+ });
+
+ const servers = organizations.flatMap((org) => org.servers);
+
+ return servers.length ?? 0;
+ }),
withSSHKey: protectedProcedure.query(async ({ ctx }) => {
const result = await db.query.server.findMany({
orderBy: desc(server.createdAt),
where: IS_CLOUD
? and(
isNotNull(server.sshKeyId),
- eq(server.adminId, ctx.user.adminId),
+ eq(server.organizationId, ctx.session.activeOrganizationId),
eq(server.serverStatus, "active"),
)
- : and(isNotNull(server.sshKeyId), eq(server.adminId, ctx.user.adminId)),
+ : and(
+ isNotNull(server.sshKeyId),
+ eq(server.organizationId, ctx.session.activeOrganizationId),
+ ),
});
return result;
}),
@@ -117,7 +137,7 @@ export const serverRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to setup this server",
@@ -142,7 +162,7 @@ export const serverRouter = createTRPCRouter({
.subscription(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to setup this server",
@@ -162,7 +182,7 @@ export const serverRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to validate this server",
@@ -204,7 +224,7 @@ export const serverRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to validate this server",
@@ -254,7 +274,7 @@ export const serverRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to setup this server",
@@ -296,7 +316,7 @@ export const serverRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this server",
@@ -315,12 +335,9 @@ export const serverRouter = createTRPCRouter({
await deleteServer(input.serverId);
if (IS_CLOUD) {
- const admin = await findAdminById(ctx.user.adminId);
+ const admin = await findUserById(ctx.user.ownerId);
- await updateServersBasedOnQuantity(
- admin.adminId,
- admin.serversQuantity,
- );
+ await updateServersBasedOnQuantity(admin.id, admin.serversQuantity);
}
return currentServer;
@@ -333,7 +350,7 @@ export const serverRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this server",
@@ -355,11 +372,69 @@ export const serverRouter = createTRPCRouter({
throw error;
}
}),
- publicIp: protectedProcedure.query(async ({ ctx }) => {
+ publicIp: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return "";
}
const ip = await getPublicIpWithFallback();
return ip;
}),
+ getServerMetrics: protectedProcedure
+ .input(
+ z.object({
+ url: z.string(),
+ token: z.string(),
+ dataPoints: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ try {
+ const url = new URL(input.url);
+ url.searchParams.append("limit", input.dataPoints);
+ const response = await fetch(url.toString(), {
+ headers: {
+ Authorization: `Bearer ${input.token}`,
+ },
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`,
+ );
+ }
+
+ const data = await response.json();
+ if (!Array.isArray(data) || data.length === 0) {
+ throw new Error(
+ [
+ "No monitoring data available. This could be because:",
+ "",
+ "1. You don't have setup the monitoring service, you can do in web server section.",
+ "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.",
+ ].join("\n"),
+ );
+ }
+ return data as {
+ cpu: string;
+ cpuModel: string;
+ cpuCores: number;
+ cpuPhysicalCores: number;
+ cpuSpeed: number;
+ os: string;
+ distro: string;
+ kernel: string;
+ arch: string;
+ memUsed: string;
+ memUsedGB: string;
+ memTotal: string;
+ uptime: number;
+ diskUsed: string;
+ totalDisk: string;
+ networkIn: string;
+ networkOut: string;
+ timestamp: string;
+ }[];
+ } catch (error) {
+ throw error;
+ }
+ }),
});
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index cb0e32d9..fc1255fc 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -22,9 +22,8 @@ import {
cleanUpUnusedVolumes,
execAsync,
execAsyncRemote,
- findAdmin,
- findAdminById,
findServerById,
+ findUserById,
getDokployImage,
getDokployImageTag,
getUpdateData,
@@ -47,10 +46,10 @@ import {
startServiceRemote,
stopService,
stopServiceRemote,
- updateAdmin,
updateLetsEncryptEmail,
updateServerById,
updateServerTraefik,
+ updateUser,
writeConfig,
writeMainConfig,
writeTraefikConfigInPath,
@@ -164,7 +163,7 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return true;
}
- await updateAdmin(ctx.user.authId, {
+ await updateUser(ctx.user.id, {
sshPrivateKey: input.sshPrivateKey,
});
@@ -176,7 +175,7 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return true;
}
- const admin = await updateAdmin(ctx.user.authId, {
+ const user = await updateUser(ctx.user.id, {
host: input.host,
...(input.letsEncryptEmail && {
letsEncryptEmail: input.letsEncryptEmail,
@@ -184,25 +183,25 @@ export const settingsRouter = createTRPCRouter({
certificateType: input.certificateType,
});
- if (!admin) {
+ if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
- message: "Admin not found",
+ message: "User not found",
});
}
- updateServerTraefik(admin, input.host);
+ updateServerTraefik(user, input.host);
if (input.letsEncryptEmail) {
updateLetsEncryptEmail(input.letsEncryptEmail);
}
- return admin;
+ return user;
}),
cleanSSHPrivateKey: adminProcedure.mutation(async ({ ctx }) => {
if (IS_CLOUD) {
return true;
}
- await updateAdmin(ctx.user.authId, {
+ await updateUser(ctx.user.id, {
sshPrivateKey: null,
});
return true;
@@ -217,7 +216,7 @@ export const settingsRouter = createTRPCRouter({
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
@@ -246,7 +245,7 @@ export const settingsRouter = createTRPCRouter({
await cleanUpUnusedImages(server.serverId);
await cleanUpDockerBuilder(server.serverId);
await cleanUpSystemPrune(server.serverId);
- await sendDockerCleanupNotifications(server.adminId);
+ await sendDockerCleanupNotifications(server.organizationId);
});
}
} else {
@@ -262,19 +261,11 @@ export const settingsRouter = createTRPCRouter({
}
}
} else if (!IS_CLOUD) {
- const admin = await findAdminById(ctx.user.adminId);
-
- if (admin.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to access this admin",
- });
- }
- const adminUpdated = await updateAdmin(ctx.user.authId, {
+ const userUpdated = await updateUser(ctx.user.id, {
enableDockerCleanup: input.enableDockerCleanup,
});
- if (adminUpdated?.enableDockerCleanup) {
+ if (userUpdated?.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running...`,
@@ -282,7 +273,9 @@ export const settingsRouter = createTRPCRouter({
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
- await sendDockerCleanupNotifications(admin.adminId);
+ await sendDockerCleanupNotifications(
+ ctx.session.activeOrganizationId,
+ );
});
} else {
const currentJob = scheduledJobs["docker-cleanup"];
@@ -383,8 +376,11 @@ export const settingsRouter = createTRPCRouter({
.input(apiServerSchema)
.query(async ({ ctx, input }) => {
try {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const canAccess = await canAccessToTraefikFiles(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -401,8 +397,11 @@ export const settingsRouter = createTRPCRouter({
updateTraefikFile: protectedProcedure
.input(apiModifyTraefikConfig)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const canAccess = await canAccessToTraefikFiles(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -419,8 +418,11 @@ export const settingsRouter = createTRPCRouter({
readTraefikFile: protectedProcedure
.input(apiReadTraefikConfig)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const canAccess = await canAccessToTraefikFiles(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -428,12 +430,12 @@ export const settingsRouter = createTRPCRouter({
}
return readConfigInPath(input.path, input.serverId);
}),
- getIp: protectedProcedure.query(async () => {
+ getIp: protectedProcedure.query(async ({ ctx }) => {
if (IS_CLOUD) {
return true;
}
- const admin = await findAdmin();
- return admin.serverIp;
+ const user = await findUserById(ctx.user.ownerId);
+ return user.serverIp;
}),
getOpenApiDocument: protectedProcedure.query(
@@ -480,10 +482,28 @@ export const settingsRouter = createTRPCRouter({
openApiDocument.info = {
title: "Dokploy API",
description: "Endpoints for dokploy",
- // TODO: get version from package.json
version: "1.0.0",
};
+ // Add security schemes configuration
+ openApiDocument.components = {
+ ...openApiDocument.components,
+ securitySchemes: {
+ apiKey: {
+ type: "apiKey",
+ in: "header",
+ name: "x-api-key",
+ description: "API key authentication",
+ },
+ },
+ };
+
+ // Apply security globally to all endpoints
+ openApiDocument.security = [
+ {
+ apiKey: [],
+ },
+ ];
return openApiDocument;
},
),
@@ -655,7 +675,7 @@ export const settingsRouter = createTRPCRouter({
return true;
}),
- isCloud: protectedProcedure.query(async () => {
+ isCloud: publicProcedure.query(async () => {
return IS_CLOUD;
}),
health: publicProcedure.query(async () => {
@@ -715,7 +735,12 @@ export const settingsRouter = createTRPCRouter({
try {
return await checkGPUStatus(input.serverId || "");
} catch (error) {
- throw new Error("Failed to check GPU status");
+ const message =
+ error instanceof Error ? error.message : "Failed to check GPU status";
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message,
+ });
}
}),
updateTraefikPorts: adminProcedure
diff --git a/apps/dokploy/server/api/routers/ssh-key.ts b/apps/dokploy/server/api/routers/ssh-key.ts
index fe2f24f9..4663af8f 100644
--- a/apps/dokploy/server/api/routers/ssh-key.ts
+++ b/apps/dokploy/server/api/routers/ssh-key.ts
@@ -9,7 +9,6 @@ import {
sshKeys,
} from "@/server/db/schema";
import {
- IS_CLOUD,
createSshKey,
findSSHKeyById,
generateSSHKey,
@@ -26,7 +25,7 @@ export const sshRouter = createTRPCRouter({
try {
await createSshKey({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -41,8 +40,7 @@ export const sshRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const sshKey = await findSSHKeyById(input.sshKeyId);
- if (IS_CLOUD && sshKey.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (sshKey.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this SSH key",
@@ -59,8 +57,7 @@ export const sshRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const sshKey = await findSSHKeyById(input.sshKeyId);
- if (IS_CLOUD && sshKey.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (sshKey.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this SSH key",
@@ -70,10 +67,9 @@ export const sshRouter = createTRPCRouter({
}),
all: protectedProcedure.query(async ({ ctx }) => {
return await db.query.sshKeys.findMany({
- ...(IS_CLOUD && { where: eq(sshKeys.adminId, ctx.user.adminId) }),
+ where: eq(sshKeys.organizationId, ctx.session.activeOrganizationId),
orderBy: desc(sshKeys.createdAt),
});
- // TODO: Remove this line when the cloud version is ready
}),
generate: protectedProcedure
.input(apiGenerateSSHKey)
@@ -85,8 +81,7 @@ export const sshRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const sshKey = await findSSHKeyById(input.sshKeyId);
- if (IS_CLOUD && sshKey.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (sshKey.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to update this SSH key",
diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts
index 7a8a537c..a226eeac 100644
--- a/apps/dokploy/server/api/routers/stripe.ts
+++ b/apps/dokploy/server/api/routers/stripe.ts
@@ -1,9 +1,9 @@
import { WEBSITE_URL, getStripeItems } from "@/server/utils/stripe";
import {
IS_CLOUD,
- findAdminById,
- findServersByAdminId,
- updateAdmin,
+ findServersByUserId,
+ findUserById,
+ updateUser,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import Stripe from "stripe";
@@ -12,8 +12,8 @@ import { adminProcedure, createTRPCRouter } from "../trpc";
export const stripeRouter = createTRPCRouter({
getProducts: adminProcedure.query(async ({ ctx }) => {
- const admin = await findAdminById(ctx.user.adminId);
- const stripeCustomerId = admin.stripeCustomerId;
+ const user = await findUserById(ctx.user.ownerId);
+ const stripeCustomerId = user.stripeCustomerId;
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-09-30.acacia",
@@ -56,15 +56,15 @@ export const stripeRouter = createTRPCRouter({
});
const items = getStripeItems(input.serverQuantity, input.isAnnual);
- const admin = await findAdminById(ctx.user.adminId);
+ const user = await findUserById(ctx.user.id);
- let stripeCustomerId = admin.stripeCustomerId;
+ let stripeCustomerId = user.stripeCustomerId;
if (stripeCustomerId) {
const customer = await stripe.customers.retrieve(stripeCustomerId);
if (customer.deleted) {
- await updateAdmin(admin.authId, {
+ await updateUser(user.id, {
stripeCustomerId: null,
});
stripeCustomerId = null;
@@ -78,7 +78,7 @@ export const stripeRouter = createTRPCRouter({
customer: stripeCustomerId,
}),
metadata: {
- adminId: admin.adminId,
+ adminId: user.id,
},
allow_promotion_codes: true,
success_url: `${WEBSITE_URL}/dashboard/settings/servers?success=true`,
@@ -87,45 +87,43 @@ export const stripeRouter = createTRPCRouter({
return { sessionId: session.id };
}),
- createCustomerPortalSession: adminProcedure.mutation(
- async ({ ctx, input }) => {
- const admin = await findAdminById(ctx.user.adminId);
+ createCustomerPortalSession: adminProcedure.mutation(async ({ ctx }) => {
+ const user = await findUserById(ctx.user.id);
- if (!admin.stripeCustomerId) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Stripe Customer ID not found",
- });
- }
- const stripeCustomerId = admin.stripeCustomerId;
+ if (!user.stripeCustomerId) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Stripe Customer ID not found",
+ });
+ }
+ const stripeCustomerId = user.stripeCustomerId;
- const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
- apiVersion: "2024-09-30.acacia",
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
+ apiVersion: "2024-09-30.acacia",
+ });
+
+ try {
+ const session = await stripe.billingPortal.sessions.create({
+ customer: stripeCustomerId,
+ return_url: `${WEBSITE_URL}/dashboard/settings/billing`,
});
- try {
- const session = await stripe.billingPortal.sessions.create({
- customer: stripeCustomerId,
- return_url: `${WEBSITE_URL}/dashboard/settings/billing`,
- });
-
- return { url: session.url };
- } catch (error) {
- return {
- url: "",
- };
- }
- },
- ),
+ return { url: session.url };
+ } catch (_) {
+ return {
+ url: "",
+ };
+ }
+ }),
canCreateMoreServers: adminProcedure.query(async ({ ctx }) => {
- const admin = await findAdminById(ctx.user.adminId);
- const servers = await findServersByAdminId(admin.adminId);
+ const user = await findUserById(ctx.user.ownerId);
+ const servers = await findServersByUserId(user.id);
if (!IS_CLOUD) {
return true;
}
- return servers.length < admin.serversQuantity;
+ return servers.length < user.serversQuantity;
}),
});
diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts
index 91db9826..0b740ab7 100644
--- a/apps/dokploy/server/api/routers/user.ts
+++ b/apps/dokploy/server/api/routers/user.ts
@@ -1,34 +1,330 @@
-import { apiFindOneUser, apiFindOneUserByAuth } from "@/server/db/schema";
-import { findUserByAuthId, findUserById, findUsers } from "@dokploy/server";
+import {
+ IS_CLOUD,
+ findOrganizationById,
+ findUserById,
+ getUserByToken,
+ removeUserById,
+ updateUser,
+ createApiKey,
+} from "@dokploy/server";
+import { db } from "@dokploy/server/db";
+import {
+ account,
+ apiAssignPermissions,
+ apiFindOneToken,
+ apiUpdateUser,
+ invitation,
+ member,
+ apikey,
+} from "@dokploy/server/db/schema";
+import * as bcrypt from "bcrypt";
import { TRPCError } from "@trpc/server";
-import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
+import { and, asc, eq, gt } from "drizzle-orm";
+import { z } from "zod";
+import {
+ adminProcedure,
+ createTRPCRouter,
+ protectedProcedure,
+ publicProcedure,
+} from "../trpc";
+
+const apiCreateApiKey = z.object({
+ name: z.string().min(1),
+ prefix: z.string().optional(),
+ expiresIn: z.number().optional(),
+ metadata: z.object({
+ organizationId: z.string(),
+ }),
+ // Rate limiting
+ rateLimitEnabled: z.boolean().optional(),
+ rateLimitTimeWindow: z.number().optional(),
+ rateLimitMax: z.number().optional(),
+ // Request limiting
+ remaining: z.number().optional(),
+ refillAmount: z.number().optional(),
+ refillInterval: z.number().optional(),
+});
export const userRouter = createTRPCRouter({
all: adminProcedure.query(async ({ ctx }) => {
- return await findUsers(ctx.user.adminId);
+ return await db.query.member.findMany({
+ where: eq(member.organizationId, ctx.session.activeOrganizationId),
+ with: {
+ user: true,
+ },
+ orderBy: [asc(member.createdAt)],
+ });
}),
- byAuthId: protectedProcedure
- .input(apiFindOneUserByAuth)
+ one: protectedProcedure
+ .input(
+ z.object({
+ userId: z.string(),
+ }),
+ )
.query(async ({ input, ctx }) => {
- const user = await findUserByAuthId(input.authId);
- if (user.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to access this user",
- });
- }
- return user;
+ const memberResult = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, input.userId),
+ eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ ),
+ with: {
+ user: true,
+ },
+ });
+
+ return memberResult;
}),
- byUserId: protectedProcedure
- .input(apiFindOneUser)
- .query(async ({ input, ctx }) => {
- const user = await findUserById(input.userId);
- if (user.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to access this user",
+ get: protectedProcedure.query(async ({ ctx }) => {
+ const memberResult = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, ctx.user.id),
+ eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ ),
+ with: {
+ user: {
+ with: {
+ apiKeys: true,
+ },
+ },
+ },
+ });
+
+ return memberResult;
+ }),
+ getServerMetrics: protectedProcedure.query(async ({ ctx }) => {
+ const memberResult = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, ctx.user.id),
+ eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ ),
+ with: {
+ user: true,
+ },
+ });
+
+ return memberResult?.user;
+ }),
+ update: protectedProcedure
+ .input(apiUpdateUser)
+ .mutation(async ({ input, ctx }) => {
+ if (input.password || input.currentPassword) {
+ const currentAuth = await db.query.account.findFirst({
+ where: eq(account.userId, ctx.user.id),
});
+ const correctPassword = bcrypt.compareSync(
+ input.currentPassword || "",
+ currentAuth?.password || "",
+ );
+
+ if (!correctPassword) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Current password is incorrect",
+ });
+ }
+
+ if (!input.password) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "New password is required",
+ });
+ }
+ await db
+ .update(account)
+ .set({
+ password: bcrypt.hashSync(input.password, 10),
+ })
+ .where(eq(account.userId, ctx.user.id));
}
- return user;
+ return await updateUser(ctx.user.id, input);
+ }),
+ getUserByToken: publicProcedure
+ .input(apiFindOneToken)
+ .query(async ({ input }) => {
+ return await getUserByToken(input.token);
+ }),
+ getMetricsToken: protectedProcedure.query(async ({ ctx }) => {
+ const user = await findUserById(ctx.user.ownerId);
+ return {
+ serverIp: user.serverIp,
+ enabledFeatures: user.enablePaidFeatures,
+ metricsConfig: user?.metricsConfig,
+ };
+ }),
+ remove: protectedProcedure
+ .input(
+ z.object({
+ userId: z.string(),
+ }),
+ )
+ .mutation(async ({ input }) => {
+ if (IS_CLOUD) {
+ return true;
+ }
+ return await removeUserById(input.userId);
+ }),
+ assignPermissions: adminProcedure
+ .input(apiAssignPermissions)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const organization = await findOrganizationById(
+ ctx.session?.activeOrganizationId || "",
+ );
+
+ if (organization?.ownerId !== ctx.user.ownerId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not allowed to assign permissions",
+ });
+ }
+
+ const { id, ...rest } = input;
+
+ await db
+ .update(member)
+ .set({
+ ...rest,
+ })
+ .where(
+ and(
+ eq(member.userId, input.id),
+ eq(
+ member.organizationId,
+ ctx.session?.activeOrganizationId || "",
+ ),
+ ),
+ );
+ } catch (error) {
+ throw error;
+ }
+ }),
+ getInvitations: protectedProcedure.query(async ({ ctx }) => {
+ return await db.query.invitation.findMany({
+ where: and(
+ eq(invitation.email, ctx.user.email),
+ gt(invitation.expiresAt, new Date()),
+ eq(invitation.status, "pending"),
+ ),
+ with: {
+ organization: true,
+ },
+ });
+ }),
+
+ getContainerMetrics: protectedProcedure
+ .input(
+ z.object({
+ url: z.string(),
+ token: z.string(),
+ appName: z.string(),
+ dataPoints: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ try {
+ if (!input.appName) {
+ throw new Error(
+ [
+ "No Application Selected:",
+ "",
+ "Make Sure to select an application to monitor.",
+ ].join("\n"),
+ );
+ }
+ const url = new URL(`${input.url}/metrics/containers`);
+ url.searchParams.append("limit", input.dataPoints);
+ url.searchParams.append("appName", input.appName);
+ const response = await fetch(url.toString(), {
+ headers: {
+ Authorization: `Bearer ${input.token}`,
+ },
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`,
+ );
+ }
+
+ const data = await response.json();
+ if (!Array.isArray(data) || data.length === 0) {
+ throw new Error(
+ [
+ `No monitoring data available for "${input.appName}". This could be because:`,
+ "",
+ "1. The container was recently started - wait a few minutes for data to be collected",
+ "2. The container is not running - verify its status",
+ "3. The service is not included in your monitoring configuration",
+ ].join("\n"),
+ );
+ }
+ return data as {
+ containerId: string;
+ containerName: string;
+ containerImage: string;
+ containerLabels: string;
+ containerCommand: string;
+ containerCreated: string;
+ }[];
+ } catch (error) {
+ throw error;
+ }
+ }),
+
+ generateToken: protectedProcedure.mutation(async () => {
+ return "token";
+ }),
+
+ deleteApiKey: protectedProcedure
+ .input(
+ z.object({
+ apiKeyId: z.string(),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const apiKeyToDelete = await db.query.apikey.findFirst({
+ where: eq(apikey.id, input.apiKeyId),
+ });
+
+ if (!apiKeyToDelete) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "API key not found",
+ });
+ }
+
+ if (apiKeyToDelete.userId !== ctx.user.id) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to delete this API key",
+ });
+ }
+
+ await db.delete(apikey).where(eq(apikey.id, input.apiKeyId));
+ return true;
+ } catch (error) {
+ throw error;
+ }
+ }),
+
+ createApiKey: protectedProcedure
+ .input(apiCreateApiKey)
+ .mutation(async ({ input, ctx }) => {
+ const apiKey = await createApiKey(ctx.user.id, input);
+ return apiKey;
+ }),
+
+ checkUserOrganizations: protectedProcedure
+ .input(
+ z.object({
+ userId: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ const organizations = await db.query.member.findMany({
+ where: eq(member.userId, input.userId),
+ });
+
+ return organizations.length;
}),
});
diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts
index db4f7adf..4c88eb22 100644
--- a/apps/dokploy/server/api/trpc.ts
+++ b/apps/dokploy/server/api/trpc.ts
@@ -9,7 +9,7 @@
// import { getServerAuthSession } from "@/server/auth";
import { db } from "@/server/db";
-import { validateBearerToken, validateRequest } from "@dokploy/server";
+import { validateRequest } from "@dokploy/server/lib/auth";
import type { OpenApiMeta } from "@dokploy/trpc-openapi";
import { TRPCError, initTRPC } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
@@ -18,7 +18,7 @@ import {
experimental_isMultipartFormDataRequest,
experimental_parseMultipartFormData,
} from "@trpc/server/adapters/node-http/content-type/form-data";
-import type { Session, User } from "lucia";
+import type { Session, User } from "better-auth";
import superjson from "superjson";
import { ZodError } from "zod";
/**
@@ -30,8 +30,8 @@ import { ZodError } from "zod";
*/
interface CreateContextOptions {
- user: (User & { authId: string; adminId: string }) | null;
- session: Session | null;
+ user: (User & { rol: "member" | "admin" | "owner"; ownerId: string }) | null;
+ session: (Session & { activeOrganizationId: string }) | null;
req: CreateNextContextOptions["req"];
res: CreateNextContextOptions["res"];
}
@@ -65,30 +65,29 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => {
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
- let { session, user } = await validateBearerToken(req);
-
- if (!session) {
- const cookieResult = await validateRequest(req, res);
- session = cookieResult.session;
- user = cookieResult.user;
- }
+ // Get from the request
+ const { session, user } = await validateRequest(req);
return createInnerTRPCContext({
req,
res,
- session: session,
- ...((user && {
- user: {
- authId: user.id,
- email: user.email,
- rol: user.rol,
- id: user.id,
- secret: user.secret,
- adminId: user.adminId,
- },
- }) || {
- user: null,
- }),
+ // @ts-ignore
+ session: session
+ ? {
+ ...session,
+ activeOrganizationId: session.activeOrganizationId || "",
+ }
+ : null,
+ // @ts-ignore
+ user: user
+ ? {
+ ...user,
+ email: user.email,
+ rol: user.role as "owner" | "member" | "admin",
+ id: user.id,
+ ownerId: user.ownerId,
+ }
+ : null,
});
};
@@ -181,7 +180,7 @@ export const uploadProcedure = async (opts: any) => {
};
export const cliProcedure = t.procedure.use(({ ctx, next }) => {
- if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
+ if (!ctx.session || !ctx.user || ctx.user.rol !== "owner") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
@@ -195,7 +194,7 @@ export const cliProcedure = t.procedure.use(({ ctx, next }) => {
});
export const adminProcedure = t.procedure.use(({ ctx, next }) => {
- if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
+ if (!ctx.session || !ctx.user || ctx.user.rol !== "owner") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
diff --git a/apps/dokploy/server/db/seed.ts b/apps/dokploy/server/db/seed.ts
index b7935079..5b3eb6c6 100644
--- a/apps/dokploy/server/db/seed.ts
+++ b/apps/dokploy/server/db/seed.ts
@@ -1,16 +1,10 @@
-import bc from "bcrypt";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
-import { users } from "./schema";
const connectionString = process.env.DATABASE_URL!;
const pg = postgres(connectionString, { max: 1 });
-const db = drizzle(pg);
-
-function password(txt: string) {
- return bc.hashSync(txt, 10);
-}
+const _db = drizzle(pg);
async function seed() {
console.log("> Seed:", process.env.DATABASE_PATH, "\n");
diff --git a/apps/dokploy/server/utils/backup.ts b/apps/dokploy/server/utils/backup.ts
index 2f141971..4fc9db93 100644
--- a/apps/dokploy/server/utils/backup.ts
+++ b/apps/dokploy/server/utils/backup.ts
@@ -2,7 +2,6 @@ import {
type BackupScheduleList,
IS_CLOUD,
removeScheduleBackup,
- scheduleBackup,
} from "@dokploy/server/index";
type QueueJob =
diff --git a/apps/dokploy/server/utils/docker.ts b/apps/dokploy/server/utils/docker.ts
index 92008678..3314eb62 100644
--- a/apps/dokploy/server/utils/docker.ts
+++ b/apps/dokploy/server/utils/docker.ts
@@ -6,7 +6,7 @@ export const isWSL = async () => {
const { stdout } = await execAsync("uname -r");
const isWSL = stdout.includes("microsoft");
return isWSL;
- } catch (error) {
+ } catch (_error) {
return false;
}
};
diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts
index 092f3973..8d08ebd4 100644
--- a/apps/dokploy/server/wss/docker-container-logs.ts
+++ b/apps/dokploy/server/wss/docker-container-logs.ts
@@ -1,5 +1,5 @@
import type http from "node:http";
-import { findServerById, validateWebSocketRequest } from "@dokploy/server";
+import { findServerById, validateRequest } from "@dokploy/server";
import { spawn } from "node-pty";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
@@ -35,7 +35,7 @@ export const setupDockerContainerLogsWebSocketServer = (
const since = url.searchParams.get("since");
const serverId = url.searchParams.get("serverId");
const runType = url.searchParams.get("runType");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!containerId) {
ws.close(4000, "containerId no provided");
diff --git a/apps/dokploy/server/wss/docker-container-terminal.ts b/apps/dokploy/server/wss/docker-container-terminal.ts
index 8981ccbc..2f25edb1 100644
--- a/apps/dokploy/server/wss/docker-container-terminal.ts
+++ b/apps/dokploy/server/wss/docker-container-terminal.ts
@@ -1,5 +1,5 @@
import type http from "node:http";
-import { findServerById, validateWebSocketRequest } from "@dokploy/server";
+import { findServerById, validateRequest } from "@dokploy/server";
import { spawn } from "node-pty";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
@@ -32,7 +32,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
const containerId = url.searchParams.get("containerId");
const activeWay = url.searchParams.get("activeWay");
const serverId = url.searchParams.get("serverId");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!containerId) {
ws.close(4000, "containerId no provided");
@@ -50,8 +50,8 @@ export const setupDockerContainerTerminalWebSocketServer = (
throw new Error("No SSH key available for this server");
const conn = new Client();
- let stdout = "";
- let stderr = "";
+ let _stdout = "";
+ let _stderr = "";
conn
.once("ready", () => {
conn.exec(
@@ -61,16 +61,16 @@ export const setupDockerContainerTerminalWebSocketServer = (
if (err) throw err;
stream
- .on("close", (code: number, signal: string) => {
+ .on("close", (code: number, _signal: string) => {
ws.send(`\nContainer closed with code: ${code}\n`);
conn.end();
})
.on("data", (data: string) => {
- stdout += data.toString();
+ _stdout += data.toString();
ws.send(data.toString());
})
.stderr.on("data", (data) => {
- stderr += data.toString();
+ _stderr += data.toString();
ws.send(data.toString());
console.error("Error: ", data.toString());
});
diff --git a/apps/dokploy/server/wss/docker-stats.ts b/apps/dokploy/server/wss/docker-stats.ts
index b1e4585c..99e993dc 100644
--- a/apps/dokploy/server/wss/docker-stats.ts
+++ b/apps/dokploy/server/wss/docker-stats.ts
@@ -4,7 +4,7 @@ import {
execAsync,
getLastAdvancedStatsFile,
recordAdvancedStats,
- validateWebSocketRequest,
+ validateRequest,
} from "@dokploy/server";
import { WebSocketServer } from "ws";
@@ -36,7 +36,7 @@ export const setupDockerStatsMonitoringSocketServer = (
| "application"
| "stack"
| "docker-compose";
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!appName) {
ws.close(4000, "appName no provided");
diff --git a/apps/dokploy/server/wss/drawer-logs.ts b/apps/dokploy/server/wss/drawer-logs.ts
index c1dec315..404dfeee 100644
--- a/apps/dokploy/server/wss/drawer-logs.ts
+++ b/apps/dokploy/server/wss/drawer-logs.ts
@@ -1,4 +1,5 @@
import type http from "node:http";
+import { validateRequest } from "@dokploy/server/index";
import { applyWSSHandler } from "@trpc/server/adapters/ws";
import { WebSocketServer } from "ws";
import { appRouter } from "../api/root";
@@ -31,6 +32,12 @@ export const setupDrawerLogsWebSocketServer = (
});
wssTerm.on("connection", async (ws, req) => {
- const url = new URL(req.url || "", `http://${req.headers.host}`);
+ const _url = new URL(req.url || "", `http://${req.headers.host}`);
+ const { user, session } = await validateRequest(req);
+
+ if (!user || !session) {
+ ws.close();
+ return;
+ }
});
};
diff --git a/apps/dokploy/server/wss/listen-deployment.ts b/apps/dokploy/server/wss/listen-deployment.ts
index df77ceb4..4a25c6f0 100644
--- a/apps/dokploy/server/wss/listen-deployment.ts
+++ b/apps/dokploy/server/wss/listen-deployment.ts
@@ -1,6 +1,6 @@
import { spawn } from "node:child_process";
import type http from "node:http";
-import { findServerById, validateWebSocketRequest } from "@dokploy/server";
+import { findServerById, validateRequest } from "@dokploy/server";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
@@ -29,7 +29,7 @@ export const setupDeploymentLogsWebSocketServer = (
const url = new URL(req.url || "", `http://${req.headers.host}`);
const logPath = url.searchParams.get("logPath");
const serverId = url.searchParams.get("serverId");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!logPath) {
console.log("logPath no provided");
@@ -103,7 +103,7 @@ export const setupDeploymentLogsWebSocketServer = (
ws.close();
});
}
- } catch (error) {
+ } catch (_error) {
// @ts-ignore
// const errorMessage = error?.message as unknown as string;
// ws.send(errorMessage);
diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts
index 5fa1accc..094c5e15 100644
--- a/apps/dokploy/server/wss/terminal.ts
+++ b/apps/dokploy/server/wss/terminal.ts
@@ -1,9 +1,5 @@
import type http from "node:http";
-import {
- IS_CLOUD,
- findServerById,
- validateWebSocketRequest,
-} from "@dokploy/server";
+import { IS_CLOUD, findServerById, validateRequest } from "@dokploy/server";
import { publicIpv4, publicIpv6 } from "public-ip";
import { Client, type ConnectConfig } from "ssh2";
import { WebSocketServer } from "ws";
@@ -71,7 +67,7 @@ export const setupTerminalWebSocketServer = (
wssTerm.on("connection", async (ws, req) => {
const url = new URL(req.url || "", `http://${req.headers.host}`);
const serverId = url.searchParams.get("serverId");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!user || !session || !serverId) {
ws.close();
return;
@@ -148,8 +144,8 @@ export const setupTerminalWebSocketServer = (
}
const conn = new Client();
- let stdout = "";
- let stderr = "";
+ let _stdout = "";
+ let _stderr = "";
ws.send("Connecting...\n");
@@ -162,16 +158,16 @@ export const setupTerminalWebSocketServer = (
if (err) throw err;
stream
- .on("close", (code: number, signal: string) => {
+ .on("close", (code: number, _signal: string) => {
ws.send(`\nContainer closed with code: ${code}\n`);
conn.end();
})
.on("data", (data: string) => {
- stdout += data.toString();
+ _stdout += data.toString();
ws.send(data.toString());
})
.stderr.on("data", (data) => {
- stderr += data.toString();
+ _stderr += data.toString();
ws.send(data.toString());
console.error("Error: ", data.toString());
});
diff --git a/apps/dokploy/templates/appsmith/index.ts b/apps/dokploy/templates/appsmith/index.ts
index ff744a24..73279e91 100644
--- a/apps/dokploy/templates/appsmith/index.ts
+++ b/apps/dokploy/templates/appsmith/index.ts
@@ -7,7 +7,7 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
+ const _mainServiceHash = generateHash(schema.projectName);
const domains: DomainSchema[] = [
{
diff --git a/apps/dokploy/templates/appwrite/docker-compose.yml b/apps/dokploy/templates/appwrite/docker-compose.yml
new file mode 100644
index 00000000..163cb3d0
--- /dev/null
+++ b/apps/dokploy/templates/appwrite/docker-compose.yml
@@ -0,0 +1,887 @@
+version: "3.8"
+
+x-logging: &x-logging
+ logging:
+ driver: "json-file"
+ options:
+ max-file: "5"
+ max-size: "10m"
+
+services:
+ appwrite:
+ image: appwrite/appwrite:1.6.0
+ container_name: appwrite
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=appwrite
+ volumes:
+ - appwrite-uploads:/storage/uploads:rw
+ - appwrite-cache:/storage/cache:rw
+ - appwrite-config:/storage/config:rw
+ - appwrite-certificates:/storage/certificates:rw
+ - appwrite-functions:/storage/functions:rw
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_LOCALE
+ - _APP_CONSOLE_WHITELIST_ROOT
+ - _APP_CONSOLE_WHITELIST_EMAILS
+ - _APP_CONSOLE_SESSION_ALERTS
+ - _APP_CONSOLE_WHITELIST_IPS
+ - _APP_CONSOLE_HOSTNAMES
+ - _APP_SYSTEM_EMAIL_NAME
+ - _APP_SYSTEM_EMAIL_ADDRESS
+ - _APP_EMAIL_SECURITY
+ - _APP_SYSTEM_RESPONSE_FORMAT
+ - _APP_OPTIONS_ABUSE
+ - _APP_OPTIONS_ROUTER_PROTECTION
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_SMTP_HOST
+ - _APP_SMTP_PORT
+ - _APP_SMTP_SECURE
+ - _APP_SMTP_USERNAME
+ - _APP_SMTP_PASSWORD
+ - _APP_USAGE_STATS
+ - _APP_STORAGE_LIMIT
+ - _APP_STORAGE_PREVIEW_LIMIT
+ - _APP_STORAGE_ANTIVIRUS
+ - _APP_STORAGE_ANTIVIRUS_HOST
+ - _APP_STORAGE_ANTIVIRUS_PORT
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+ - _APP_FUNCTIONS_SIZE_LIMIT
+ - _APP_FUNCTIONS_TIMEOUT
+ - _APP_FUNCTIONS_BUILD_TIMEOUT
+ - _APP_FUNCTIONS_CPUS
+ - _APP_FUNCTIONS_MEMORY
+ - _APP_FUNCTIONS_RUNTIMES
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_LOGGING_CONFIG
+ - _APP_MAINTENANCE_INTERVAL
+ - _APP_MAINTENANCE_DELAY
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
+ - _APP_MAINTENANCE_RETENTION_CACHE
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
+ - _APP_MAINTENANCE_RETENTION_SCHEDULES
+ - _APP_SMS_PROVIDER
+ - _APP_SMS_FROM
+ - _APP_GRAPHQL_MAX_BATCH_SIZE
+ - _APP_GRAPHQL_MAX_COMPLEXITY
+ - _APP_GRAPHQL_MAX_DEPTH
+ - _APP_VCS_GITHUB_APP_NAME
+ - _APP_VCS_GITHUB_PRIVATE_KEY
+ - _APP_VCS_GITHUB_APP_ID
+ - _APP_VCS_GITHUB_WEBHOOK_SECRET
+ - _APP_VCS_GITHUB_CLIENT_SECRET
+ - _APP_VCS_GITHUB_CLIENT_ID
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_ID
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
+ - _APP_ASSISTANT_OPENAI_API_KEY
+
+ appwrite-console:
+ image: appwrite/console:5.0.12
+ container_name: appwrite-console
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ labels:
+ - "traefik.enable=true"
+ - "traefik.constraint-label-stack=appwrite"
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_LOCALE
+ - _APP_CONSOLE_WHITELIST_ROOT
+ - _APP_CONSOLE_WHITELIST_EMAILS
+ - _APP_CONSOLE_SESSION_ALERTS
+ - _APP_CONSOLE_WHITELIST_IPS
+ - _APP_CONSOLE_HOSTNAMES
+ - _APP_SYSTEM_EMAIL_NAME
+ - _APP_SYSTEM_EMAIL_ADDRESS
+ - _APP_EMAIL_SECURITY
+ - _APP_SYSTEM_RESPONSE_FORMAT
+ - _APP_OPTIONS_ABUSE
+ - _APP_OPTIONS_ROUTER_PROTECTION
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_SMTP_HOST
+ - _APP_SMTP_PORT
+ - _APP_SMTP_SECURE
+ - _APP_SMTP_USERNAME
+ - _APP_SMTP_PASSWORD
+ - _APP_USAGE_STATS
+ - _APP_STORAGE_LIMIT
+ - _APP_STORAGE_PREVIEW_LIMIT
+ - _APP_STORAGE_ANTIVIRUS
+ - _APP_STORAGE_ANTIVIRUS_HOST
+ - _APP_STORAGE_ANTIVIRUS_PORT
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+
+ appwrite-realtime:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: realtime
+ container_name: appwrite-realtime
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ labels:
+ - "traefik.enable=true"
+ - "traefik.constraint-label-stack=appwrite"
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPTIONS_ABUSE
+ - _APP_OPTIONS_ROUTER_PROTECTION
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_USAGE_STATS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-audits:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-audits
+ <<: *x-logging
+ container_name: appwrite-worker-audits
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-webhooks:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-webhooks
+ <<: *x-logging
+ container_name: appwrite-worker-webhooks
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_EMAIL_SECURITY
+ - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-deletes:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-deletes
+ <<: *x-logging
+ container_name: appwrite-worker-deletes
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ volumes:
+ - appwrite-uploads:/storage/uploads:rw
+ - appwrite-cache:/storage/cache:rw
+ - appwrite-functions:/storage/functions:rw
+ - appwrite-builds:/storage/builds:rw
+ - appwrite-certificates:/storage/certificates:rw
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+ - _APP_LOGGING_CONFIG
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
+
+ appwrite-worker-databases:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-databases
+ <<: *x-logging
+ container_name: appwrite-worker-databases
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-builds:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-builds
+ <<: *x-logging
+ container_name: appwrite-worker-builds
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ volumes:
+ - appwrite-functions:/storage/functions:rw
+ - appwrite-builds:/storage/builds:rw
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+ - _APP_VCS_GITHUB_APP_NAME
+ - _APP_VCS_GITHUB_PRIVATE_KEY
+ - _APP_VCS_GITHUB_APP_ID
+ - _APP_FUNCTIONS_TIMEOUT
+ - _APP_FUNCTIONS_BUILD_TIMEOUT
+ - _APP_FUNCTIONS_CPUS
+ - _APP_FUNCTIONS_MEMORY
+ - _APP_FUNCTIONS_SIZE_LIMIT
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
+ - _APP_DOMAIN
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+
+ appwrite-worker-certificates:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-certificates
+ <<: *x-logging
+ container_name: appwrite-worker-certificates
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ volumes:
+ - appwrite-config:/storage/config:rw
+ - appwrite-certificates:/storage/certificates:rw
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_EMAIL_CERTIFICATES
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-functions:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-functions
+ <<: *x-logging
+ container_name: appwrite-worker-functions
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ - openruntimes-executor
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_FUNCTIONS_TIMEOUT
+ - _APP_FUNCTIONS_BUILD_TIMEOUT
+ - _APP_FUNCTIONS_CPUS
+ - _APP_FUNCTIONS_MEMORY
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_USAGE_STATS
+ - _APP_DOCKER_HUB_USERNAME
+ - _APP_DOCKER_HUB_PASSWORD
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-mails:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-mails
+ <<: *x-logging
+ container_name: appwrite-worker-mails
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_SYSTEM_EMAIL_NAME
+ - _APP_SYSTEM_EMAIL_ADDRESS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_SMTP_HOST
+ - _APP_SMTP_PORT
+ - _APP_SMTP_SECURE
+ - _APP_SMTP_USERNAME
+ - _APP_SMTP_PASSWORD
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-messaging:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-messaging
+ container_name: appwrite-worker-messaging
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ volumes:
+ - appwrite-uploads:/storage/uploads:rw
+ depends_on:
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+ - _APP_SMS_FROM
+ - _APP_SMS_PROVIDER
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+
+ appwrite-worker-migrations:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-migrations
+ <<: *x-logging
+ container_name: appwrite-worker-migrations
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_EMAIL_SECURITY
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_ID
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
+
+ appwrite-task-maintenance:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: maintenance
+ <<: *x-logging
+ container_name: appwrite-task-maintenance
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_MAINTENANCE_INTERVAL
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
+ - _APP_MAINTENANCE_RETENTION_CACHE
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
+ - _APP_MAINTENANCE_RETENTION_SCHEDULES
+
+ appwrite-worker-usage:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-usage
+ container_name: appwrite-worker-usage
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_USAGE_STATS
+ - _APP_LOGGING_CONFIG
+ - _APP_USAGE_AGGREGATION_INTERVAL
+
+ appwrite-worker-usage-dump:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-usage-dump
+ container_name: appwrite-worker-usage-dump
+ <<: *x-logging
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_USAGE_STATS
+ - _APP_LOGGING_CONFIG
+ - _APP_USAGE_AGGREGATION_INTERVAL
+
+ appwrite-task-scheduler-functions:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: schedule-functions
+ container_name: appwrite-task-scheduler-functions
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+
+ appwrite-task-scheduler-executions:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: schedule-executions
+ container_name: appwrite-task-scheduler-executions
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+
+ appwrite-task-scheduler-messages:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: schedule-messages
+ container_name: appwrite-task-scheduler-messages
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+
+ appwrite-assistant:
+ image: appwrite/assistant:0.4.0
+ container_name: appwrite-assistant
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ environment:
+ - _APP_ASSISTANT_OPENAI_API_KEY
+
+ openruntimes-executor:
+ container_name: openruntimes-executor
+ hostname: exc1
+ <<: *x-logging
+ restart: unless-stopped
+ stop_signal: SIGINT
+ image: openruntimes/executor:0.6.11
+ networks:
+ - dokploy-network
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - appwrite-builds:/storage/builds:rw
+ - appwrite-functions:/storage/functions:rw
+ - /tmp:/tmp:rw
+ environment:
+ - OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
+ - OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
+ - OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
+ - OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
+ - OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
+ - OPR_EXECUTOR_ENV=$_APP_ENV
+ - OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
+ - OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
+ - OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
+ - OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
+ - OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_S3_SECRET=$_APP_STORAGE_S3_SECRET
+ - OPR_EXECUTOR_STORAGE_S3_REGION=$_APP_STORAGE_S3_REGION
+ - OPR_EXECUTOR_STORAGE_S3_BUCKET=$_APP_STORAGE_S3_BUCKET
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=$_APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=$_APP_STORAGE_DO_SPACES_SECRET
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=$_APP_STORAGE_DO_SPACES_REGION
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_BUCKET=$_APP_STORAGE_DO_SPACES_BUCKET
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_ACCESS_KEY=$_APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_SECRET=$_APP_STORAGE_BACKBLAZE_SECRET
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_REGION=$_APP_STORAGE_BACKBLAZE_REGION
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_BUCKET=$_APP_STORAGE_BACKBLAZE_BUCKET
+ - OPR_EXECUTOR_STORAGE_LINODE_ACCESS_KEY=$_APP_STORAGE_LINODE_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_LINODE_SECRET=$_APP_STORAGE_LINODE_SECRET
+ - OPR_EXECUTOR_STORAGE_LINODE_REGION=$_APP_STORAGE_LINODE_REGION
+ - OPR_EXECUTOR_STORAGE_LINODE_BUCKET=$_APP_STORAGE_LINODE_BUCKET
+ - OPR_EXECUTOR_STORAGE_WASABI_ACCESS_KEY=$_APP_STORAGE_WASABI_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
+ - OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
+ - OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
+
+ mariadb:
+ image: mariadb:10.11
+ container_name: appwrite-mariadb
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ volumes:
+ - appwrite-mariadb:/var/lib/mysql:rw
+ environment:
+ - MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
+ - MYSQL_DATABASE=${_APP_DB_SCHEMA}
+ - MYSQL_USER=${_APP_DB_USER}
+ - MYSQL_PASSWORD=${_APP_DB_PASS}
+ - MARIADB_AUTO_UPGRADE=1
+ command: "mysqld --innodb-flush-method=fsync"
+
+ redis:
+ image: redis:7.2.4-alpine
+ container_name: appwrite-redis
+ <<: *x-logging
+ restart: unless-stopped
+ command: >
+ redis-server
+ --maxmemory 512mb
+ --maxmemory-policy allkeys-lru
+ --maxmemory-samples 5
+ networks:
+ - dokploy-network
+ volumes:
+ - appwrite-redis:/data:rw
+
+# Uncomment and configure if ClamAV is needed
+# clamav:
+# image: appwrite/clamav:1.2.0
+# container_name: appwrite-clamav
+# restart: unless-stopped
+# networks:
+# - dokploy-network
+# volumes:
+# - appwrite-uploads:/storage/uploads
+
+volumes:
+ appwrite-mariadb:
+ appwrite-redis:
+ appwrite-cache:
+ appwrite-uploads:
+ appwrite-certificates:
+ appwrite-functions:
+ appwrite-builds:
+ appwrite-config:
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/apps/dokploy/templates/appwrite/index.ts b/apps/dokploy/templates/appwrite/index.ts
new file mode 100644
index 00000000..4e671324
--- /dev/null
+++ b/apps/dokploy/templates/appwrite/index.ts
@@ -0,0 +1,153 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ { host: mainDomain, port: 80, serviceName: "appwrite", path: "/" },
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "appwrite-console",
+ path: "/console",
+ },
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "appwrite-realtime",
+ path: "/v1/realtime",
+ },
+ ];
+
+ const envs = [
+ "_APP_ENV=production",
+ "_APP_LOCALE=en",
+ "_APP_OPTIONS_ABUSE=enabled",
+ "_APP_OPTIONS_FORCE_HTTPS=disabled",
+ "_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled",
+ "_APP_OPTIONS_ROUTER_PROTECTION=disabled",
+ "_APP_OPENSSL_KEY_V1=your-secret-key",
+ `_APP_DOMAIN=${mainDomain}`,
+ `_APP_DOMAIN_FUNCTIONS=${mainDomain}`,
+ `_APP_DOMAIN_TARGET=${mainDomain}`,
+ "_APP_CONSOLE_WHITELIST_ROOT=enabled",
+ "_APP_CONSOLE_WHITELIST_EMAILS=",
+ "_APP_CONSOLE_WHITELIST_IPS=",
+ "_APP_CONSOLE_HOSTNAMES=",
+ "_APP_SYSTEM_EMAIL_NAME=Appwrite",
+ "_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io",
+ "_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io",
+ "_APP_SYSTEM_RESPONSE_FORMAT=",
+ "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=certs@appwrite.io",
+ "_APP_EMAIL_SECURITY=",
+ "_APP_EMAIL_CERTIFICATES=",
+ "_APP_USAGE_STATS=enabled",
+ "_APP_LOGGING_PROVIDER=",
+ "_APP_LOGGING_CONFIG=",
+ "_APP_USAGE_AGGREGATION_INTERVAL=30",
+ "_APP_USAGE_TIMESERIES_INTERVAL=30",
+ "_APP_USAGE_DATABASE_INTERVAL=900",
+ "_APP_WORKER_PER_CORE=6",
+ "_APP_CONSOLE_SESSION_ALERTS=disabled",
+ "_APP_REDIS_HOST=redis",
+ "_APP_REDIS_PORT=6379",
+ "_APP_REDIS_USER=",
+ "_APP_REDIS_PASS=",
+ "_APP_DB_HOST=mariadb",
+ "_APP_DB_PORT=3306",
+ "_APP_DB_SCHEMA=appwrite",
+ "_APP_DB_USER=user",
+ "_APP_DB_PASS=password",
+ "_APP_DB_ROOT_PASS=rootsecretpassword",
+ "_APP_INFLUXDB_HOST=influxdb",
+ "_APP_INFLUXDB_PORT=8086",
+ "_APP_STATSD_HOST=telegraf",
+ "_APP_STATSD_PORT=8125",
+ "_APP_SMTP_HOST=",
+ "_APP_SMTP_PORT=",
+ "_APP_SMTP_SECURE=",
+ "_APP_SMTP_USERNAME=",
+ "_APP_SMTP_PASSWORD=",
+ "_APP_SMS_PROVIDER=",
+ "_APP_SMS_FROM=",
+ "_APP_STORAGE_LIMIT=30000000",
+ "_APP_STORAGE_PREVIEW_LIMIT=20000000",
+ "_APP_STORAGE_ANTIVIRUS=disabled",
+ "_APP_STORAGE_ANTIVIRUS_HOST=clamav",
+ "_APP_STORAGE_ANTIVIRUS_PORT=3310",
+ "_APP_STORAGE_DEVICE=local",
+ "_APP_STORAGE_S3_ACCESS_KEY=",
+ "_APP_STORAGE_S3_SECRET=",
+ "_APP_STORAGE_S3_REGION=us-east-1",
+ "_APP_STORAGE_S3_BUCKET=",
+ "_APP_STORAGE_DO_SPACES_ACCESS_KEY=",
+ "_APP_STORAGE_DO_SPACES_SECRET=",
+ "_APP_STORAGE_DO_SPACES_REGION=us-east-1",
+ "_APP_STORAGE_DO_SPACES_BUCKET=",
+ "_APP_STORAGE_BACKBLAZE_ACCESS_KEY=",
+ "_APP_STORAGE_BACKBLAZE_SECRET=",
+ "_APP_STORAGE_BACKBLAZE_REGION=us-west-004",
+ "_APP_STORAGE_BACKBLAZE_BUCKET=",
+ "_APP_STORAGE_LINODE_ACCESS_KEY=",
+ "_APP_STORAGE_LINODE_SECRET=",
+ "_APP_STORAGE_LINODE_REGION=eu-central-1",
+ "_APP_STORAGE_LINODE_BUCKET=",
+ "_APP_STORAGE_WASABI_ACCESS_KEY=",
+ "_APP_STORAGE_WASABI_SECRET=",
+ "_APP_STORAGE_WASABI_REGION=eu-central-1",
+ "_APP_STORAGE_WASABI_BUCKET=",
+ "_APP_FUNCTIONS_SIZE_LIMIT=30000000",
+ "_APP_FUNCTIONS_BUILD_SIZE_LIMIT=2000000000",
+ "_APP_FUNCTIONS_TIMEOUT=900",
+ "_APP_FUNCTIONS_BUILD_TIMEOUT=900",
+ "_APP_FUNCTIONS_CONTAINERS=10",
+ "_APP_FUNCTIONS_CPUS=0",
+ "_APP_FUNCTIONS_MEMORY=0",
+ "_APP_FUNCTIONS_MEMORY_SWAP=0",
+ "_APP_FUNCTIONS_RUNTIMES=node-16.0,php-8.0,python-3.9,ruby-3.0",
+ "_APP_EXECUTOR_SECRET=your-secret-key",
+ "_APP_EXECUTOR_HOST=http://exc1/v1",
+ "_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes",
+ "_APP_FUNCTIONS_ENVS=node-16.0,php-7.4,python-3.9,ruby-3.0",
+ "_APP_FUNCTIONS_INACTIVE_THRESHOLD=60",
+ "DOCKERHUB_PULL_USERNAME=",
+ "DOCKERHUB_PULL_PASSWORD=",
+ "DOCKERHUB_PULL_EMAIL=",
+ "OPEN_RUNTIMES_NETWORK=appwrite_runtimes",
+ "_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes",
+ "_APP_DOCKER_HUB_USERNAME=",
+ "_APP_DOCKER_HUB_PASSWORD=",
+ "_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600",
+ "_APP_VCS_GITHUB_APP_NAME=",
+ "_APP_VCS_GITHUB_PRIVATE_KEY=",
+ "_APP_VCS_GITHUB_APP_ID=",
+ "_APP_VCS_GITHUB_CLIENT_ID=",
+ "_APP_VCS_GITHUB_CLIENT_SECRET=",
+ "_APP_VCS_GITHUB_WEBHOOK_SECRET=",
+ "_APP_MAINTENANCE_INTERVAL=86400",
+ "_APP_MAINTENANCE_DELAY=0",
+ "_APP_MAINTENANCE_RETENTION_CACHE=2592000",
+ "_APP_MAINTENANCE_RETENTION_EXECUTION=1209600",
+ "_APP_MAINTENANCE_RETENTION_AUDIT=1209600",
+ "_APP_MAINTENANCE_RETENTION_ABUSE=86400",
+ "_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000",
+ "_APP_MAINTENANCE_RETENTION_SCHEDULES=86400",
+ "_APP_GRAPHQL_MAX_BATCH_SIZE=10",
+ "_APP_GRAPHQL_MAX_COMPLEXITY=250",
+ "_APP_GRAPHQL_MAX_DEPTH=3",
+ "_APP_MIGRATIONS_FIREBASE_CLIENT_ID=",
+ "_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=",
+ "_APP_ASSISTANT_OPENAI_API_KEY=",
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts: [],
+ };
+}
diff --git a/apps/dokploy/templates/blender/index.ts b/apps/dokploy/templates/blender/index.ts
index 84e52755..79508bed 100644
--- a/apps/dokploy/templates/blender/index.ts
+++ b/apps/dokploy/templates/blender/index.ts
@@ -7,7 +7,7 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
+ const _mainServiceHash = generateHash(schema.projectName);
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
diff --git a/apps/dokploy/templates/cloudflared/index.ts b/apps/dokploy/templates/cloudflared/index.ts
index 661fa31d..93ea091c 100644
--- a/apps/dokploy/templates/cloudflared/index.ts
+++ b/apps/dokploy/templates/cloudflared/index.ts
@@ -1,6 +1,6 @@
import type { Schema, Template } from "../utils";
-export function generate(schema: Schema): Template {
+export function generate(_schema: Schema): Template {
const envs = [`CLOUDFLARE_TUNNEL_TOKEN="
"`];
return {
diff --git a/apps/dokploy/templates/convex/docker-compose.yml b/apps/dokploy/templates/convex/docker-compose.yml
new file mode 100644
index 00000000..12e2b5ad
--- /dev/null
+++ b/apps/dokploy/templates/convex/docker-compose.yml
@@ -0,0 +1,37 @@
+services:
+ backend:
+ image: ghcr.io/get-convex/convex-backend:6c974d219776b753cd23d26f4a296629ff7c2cad
+ ports:
+ - "${PORT:-3210}:3210"
+ - "${SITE_PROXY_PORT:-3211}:3211"
+ volumes:
+ - data:/convex/data
+ environment:
+ - INSTANCE_NAME=${INSTANCE_NAME:-}
+ - INSTANCE_SECRET=${INSTANCE_SECRET:-}
+ - CONVEX_RELEASE_VERSION_DEV=${CONVEX_RELEASE_VERSION_DEV:-}
+ - ACTIONS_USER_TIMEOUT_SECS=${ACTIONS_USER_TIMEOUT_SECS:-}
+ - CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://127.0.0.1:3210}
+ - CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://127.0.0.1:3211}
+ - DATABASE_URL=${DATABASE_URL:-}
+ - DISABLE_BEACON=${DISABLE_BEACON:-}
+ - REDACT_LOGS_TO_CLIENT=${REDACT_LOGS_TO_CLIENT:-}
+ - RUST_LOG=${RUST_LOG:-info}
+ - RUST_BACKTRACE=${RUST_BACKTRACE:-}
+ healthcheck:
+ test: curl -f http://localhost:3210/version
+ interval: 5s
+ start_period: 5s
+
+ dashboard:
+ image: ghcr.io/get-convex/convex-dashboard:4499dd4fd7f2148687a7774599c613d052950f46
+ ports:
+ - "${DASHBOARD_PORT:-6791}:6791"
+ environment:
+ - NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://127.0.0.1:3210}
+ depends_on:
+ backend:
+ condition: service_healthy
+
+volumes:
+ data:
diff --git a/apps/dokploy/templates/convex/index.ts b/apps/dokploy/templates/convex/index.ts
new file mode 100644
index 00000000..badfe732
--- /dev/null
+++ b/apps/dokploy/templates/convex/index.ts
@@ -0,0 +1,38 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const dashboardDomain = generateRandomDomain(schema);
+ const backendDomain = generateRandomDomain(schema);
+ const actionsDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: dashboardDomain,
+ port: 6791,
+ serviceName: "dashboard",
+ },
+ {
+ host: backendDomain,
+ port: 3210,
+ serviceName: "backend",
+ },
+ {
+ host: actionsDomain,
+ port: 3211,
+ serviceName: "backend",
+ },
+ ];
+
+ const envs = [
+ `NEXT_PUBLIC_DEPLOYMENT_URL=http://${backendDomain}`,
+ `CONVEX_CLOUD_ORIGIN=http://${backendDomain}`,
+ `CONVEX_SITE_ORIGIN=http://${actionsDomain}`,
+ ];
+
+ return { envs, domains };
+}
diff --git a/apps/dokploy/templates/drawio/index.ts b/apps/dokploy/templates/drawio/index.ts
index e3c57c5a..701283c8 100644
--- a/apps/dokploy/templates/drawio/index.ts
+++ b/apps/dokploy/templates/drawio/index.ts
@@ -8,7 +8,7 @@ import {
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
- const secretKeyBase = generateBase64(64);
+ const _secretKeyBase = generateBase64(64);
const domains: DomainSchema[] = [
{
diff --git a/apps/dokploy/templates/excalidraw/index.ts b/apps/dokploy/templates/excalidraw/index.ts
index 13a43c44..7f73f395 100644
--- a/apps/dokploy/templates/excalidraw/index.ts
+++ b/apps/dokploy/templates/excalidraw/index.ts
@@ -2,7 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/ghost/index.ts b/apps/dokploy/templates/ghost/index.ts
index 1a88c362..052b7c6b 100644
--- a/apps/dokploy/templates/ghost/index.ts
+++ b/apps/dokploy/templates/ghost/index.ts
@@ -2,7 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/glance/docker-compose.yml b/apps/dokploy/templates/glance/docker-compose.yml
index e931d6e4..ace8bc94 100644
--- a/apps/dokploy/templates/glance/docker-compose.yml
+++ b/apps/dokploy/templates/glance/docker-compose.yml
@@ -2,7 +2,10 @@ services:
glance:
image: glanceapp/glance
volumes:
- - ../files/app/glance.yml:/app/glance.yml
+ - ../files/app/config/:/app/config
+ - ../files/app/assets:/app/assets
+ # Optionally, also mount docker socket if you want to use the docker containers widget
+ # - /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 8080
- restart: unless-stopped
+ env_file: .env
\ No newline at end of file
diff --git a/apps/dokploy/templates/glance/index.ts b/apps/dokploy/templates/glance/index.ts
index 4b229786..a0ab1b67 100644
--- a/apps/dokploy/templates/glance/index.ts
+++ b/apps/dokploy/templates/glance/index.ts
@@ -17,7 +17,7 @@ export function generate(schema: Schema): Template {
const mounts: Template["mounts"] = [
{
- filePath: "/app/glance.yml",
+ filePath: "/app/config/glance.yml",
content: `
branding:
hide-footer: true
diff --git a/apps/dokploy/templates/immich/index.ts b/apps/dokploy/templates/immich/index.ts
index b1b11afb..4beca87d 100644
--- a/apps/dokploy/templates/immich/index.ts
+++ b/apps/dokploy/templates/immich/index.ts
@@ -11,7 +11,7 @@ export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const dbPassword = generatePassword();
const dbUser = "immich";
- const appSecret = generateBase64(32);
+ const _appSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
diff --git a/apps/dokploy/templates/linkwarden/docker-compose.yml b/apps/dokploy/templates/linkwarden/docker-compose.yml
new file mode 100644
index 00000000..05ffb8a0
--- /dev/null
+++ b/apps/dokploy/templates/linkwarden/docker-compose.yml
@@ -0,0 +1,40 @@
+services:
+ linkwarden:
+ environment:
+ - NEXTAUTH_SECRET
+ - NEXTAUTH_URL
+ - DATABASE_URL=postgresql://linkwarden:${POSTGRES_PASSWORD}@postgres:5432/linkwarden
+ restart: unless-stopped
+ image: ghcr.io/linkwarden/linkwarden:v2.9.3
+ ports:
+ - 3000
+ volumes:
+ - linkwarden-data:/data/data
+ depends_on:
+ - postgres
+ healthcheck:
+ test: curl --fail http://localhost:3000 || exit 1
+ interval: 60s
+ retries: 2
+ start_period: 60s
+ timeout: 15s
+
+ postgres:
+ image: postgres:17-alpine
+ restart: unless-stopped
+ user: postgres
+ environment:
+ POSTGRES_USER: linkwarden
+ POSTGRES_DB: linkwarden
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ linkwarden-data:
+ postgres-data:
diff --git a/apps/dokploy/templates/linkwarden/index.ts b/apps/dokploy/templates/linkwarden/index.ts
new file mode 100644
index 00000000..86025035
--- /dev/null
+++ b/apps/dokploy/templates/linkwarden/index.ts
@@ -0,0 +1,33 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const postgresPassword = generatePassword();
+ const nextSecret = generateBase64(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "linkwarden",
+ },
+ ];
+
+ const envs = [
+ `POSTGRES_PASSWORD=${postgresPassword}`,
+ `NEXTAUTH_SECRET=${nextSecret}`,
+ `NEXTAUTH_URL=http://${mainDomain}/api/v1/auth`,
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/mailpit/docker-compose.yml b/apps/dokploy/templates/mailpit/docker-compose.yml
new file mode 100644
index 00000000..d0dbdb8e
--- /dev/null
+++ b/apps/dokploy/templates/mailpit/docker-compose.yml
@@ -0,0 +1,25 @@
+services:
+ mailpit:
+ image: axllent/mailpit:v1.22.3
+ restart: unless-stopped
+ ports:
+ - '1025:1025'
+ volumes:
+ - 'mailpit-data:/data'
+ environment:
+ - MP_SMTP_AUTH_ALLOW_INSECURE=true
+ - MP_MAX_MESSAGES=5000
+ - MP_DATABASE=/data/mailpit.db
+ - MP_UI_AUTH=${MP_UI_AUTH}
+ - MP_SMTP_AUTH=${MP_SMTP_AUTH}
+ healthcheck:
+ test:
+ - CMD
+ - /mailpit
+ - readyz
+ interval: 5s
+ timeout: 20s
+ retries: 10
+
+volumes:
+ mailpit-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/mailpit/index.ts b/apps/dokploy/templates/mailpit/index.ts
new file mode 100644
index 00000000..25f18f7e
--- /dev/null
+++ b/apps/dokploy/templates/mailpit/index.ts
@@ -0,0 +1,31 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 8025,
+ serviceName: "mailpit",
+ },
+ ];
+
+ const defaultPassword = generatePassword();
+
+ const envs = [
+ "# Uncomment below if you want basic auth on UI and SMTP",
+ `#MP_UI_AUTH=mailpit:${defaultPassword}`,
+ `#MP_SMTP_AUTH=mailpit:${defaultPassword}`,
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/outline/docker-compose.yml b/apps/dokploy/templates/outline/docker-compose.yml
new file mode 100644
index 00000000..aaf98ac0
--- /dev/null
+++ b/apps/dokploy/templates/outline/docker-compose.yml
@@ -0,0 +1,57 @@
+services:
+ outline:
+ image: outlinewiki/outline:0.82.0
+ restart: always
+ depends_on:
+ - postgres
+ - redis
+ - dex
+ ports:
+ - 3000
+ environment:
+ NODE_ENV: production
+ URL: ${URL}
+ FORCE_HTTPS: 'false'
+ SECRET_KEY: ${SECRET_KEY}
+ UTILS_SECRET: ${UTILS_SECRET}
+ DATABASE_URL: postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline
+ PGSSLMODE: disable
+ REDIS_URL: redis://redis:6379
+ OIDC_CLIENT_ID: outline
+ OIDC_CLIENT_SECRET: ${CLIENT_SECRET}
+ OIDC_AUTH_URI: ${DEX_URL}/auth
+ OIDC_TOKEN_URI: ${DEX_URL}/token
+ OIDC_USERINFO_URI: ${DEX_URL}/userinfo
+
+ dex:
+ image: ghcr.io/dexidp/dex:v2.37.0
+ restart: always
+ volumes:
+ - ../files/etc/dex/config.yaml:/etc/dex/config.yaml
+ command:
+ - dex
+ - serve
+ - /etc/dex/config.yaml
+ ports:
+ - 5556
+
+ postgres:
+ image: postgres:15
+ restart: always
+ environment:
+ POSTGRES_DB: outline
+ POSTGRES_USER: outline
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ volumes:
+ - postgres_data-test-outline-khufpx:/var/lib/postgresql/data
+
+ redis:
+ image: redis:latest
+ restart: always
+ command: redis-server --appendonly yes
+ volumes:
+ - redis_data-test-outline-khufpx:/data
+
+volumes:
+ postgres_data-test-outline-khufpx:
+ redis_data-test-outline-khufpx:
\ No newline at end of file
diff --git a/apps/dokploy/templates/outline/index.ts b/apps/dokploy/templates/outline/index.ts
new file mode 100644
index 00000000..8431e568
--- /dev/null
+++ b/apps/dokploy/templates/outline/index.ts
@@ -0,0 +1,90 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const dexDomain = generateRandomDomain(schema);
+ const SECRET_KEY = generateBase64(32);
+ const UTILS_SECRET = generateBase64(32);
+ const CLIENT_SECRET = generateBase64(32);
+ const POSTGRES_PASSWORD = generatePassword();
+
+ const mainURL = `http://${mainDomain}`;
+ const dexURL = `http://${dexDomain}`;
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "outline",
+ },
+ {
+ host: dexDomain,
+ port: 5556,
+ serviceName: "dex",
+ },
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/etc/dex/config.yaml",
+ content: `issuer: ${dexURL}
+
+web:
+ http: 0.0.0.0:5556
+
+storage:
+ type: memory
+
+enablePasswordDB: true
+
+frontend:
+ issuer: Outline
+
+logger:
+ level: debug
+
+staticPasswords:
+ - email: "admin@example.com"
+ # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
+ hash: "$2y$10$jsRWHw54uxTUIfhjgUrB9u8HSzPk7TUuQri9sXZrKzRXcScvwYor."
+ username: "admin"
+ userID: "1"
+
+
+oauth2:
+ skipApprovalScreen: true
+ alwaysShowLoginScreen: false
+ passwordConnector: local
+
+staticClients:
+ - id: "outline"
+ redirectURIs:
+ - ${mainURL}/auth/oidc.callback
+ name: "Outline"
+ secret: "${CLIENT_SECRET}"`,
+ },
+ ];
+
+ const envs = [
+ `URL=${mainURL}`,
+ `DEX_URL=${dexURL}`,
+ `DOMAIN_NAME=${mainDomain}`,
+ `POSTGRES_PASSWORD=${POSTGRES_PASSWORD}`,
+ `SECRET_KEY=${SECRET_KEY}`,
+ `UTILS_SECRET=${UTILS_SECRET}`,
+ `CLIENT_SECRET=${CLIENT_SECRET}`,
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/penpot/index.ts b/apps/dokploy/templates/penpot/index.ts
index f657c698..a3e90e8a 100644
--- a/apps/dokploy/templates/penpot/index.ts
+++ b/apps/dokploy/templates/penpot/index.ts
@@ -2,8 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateBase64,
- generatePassword,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/photoprism/index.ts b/apps/dokploy/templates/photoprism/index.ts
index d20ac29c..4a103a62 100644
--- a/apps/dokploy/templates/photoprism/index.ts
+++ b/apps/dokploy/templates/photoprism/index.ts
@@ -2,7 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/plausible/docker-compose.yml b/apps/dokploy/templates/plausible/docker-compose.yml
index f17bdfa3..ad483ecf 100644
--- a/apps/dokploy/templates/plausible/docker-compose.yml
+++ b/apps/dokploy/templates/plausible/docker-compose.yml
@@ -1,4 +1,3 @@
-version: "3.8"
services:
plausible_db:
image: postgres:16-alpine
@@ -24,7 +23,7 @@ services:
hard: 262144
plausible:
- image: ghcr.io/plausible/community-edition:v2.1.4
+ image: ghcr.io/plausible/community-edition:v2.1.5
restart: always
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
depends_on:
diff --git a/apps/dokploy/templates/pocket-id/docker-compose.yml b/apps/dokploy/templates/pocket-id/docker-compose.yml
new file mode 100644
index 00000000..f9385143
--- /dev/null
+++ b/apps/dokploy/templates/pocket-id/docker-compose.yml
@@ -0,0 +1,21 @@
+services:
+ pocket-id:
+ image: ghcr.io/pocket-id/pocket-id:v0.35.1
+ restart: unless-stopped
+ environment:
+ - PUBLIC_UI_CONFIG_DISABLED
+ - PUBLIC_APP_URL
+ - TRUST_PROXY
+ ports:
+ - 80
+ volumes:
+ - pocket-id-data:/app/backend/data
+ healthcheck:
+ test: "curl -f http://localhost/health"
+ interval: 1m30s
+ timeout: 5s
+ retries: 2
+ start_period: 10s
+
+volumes:
+ pocket-id-data:
diff --git a/apps/dokploy/templates/pocket-id/index.ts b/apps/dokploy/templates/pocket-id/index.ts
new file mode 100644
index 00000000..9a9faa2a
--- /dev/null
+++ b/apps/dokploy/templates/pocket-id/index.ts
@@ -0,0 +1,29 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "pocket-id",
+ },
+ ];
+
+ const envs = [
+ "PUBLIC_UI_CONFIG_DISABLED=false",
+ `PUBLIC_APP_URL=http://${mainDomain}`,
+ "TRUST_PROXY=true",
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/registry/docker-compose.yml b/apps/dokploy/templates/registry/docker-compose.yml
new file mode 100644
index 00000000..08c5c368
--- /dev/null
+++ b/apps/dokploy/templates/registry/docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+ registry:
+ restart: always
+ image: registry:2
+ ports:
+ - 5000
+ volumes:
+ - ../files/auth/registry.password:/auth/registry.password
+ - registry-data:/var/lib/registry
+ environment:
+ REGISTRY_STORAGE_DELETE_ENABLED: true
+ REGISTRY_HEALTH_STORAGEDRIVER_ENABLED: false
+ REGISTRY_HTTP_SECRET: ${REGISTRY_HTTP_SECRET}
+ REGISTRY_AUTH: htpasswd
+ REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
+ REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
+
+volumes:
+ registry-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/registry/index.ts b/apps/dokploy/templates/registry/index.ts
new file mode 100644
index 00000000..81965e6e
--- /dev/null
+++ b/apps/dokploy/templates/registry/index.ts
@@ -0,0 +1,35 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 5000,
+ serviceName: "registry",
+ },
+ ];
+
+ const registryHttpSecret = generatePassword(30);
+
+ const envs = [`REGISTRY_HTTP_SECRET=${registryHttpSecret}`];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/auth/registry.password",
+ content:
+ "# from: docker run --rm --entrypoint htpasswd httpd:2 -Bbn docker password\ndocker:$2y$10$qWZoWev/u5PV7WneFoRAMuoGpRcAQOgUuIIdLnU7pJXogrBSY23/2\n",
+ },
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/superset/docker-compose.yml b/apps/dokploy/templates/superset/docker-compose.yml
index 8dd1cd2d..b73bf55e 100644
--- a/apps/dokploy/templates/superset/docker-compose.yml
+++ b/apps/dokploy/templates/superset/docker-compose.yml
@@ -1,5 +1,8 @@
-# Note: this is an UNOFFICIAL production docker image build for Superset:
+# This is an UNOFFICIAL production docker image build for Superset:
# - https://github.com/amancevice/docker-superset
+
+
+# ## SETUP INSTRUCTIONS
#
# After deploying this image, you will need to run one of the two
# commands below in a terminal within the superset container:
@@ -7,11 +10,30 @@
# $ superset-init # Initialise database only
#
# You will be prompted to enter the credentials for the admin user.
+
+
+# ## NETWORK INSTRUCTIONS
+#
+# If you want to connect superset with other internal databases managed by
+# Dokploy (on dokploy-network) using internal hostnames, you will need to
+# uncomment the `networks` section, both for the superset container and
+# at the very bottom of this docker-compose template.
+#
+# Note that the `superset` service name/hostname will not be unique on the
+# global `dokploy-network`. If you plan to:
+#
+# 1. deploy a second instance of superset on dokploy-network, and
+# 2. have other containers on dokploy-network utilise the second instance's
+# Superset API (https://superset.apache.org/docs/api)
+#
+# Please change the service name of the second instance.
services:
superset:
image: amancevice/superset
restart: always
+ #networks:
+ # - dokploy-network
depends_on:
- superset_postgres
- superset_redis
@@ -44,8 +66,7 @@ services:
timeout: 10s
retries: 3
-
- superset_redis:
+superset_redis:
image: redis
restart: always
volumes:
@@ -57,6 +78,9 @@ services:
timeout: 10s
retries: 3
+#networks:
+# dokploy-network:
+# external: true
volumes:
superset_postgres_data:
diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts
index 8143bbb2..d39465a8 100644
--- a/apps/dokploy/templates/templates.ts
+++ b/apps/dokploy/templates/templates.ts
@@ -1,6 +1,37 @@
import type { TemplateData } from "./types/templates-data.type";
export const templates: TemplateData[] = [
+ {
+ id: "appwrite",
+ name: "Appwrite",
+ version: "1.6.0",
+ description:
+ "Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.\n" +
+ "Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and more services.",
+ links: {
+ github: "https://github.com/appwrite/appwrite",
+ website: "https://appwrite.io/",
+ docs: "https://appwrite.io/docs",
+ },
+ logo: "appwrite.svg",
+ tags: ["database", "firebase", "postgres"],
+ load: () => import("./appwrite/index").then((m) => m.generate),
+ },
+ {
+ id: "outline",
+ name: "Outline",
+ version: "0.82.0",
+ description:
+ "Outline is a self-hosted knowledge base and documentation platform that allows you to build and manage your own knowledge base applications.",
+ links: {
+ github: "https://github.com/outline/outline",
+ website: "https://getoutline.com/",
+ docs: "https://docs.getoutline.com/s/guide",
+ },
+ logo: "outline.png",
+ load: () => import("./outline/index").then((m) => m.generate),
+ tags: ["documentation", "knowledge-base", "self-hosted"],
+ },
{
id: "supabase",
name: "SupaBase",
@@ -34,7 +65,7 @@ export const templates: TemplateData[] = [
{
id: "plausible",
name: "Plausible",
- version: "v2.1.4",
+ version: "v2.1.5",
description:
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
logo: "plausible.svg",
@@ -362,6 +393,21 @@ export const templates: TemplateData[] = [
tags: ["chat"],
load: () => import("./open-webui/index").then((m) => m.generate),
},
+ {
+ id: "mailpit",
+ name: "Mailpit",
+ version: "v1.22.3",
+ description:
+ "Mailpit is a tiny, self-contained, and secure email & SMTP testing tool with API for developers.",
+ logo: "mailpit.svg",
+ links: {
+ github: "https://github.com/axllent/mailpit",
+ website: "https://mailpit.axllent.org/",
+ docs: "https://mailpit.axllent.org/docs/",
+ },
+ tags: ["email", "smtp"],
+ load: () => import("./mailpit/index").then((m) => m.generate),
+ },
{
id: "listmonk",
name: "Listmonk",
@@ -395,7 +441,7 @@ export const templates: TemplateData[] = [
{
id: "umami",
name: "Umami",
- version: "v2.14.0",
+ version: "v2.16.1",
description:
"Umami is a simple, fast, privacy-focused alternative to Google Analytics.",
logo: "umami.png",
@@ -631,6 +677,21 @@ export const templates: TemplateData[] = [
tags: ["open-source"],
load: () => import("./vaultwarden/index").then((m) => m.generate),
},
+ {
+ id: "linkwarden",
+ name: "Linkwarden",
+ version: "2.9.3",
+ description:
+ "Self-hosted, open-source collaborative bookmark manager to collect, organize and archive webpages.",
+ logo: "linkwarden.png",
+ links: {
+ github: "https://github.com/linkwarden/linkwarden",
+ website: "https://linkwarden.app/",
+ docs: "https://docs.linkwarden.app/",
+ },
+ tags: ["bookmarks", "link-sharing"],
+ load: () => import("./linkwarden/index").then((m) => m.generate),
+ },
{
id: "hi-events",
name: "Hi.events",
@@ -1062,6 +1123,21 @@ export const templates: TemplateData[] = [
tags: ["identity", "auth"],
load: () => import("./logto/index").then((m) => m.generate),
},
+ {
+ id: "pocket-id",
+ name: "Pocket ID",
+ version: "0.35.1",
+ description:
+ "A simple and easy-to-use OIDC provider that allows users to authenticate with their passkeys to your services.",
+ logo: "pocket-id.svg",
+ links: {
+ github: "https://github.com/pocket-id/pocket-id",
+ website: "https://pocket-id.org/",
+ docs: "https://pocket-id.org/docs",
+ },
+ tags: ["identity", "auth"],
+ load: () => import("./pocket-id/index").then((m) => m.generate),
+ },
{
id: "penpot",
name: "Penpot",
@@ -1408,6 +1484,21 @@ export const templates: TemplateData[] = [
tags: ["file-manager", "vdfs", "storage"],
load: () => import("./spacedrive/index").then((m) => m.generate),
},
+ {
+ id: "registry",
+ name: "Docker Registry",
+ version: "2",
+ description:
+ "Distribution implementation for storing and distributing of Docker container images and artifacts.",
+ links: {
+ github: "https://github.com/distribution/distribution",
+ website: "https://hub.docker.com/_/registry",
+ docs: "https://distribution.github.io/distribution/",
+ },
+ logo: "registry.png",
+ tags: ["registry", "docker", "self-hosted"],
+ load: () => import("./registry/index").then((m) => m.generate),
+ },
{
id: "alist",
name: "AList",
@@ -1483,4 +1574,48 @@ export const templates: TemplateData[] = [
tags: ["forms", "analytics"],
load: () => import("./formbricks/index").then((m) => m.generate),
},
+ {
+ id: "trilium",
+ name: "Trilium",
+ description:
+ "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
+ logo: "trilium.png",
+ version: "latest",
+ links: {
+ github: "https://github.com/zadam/trilium",
+ website: "https://github.com/zadam/trilium",
+ docs: "https://github.com/zadam/trilium/wiki/",
+ },
+ tags: ["self-hosted", "productivity", "personal-use"],
+ load: () => import("./trilium/index").then((m) => m.generate),
+ },
+ {
+ id: "convex",
+ name: "Convex",
+ version: "latest",
+ description:
+ "Convex is an open-source reactive database designed to make life easy for web app developers.",
+ logo: "convex.svg",
+ links: {
+ github: "https://github.com/get-convex/convex",
+ website: "https://www.convex.dev/",
+ docs: "https://www.convex.dev/docs",
+ },
+ tags: ["backend", "database", "api"],
+ load: () => import("./convex/index").then((m) => m.generate),
+ },
+ {
+ id: "wikijs",
+ name: "Wiki.js",
+ version: "2.5",
+ description: "The most powerful and extensible open source Wiki software.",
+ logo: "wikijs.svg",
+ links: {
+ github: "https://github.com/requarks/wiki",
+ website: "https://js.wiki/",
+ docs: "https://docs.requarks.io/",
+ },
+ tags: ["knowledge-base", "self-hosted", "documentation"],
+ load: () => import("./wikijs/index").then((m) => m.generate),
+ },
];
diff --git a/apps/dokploy/templates/triggerdotdev/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts
index 7b894acb..c11c708b 100644
--- a/apps/dokploy/templates/triggerdotdev/index.ts
+++ b/apps/dokploy/templates/triggerdotdev/index.ts
@@ -1,4 +1,3 @@
-import { Secrets } from "@/components/ui/secrets";
import {
type DomainSchema,
type Schema,
diff --git a/apps/dokploy/templates/trilium/docker-compose.yml b/apps/dokploy/templates/trilium/docker-compose.yml
new file mode 100644
index 00000000..f549d820
--- /dev/null
+++ b/apps/dokploy/templates/trilium/docker-compose.yml
@@ -0,0 +1,14 @@
+services:
+ trilium:
+ image: zadam/trilium:latest
+ ports:
+ - 8080
+ networks:
+ - dokploy-network
+ restart: always
+ volumes:
+ - /root/trilium-backups:/home/node/trilium-data/backup
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/apps/dokploy/templates/trilium/index.ts b/apps/dokploy/templates/trilium/index.ts
new file mode 100644
index 00000000..acac9841
--- /dev/null
+++ b/apps/dokploy/templates/trilium/index.ts
@@ -0,0 +1,22 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const triliumDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: triliumDomain,
+ port: 8080,
+ serviceName: "trilium",
+ },
+ ];
+
+ return {
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/umami/docker-compose.yml b/apps/dokploy/templates/umami/docker-compose.yml
index 87568165..26efd337 100644
--- a/apps/dokploy/templates/umami/docker-compose.yml
+++ b/apps/dokploy/templates/umami/docker-compose.yml
@@ -1,6 +1,6 @@
services:
umami:
- image: ghcr.io/umami-software/umami:postgresql-v2.14.0
+ image: ghcr.io/umami-software/umami:postgresql-v2.16.1
restart: always
healthcheck:
test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"]
diff --git a/apps/dokploy/templates/unifi/index.ts b/apps/dokploy/templates/unifi/index.ts
index 975ce63d..ea67b0fa 100644
--- a/apps/dokploy/templates/unifi/index.ts
+++ b/apps/dokploy/templates/unifi/index.ts
@@ -1,6 +1,6 @@
import type { Schema, Template } from "../utils";
-export function generate(schema: Schema): Template {
+export function generate(_schema: Schema): Template {
const mounts: Template["mounts"] = [
{
filePath: "init-mongo.sh",
diff --git a/apps/dokploy/templates/unsend/index.ts b/apps/dokploy/templates/unsend/index.ts
index 1c4c9c71..dcc80f66 100644
--- a/apps/dokploy/templates/unsend/index.ts
+++ b/apps/dokploy/templates/unsend/index.ts
@@ -3,7 +3,6 @@ import {
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/utils/index.ts b/apps/dokploy/templates/utils/index.ts
index b5369b91..941afc80 100644
--- a/apps/dokploy/templates/utils/index.ts
+++ b/apps/dokploy/templates/utils/index.ts
@@ -12,7 +12,9 @@ export interface Schema {
projectName: string;
}
-export type DomainSchema = Pick;
+export type DomainSchema = Pick & {
+ path?: string;
+};
export interface Template {
envs?: string[];
diff --git a/apps/dokploy/templates/wikijs/docker-compose.yml b/apps/dokploy/templates/wikijs/docker-compose.yml
new file mode 100644
index 00000000..6b21423d
--- /dev/null
+++ b/apps/dokploy/templates/wikijs/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3.5'
+services:
+ wiki:
+ image: ghcr.io/requarks/wiki:2.5
+ restart: unless-stopped
+ environment:
+ - DB_TYPE
+ - DB_HOST
+ - DB_PORT
+ - DB_USER
+ - DB_PASS
+ - DB_NAME
+ depends_on:
+ - db
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=wikijs
+ db:
+ image: postgres:14
+ restart: unless-stopped
+ environment:
+ - POSTGRES_USER
+ - POSTGRES_PASSWORD
+ - POSTGRES_DB
+ volumes:
+ - wiki-db-data:/var/lib/postgresql/data
+networks:
+ dokploy-network:
+ external: true
+volumes:
+ wiki-db-data:
diff --git a/apps/dokploy/templates/wikijs/index.ts b/apps/dokploy/templates/wikijs/index.ts
new file mode 100644
index 00000000..ff6c234d
--- /dev/null
+++ b/apps/dokploy/templates/wikijs/index.ts
@@ -0,0 +1,35 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 3000,
+ serviceName: "wiki",
+ },
+ ];
+
+ const envs = [
+ "# Database Setup",
+ "POSTGRES_USER=wikijs",
+ "POSTGRES_PASSWORD=wikijsrocks",
+ "POSTGRES_DB=wiki",
+ "# WikiJS Database Connection",
+ "DB_TYPE=postgres",
+ "DB_HOST=db",
+ "DB_PORT=5432",
+ "DB_USER=wikijs",
+ "DB_PASS=wikijsrocks",
+ "DB_NAME=wiki",
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/schedules/tsconfig.json b/apps/schedules/tsconfig.json
index 3c0b02bc..3d4adb16 100644
--- a/apps/schedules/tsconfig.json
+++ b/apps/schedules/tsconfig.json
@@ -7,7 +7,8 @@
"skipLibCheck": true,
"outDir": "dist",
"jsx": "react-jsx",
- "jsxImportSource": "hono/jsx"
+ "jsxImportSource": "hono/jsx",
+ "declaration": false
},
"exclude": ["node_modules", "dist"]
}
diff --git a/biome.json b/biome.json
index f5a6c232..cf677ec4 100644
--- a/biome.json
+++ b/biome.json
@@ -24,7 +24,10 @@
},
"correctness": {
"useExhaustiveDependencies": "off",
- "noUnsafeOptionalChaining": "off"
+ "noUnsafeOptionalChaining": "off",
+ "noUnusedImports": "error",
+ "noUnusedFunctionParameters": "error",
+ "noUnusedVariables": "error"
},
"style": {
"noNonNullAssertion": "off"
diff --git a/lefthook.yml b/lefthook.yml
index 1a491cd8..3f5a6d09 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -37,9 +37,9 @@
commit-msg:
commands:
commitlint:
- run: "npx commitlint --edit $1"
+ # run: "npx commitlint --edit $1"
pre-commit:
commands:
check:
- run: "pnpm check"
+ # run: "pnpm check"
diff --git a/packages/server/auth-schema.ts b/packages/server/auth-schema.ts
new file mode 100644
index 00000000..a5829046
--- /dev/null
+++ b/packages/server/auth-schema.ts
@@ -0,0 +1,133 @@
+// import {
+// pgTable,
+// text,
+// integer,
+// timestamp,
+// boolean,
+// } from "drizzle-orm/pg-core";
+
+// export const users_temp = pgTable("users_temp", {
+// id: text("id").primaryKey(),
+// name: text("name").notNull(),
+// email: text("email").notNull().unique(),
+// emailVerified: boolean("email_verified").notNull(),
+// image: text("image"),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// twoFactorEnabled: boolean("two_factor_enabled"),
+// role: text("role"),
+// ownerId: text("owner_id"),
+// });
+
+// export const session = pgTable("session", {
+// id: text("id").primaryKey(),
+// expiresAt: timestamp("expires_at").notNull(),
+// token: text("token").notNull().unique(),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// ipAddress: text("ip_address"),
+// userAgent: text("user_agent"),
+// userId: text("user_id")
+// .notNull()
+// .references(() => users_temp.id, { onDelete: "cascade" }),
+// activeOrganizationId: text("active_organization_id"),
+// });
+
+// export const account = pgTable("account", {
+// id: text("id").primaryKey(),
+// accountId: text("account_id").notNull(),
+// providerId: text("provider_id").notNull(),
+// userId: text("user_id")
+// .notNull()
+// .references(() => users_temp.id, { onDelete: "cascade" }),
+// accessToken: text("access_token"),
+// refreshToken: text("refresh_token"),
+// idToken: text("id_token"),
+// accessTokenExpiresAt: timestamp("access_token_expires_at"),
+// refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+// scope: text("scope"),
+// password: text("password"),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// });
+
+// export const verification = pgTable("verification", {
+// id: text("id").primaryKey(),
+// identifier: text("identifier").notNull(),
+// value: text("value").notNull(),
+// expiresAt: timestamp("expires_at").notNull(),
+// createdAt: timestamp("created_at"),
+// updatedAt: timestamp("updated_at"),
+// });
+
+// export const apikey = pgTable("apikey", {
+// id: text("id").primaryKey(),
+// name: text("name"),
+// start: text("start"),
+// prefix: text("prefix"),
+// key: text("key").notNull(),
+// userId: text("user_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// refillInterval: integer("refill_interval"),
+// refillAmount: integer("refill_amount"),
+// lastRefillAt: timestamp("last_refill_at"),
+// enabled: boolean("enabled"),
+// rateLimitEnabled: boolean("rate_limit_enabled"),
+// rateLimitTimeWindow: integer("rate_limit_time_window"),
+// rateLimitMax: integer("rate_limit_max"),
+// requestCount: integer("request_count"),
+// remaining: integer("remaining"),
+// lastRequest: timestamp("last_request"),
+// expiresAt: timestamp("expires_at"),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// permissions: text("permissions"),
+// metadata: text("metadata"),
+// });
+
+// export const twoFactor = pgTable("two_factor", {
+// id: text("id").primaryKey(),
+// secret: text("secret").notNull(),
+// backupCodes: text("backup_codes").notNull(),
+// userId: text("user_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// });
+
+// export const organization = pgTable("organization", {
+// id: text("id").primaryKey(),
+// name: text("name").notNull(),
+// slug: text("slug").unique(),
+// logo: text("logo"),
+// createdAt: timestamp("created_at").notNull(),
+// metadata: text("metadata"),
+// });
+
+// export const member = pgTable("member", {
+// id: text("id").primaryKey(),
+// organizationId: text("organization_id")
+// .notNull()
+// .references(() => organization.id, { onDelete: "cascade" }),
+// userId: text("user_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// role: text("role").notNull(),
+// teamId: text("team_id"),
+// createdAt: timestamp("created_at").notNull(),
+// });
+
+// export const invitation = pgTable("invitation", {
+// id: text("id").primaryKey(),
+// organizationId: text("organization_id")
+// .notNull()
+// .references(() => organization.id, { onDelete: "cascade" }),
+// email: text("email").notNull(),
+// role: text("role"),
+// teamId: text("team_id"),
+// status: text("status").notNull(),
+// expiresAt: timestamp("expires_at").notNull(),
+// inviterId: text("inviter_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// });
diff --git a/packages/server/package.json b/packages/server/package.json
index cfff36fe..d99f5c24 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -28,6 +28,11 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
+ "@better-auth/utils":"0.2.3",
+ "@oslojs/encoding":"1.1.0",
+ "@oslojs/crypto":"1.0.1",
+ "drizzle-dbml-generator":"0.10.0",
+ "better-auth":"1.2.0",
"rotating-file-stream": "3.2.3",
"@faker-js/faker": "^8.4.1",
"@lucia-auth/adapter-drizzle": "1.0.7",
diff --git a/packages/server/src/auth/auth.ts b/packages/server/src/auth/auth.ts
deleted file mode 100644
index ab340d0a..00000000
--- a/packages/server/src/auth/auth.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import type { IncomingMessage, ServerResponse } from "node:http";
-import { findAdminByAuthId } from "@dokploy/server/services/admin";
-import { findUserByAuthId } from "@dokploy/server/services/user";
-import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
-import { TimeSpan } from "lucia";
-import { Lucia } from "lucia/dist/core.js";
-import type { Session, User } from "lucia/dist/core.js";
-import { db } from "../db";
-import { type DatabaseUser, auth, sessionTable } from "../db/schema";
-
-export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
-
-export const lucia = new Lucia(adapter, {
- sessionCookie: {
- attributes: {
- secure: false,
- },
- },
- sessionExpiresIn: new TimeSpan(1, "d"),
- getUserAttributes: (attributes) => {
- return {
- email: attributes.email,
- rol: attributes.rol,
- secret: attributes.secret !== null,
- adminId: attributes.adminId,
- };
- },
-});
-
-declare module "lucia" {
- interface Register {
- Lucia: typeof lucia;
- DatabaseUserAttributes: Omit & {
- authId: string;
- adminId: string;
- };
- }
-}
-
-export type ReturnValidateToken = Promise<{
- user: (User & { authId: string; adminId: string }) | null;
- session: Session | null;
-}>;
-
-export async function validateRequest(
- req: IncomingMessage,
- res: ServerResponse,
-): ReturnValidateToken {
- const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
-
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await lucia.validateSession(sessionId);
- if (result?.session?.fresh) {
- res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(result.session.id).serialize(),
- );
- }
- if (!result.session) {
- res.appendHeader(
- "Set-Cookie",
- lucia.createBlankSessionCookie().serialize(),
- );
- }
- if (result.user) {
- try {
- if (result.user?.rol === "admin") {
- const admin = await findAdminByAuthId(result.user.id);
- result.user.adminId = admin.adminId;
- } else if (result.user?.rol === "user") {
- const userResult = await findUserByAuthId(result.user.id);
- result.user.adminId = userResult.adminId;
- }
- } catch (error) {
- return {
- user: null,
- session: null,
- };
- }
- }
-
- return {
- session: result.session,
- ...((result.user && {
- user: {
- authId: result.user.id,
- email: result.user.email,
- rol: result.user.rol,
- id: result.user.id,
- secret: result.user.secret,
- adminId: result.user.adminId,
- },
- }) || {
- user: null,
- }),
- };
-}
-
-export async function validateWebSocketRequest(
- req: IncomingMessage,
-): Promise<{ user: User; session: Session } | { user: null; session: null }> {
- const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
-
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await lucia.validateSession(sessionId);
- return result;
-}
diff --git a/packages/server/src/auth/token.ts b/packages/server/src/auth/token.ts
deleted file mode 100644
index f29d4dbd..00000000
--- a/packages/server/src/auth/token.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import type { IncomingMessage } from "node:http";
-import { TimeSpan } from "lucia";
-import { Lucia } from "lucia/dist/core.js";
-import { findAdminByAuthId } from "../services/admin";
-import { findUserByAuthId } from "../services/user";
-import { type ReturnValidateToken, adapter } from "./auth";
-
-export const luciaToken = new Lucia(adapter, {
- sessionCookie: {
- attributes: {
- secure: false,
- },
- },
- sessionExpiresIn: new TimeSpan(365, "d"),
- getUserAttributes: (attributes) => {
- return {
- email: attributes.email,
- rol: attributes.rol,
- secret: attributes.secret !== null,
- };
- },
-});
-
-export const validateBearerToken = async (
- req: IncomingMessage,
-): ReturnValidateToken => {
- const authorizationHeader = req.headers.authorization;
- const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await luciaToken.validateSession(sessionId);
-
- if (result.user) {
- if (result.user?.rol === "admin") {
- const admin = await findAdminByAuthId(result.user.id);
- result.user.adminId = admin.adminId;
- } else if (result.user?.rol === "user") {
- const userResult = await findUserByAuthId(result.user.id);
- result.user.adminId = userResult.adminId;
- }
- }
- return {
- session: result.session,
- ...((result.user && {
- user: {
- adminId: result.user.adminId,
- authId: result.user.id,
- email: result.user.email,
- rol: result.user.rol,
- id: result.user.id,
- secret: result.user.secret,
- },
- }) || {
- user: null,
- }),
- };
-};
-
-export const validateBearerTokenAPI = async (
- authorizationHeader: string,
-): ReturnValidateToken => {
- const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await luciaToken.validateSession(sessionId);
-
- if (result.user) {
- if (result.user?.rol === "admin") {
- const admin = await findAdminByAuthId(result.user.id);
- result.user.adminId = admin.adminId;
- } else if (result.user?.rol === "user") {
- const userResult = await findUserByAuthId(result.user.id);
- result.user.adminId = userResult.adminId;
- }
- }
- return {
- session: result.session,
- ...((result.user && {
- user: {
- adminId: result.user.adminId,
- authId: result.user.id,
- email: result.user.email,
- rol: result.user.rol,
- id: result.user.id,
- secret: result.user.secret,
- },
- }) || {
- user: null,
- }),
- };
-};
diff --git a/packages/server/src/db/schema/account.ts b/packages/server/src/db/schema/account.ts
new file mode 100644
index 00000000..8291ea4d
--- /dev/null
+++ b/packages/server/src/db/schema/account.ts
@@ -0,0 +1,194 @@
+import { relations, sql } from "drizzle-orm";
+import {
+ boolean,
+ integer,
+ pgTable,
+ text,
+ timestamp,
+} from "drizzle-orm/pg-core";
+import { nanoid } from "nanoid";
+import { projects } from "./project";
+import { server } from "./server";
+import { users_temp } from "./user";
+
+export const account = pgTable("account", {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ accountId: text("account_id")
+ .notNull()
+ .$defaultFn(() => nanoid()),
+ providerId: text("provider_id").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ accessToken: text("access_token"),
+ refreshToken: text("refresh_token"),
+ idToken: text("id_token"),
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+ scope: text("scope"),
+ password: text("password"),
+ is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ resetPasswordToken: text("resetPasswordToken"),
+ resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
+ confirmationToken: text("confirmationToken"),
+ confirmationExpiresAt: text("confirmationExpiresAt"),
+});
+
+export const accountRelations = relations(account, ({ one }) => ({
+ user: one(users_temp, {
+ fields: [account.userId],
+ references: [users_temp.id],
+ }),
+}));
+
+export const verification = pgTable("verification", {
+ id: text("id").primaryKey(),
+ identifier: text("identifier").notNull(),
+ value: text("value").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ createdAt: timestamp("created_at"),
+ updatedAt: timestamp("updated_at"),
+});
+
+export const organization = pgTable("organization", {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ name: text("name").notNull(),
+ slug: text("slug").unique(),
+ logo: text("logo"),
+ createdAt: timestamp("created_at").notNull(),
+ metadata: text("metadata"),
+ ownerId: text("owner_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+});
+
+export const organizationRelations = relations(
+ organization,
+ ({ one, many }) => ({
+ owner: one(users_temp, {
+ fields: [organization.ownerId],
+ references: [users_temp.id],
+ }),
+ servers: many(server),
+ projects: many(projects),
+ members: many(member),
+ }),
+);
+
+export const member = pgTable("member", {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ organizationId: text("organization_id")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ role: text("role").notNull().$type<"owner" | "member" | "admin">(),
+ createdAt: timestamp("created_at").notNull(),
+ teamId: text("team_id"),
+ // Permissions
+ canCreateProjects: boolean("canCreateProjects").notNull().default(false),
+ canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
+ canCreateServices: boolean("canCreateServices").notNull().default(false),
+ canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
+ canDeleteServices: boolean("canDeleteServices").notNull().default(false),
+ canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
+ canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
+ canAccessToGitProviders: boolean("canAccessToGitProviders")
+ .notNull()
+ .default(false),
+ canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
+ .notNull()
+ .default(false),
+ accessedProjects: text("accesedProjects")
+ .array()
+ .notNull()
+ .default(sql`ARRAY[]::text[]`),
+ accessedServices: text("accesedServices")
+ .array()
+ .notNull()
+ .default(sql`ARRAY[]::text[]`),
+});
+
+export const memberRelations = relations(member, ({ one }) => ({
+ organization: one(organization, {
+ fields: [member.organizationId],
+ references: [organization.id],
+ }),
+ user: one(users_temp, {
+ fields: [member.userId],
+ references: [users_temp.id],
+ }),
+}));
+
+export const invitation = pgTable("invitation", {
+ id: text("id").primaryKey(),
+ organizationId: text("organization_id")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
+ email: text("email").notNull(),
+ role: text("role").$type<"owner" | "member" | "admin">(),
+ status: text("status").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ inviterId: text("inviter_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ teamId: text("team_id"),
+});
+
+export const invitationRelations = relations(invitation, ({ one }) => ({
+ organization: one(organization, {
+ fields: [invitation.organizationId],
+ references: [organization.id],
+ }),
+}));
+
+export const twoFactor = pgTable("two_factor", {
+ id: text("id").primaryKey(),
+ secret: text("secret").notNull(),
+ backupCodes: text("backup_codes").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+});
+
+export const apikey = pgTable("apikey", {
+ id: text("id").primaryKey(),
+ name: text("name"),
+ start: text("start"),
+ prefix: text("prefix"),
+ key: text("key").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ refillInterval: integer("refill_interval"),
+ refillAmount: integer("refill_amount"),
+ lastRefillAt: timestamp("last_refill_at"),
+ enabled: boolean("enabled"),
+ rateLimitEnabled: boolean("rate_limit_enabled"),
+ rateLimitTimeWindow: integer("rate_limit_time_window"),
+ rateLimitMax: integer("rate_limit_max"),
+ requestCount: integer("request_count"),
+ remaining: integer("remaining"),
+ lastRequest: timestamp("last_request"),
+ expiresAt: timestamp("expires_at"),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ permissions: text("permissions"),
+ metadata: text("metadata"),
+});
+
+export const apikeyRelations = relations(apikey, ({ one }) => ({
+ user: one(users_temp, {
+ fields: [apikey.userId],
+ references: [users_temp.id],
+ }),
+}));
diff --git a/packages/server/src/db/schema/admin.ts b/packages/server/src/db/schema/admin.ts
deleted file mode 100644
index 983f99fd..00000000
--- a/packages/server/src/db/schema/admin.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import { relations } from "drizzle-orm";
-import {
- boolean,
- integer,
- json,
- jsonb,
- pgTable,
- text,
-} from "drizzle-orm/pg-core";
-import { createInsertSchema } from "drizzle-zod";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-import { auth } from "./auth";
-import { certificates } from "./certificate";
-import { registry } from "./registry";
-import { certificateType } from "./shared";
-import { sshKeys } from "./ssh-key";
-import { users } from "./user";
-
-export const admins = pgTable("admin", {
- adminId: text("adminId")
- .notNull()
- .primaryKey()
- .$defaultFn(() => nanoid()),
- serverIp: text("serverIp"),
- certificateType: certificateType("certificateType").notNull().default("none"),
- host: text("host"),
- letsEncryptEmail: text("letsEncryptEmail"),
- sshPrivateKey: text("sshPrivateKey"),
- enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
- enableLogRotation: boolean("enableLogRotation").notNull().default(false),
- authId: text("authId")
- .notNull()
- .references(() => auth.id, { onDelete: "cascade" }),
- createdAt: text("createdAt")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
- stripeCustomerId: text("stripeCustomerId"),
- stripeSubscriptionId: text("stripeSubscriptionId"),
- serversQuantity: integer("serversQuantity").notNull().default(0),
-
- // Metrics
- enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
- metricsConfig: jsonb("metricsConfig")
- .$type<{
- server: {
- type: "Dokploy" | "Remote";
- refreshRate: number;
- port: number;
- token: string;
- urlCallback: string;
- retentionDays: number;
- cronJob: string;
- thresholds: {
- cpu: number;
- memory: number;
- };
- };
- containers: {
- refreshRate: number;
- services: {
- include: string[];
- exclude: string[];
- };
- };
- }>()
- .notNull()
- .default({
- server: {
- type: "Dokploy",
- refreshRate: 60,
- port: 4500,
- token: "",
- retentionDays: 2,
- cronJob: "",
- urlCallback: "",
- thresholds: {
- cpu: 0,
- memory: 0,
- },
- },
- containers: {
- refreshRate: 60,
- services: {
- include: [],
- exclude: [],
- },
- },
- }),
- cleanupCacheApplications: boolean("cleanupCacheApplications")
- .notNull()
- .default(false),
- cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
- .notNull()
- .default(false),
- cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
- .notNull()
- .default(false),
-});
-
-export const adminsRelations = relations(admins, ({ one, many }) => ({
- auth: one(auth, {
- fields: [admins.authId],
- references: [auth.id],
- }),
- users: many(users),
- registry: many(registry),
- sshKeys: many(sshKeys),
- certificates: many(certificates),
-}));
-
-const createSchema = createInsertSchema(admins, {
- adminId: z.string(),
- enableDockerCleanup: z.boolean().optional(),
- sshPrivateKey: z.string().optional(),
- certificateType: z.enum(["letsencrypt", "none"]).default("none"),
- serverIp: z.string().optional(),
- letsEncryptEmail: z.string().optional(),
-});
-
-export const apiUpdateAdmin = createSchema.partial();
-
-export const apiSaveSSHKey = createSchema
- .pick({
- sshPrivateKey: true,
- })
- .required();
-
-export const apiAssignDomain = createSchema
- .pick({
- host: true,
- certificateType: true,
- letsEncryptEmail: true,
- })
- .required()
- .partial({
- letsEncryptEmail: true,
- });
-
-export const apiUpdateDockerCleanup = createSchema
- .pick({
- enableDockerCleanup: true,
- })
- .required()
- .extend({
- serverId: z.string().optional(),
- });
-
-export const apiTraefikConfig = z.object({
- traefikConfig: z.string().min(1),
-});
-
-export const apiModifyTraefikConfig = z.object({
- path: z.string().min(1),
- traefikConfig: z.string().min(1),
- serverId: z.string().optional(),
-});
-export const apiReadTraefikConfig = z.object({
- path: z.string().min(1),
- serverId: z.string().optional(),
-});
-
-export const apiEnableDashboard = z.object({
- enableDashboard: z.boolean().optional(),
- serverId: z.string().optional(),
-});
-
-export const apiServerSchema = z
- .object({
- serverId: z.string().optional(),
- })
- .optional();
-
-export const apiReadStatsLogs = z.object({
- page: z
- .object({
- pageIndex: z.number(),
- pageSize: z.number(),
- })
- .optional(),
- status: z.string().array().optional(),
- search: z.string().optional(),
- sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
-});
-
-export const apiUpdateWebServerMonitoring = z.object({
- metricsConfig: z
- .object({
- server: z.object({
- refreshRate: z.number().min(2),
- port: z.number().min(1),
- token: z.string(),
- urlCallback: z.string().url(),
- retentionDays: z.number().min(1),
- cronJob: z.string().min(1),
- thresholds: z.object({
- cpu: z.number().min(0),
- memory: z.number().min(0),
- }),
- }),
- containers: z.object({
- refreshRate: z.number().min(2),
- services: z.object({
- include: z.array(z.string()).optional(),
- exclude: z.array(z.string()).optional(),
- }),
- }),
- })
- .required(),
-});
diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts
index 2437f59d..e670e2e2 100644
--- a/packages/server/src/db/schema/application.ts
+++ b/packages/server/src/db/schema/application.ts
@@ -44,7 +44,6 @@ export const buildType = pgEnum("buildType", [
"static",
]);
-// TODO: refactor this types
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
diff --git a/packages/server/src/db/schema/auth.ts b/packages/server/src/db/schema/auth.ts
deleted file mode 100644
index 3e16c68e..00000000
--- a/packages/server/src/db/schema/auth.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { getRandomValues } from "node:crypto";
-import { relations } from "drizzle-orm";
-import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
-import { createInsertSchema } from "drizzle-zod";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-import { admins } from "./admin";
-import { users } from "./user";
-
-const randomImages = [
- "/avatars/avatar-1.png",
- "/avatars/avatar-2.png",
- "/avatars/avatar-3.png",
- "/avatars/avatar-4.png",
- "/avatars/avatar-5.png",
- "/avatars/avatar-6.png",
- "/avatars/avatar-7.png",
- "/avatars/avatar-8.png",
- "/avatars/avatar-9.png",
- "/avatars/avatar-10.png",
- "/avatars/avatar-11.png",
- "/avatars/avatar-12.png",
-];
-
-const generateRandomImage = () => {
- return (
- randomImages[
- // @ts-ignore
- getRandomValues(new Uint32Array(1))[0] % randomImages.length
- ] || "/avatars/avatar-1.png"
- );
-};
-export type DatabaseUser = typeof auth.$inferSelect;
-export const roles = pgEnum("Roles", ["admin", "user"]);
-
-export const auth = pgTable("auth", {
- id: text("id")
- .notNull()
- .primaryKey()
- .$defaultFn(() => nanoid()),
- email: text("email").notNull().unique(),
- password: text("password").notNull(),
- rol: roles("rol").notNull(),
- image: text("image").$defaultFn(() => generateRandomImage()),
- secret: text("secret"),
- token: text("token"),
- is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
- createdAt: text("createdAt")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
- resetPasswordToken: text("resetPasswordToken"),
- resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
- confirmationToken: text("confirmationToken"),
- confirmationExpiresAt: text("confirmationExpiresAt"),
-});
-
-export const authRelations = relations(auth, ({ many }) => ({
- admins: many(admins),
- users: many(users),
-}));
-const createSchema = createInsertSchema(auth, {
- email: z.string().email(),
- password: z.string().min(8),
- rol: z.enum(["admin", "user"]),
- image: z.string().optional(),
-});
-
-export const apiCreateAdmin = createSchema.pick({
- email: true,
- password: true,
-});
-
-export const apiCreateUser = createSchema
- .pick({
- password: true,
- id: true,
- token: true,
- })
- .required()
- .extend({
- token: z.string().min(1),
- });
-
-export const apiLogin = createSchema
- .pick({
- email: true,
- password: true,
- })
- .required();
-
-export const apiUpdateAuth = createSchema.partial().extend({
- email: z.string().nullable(),
- password: z.string().nullable(),
- image: z.string().optional(),
- currentPassword: z.string().nullable(),
-});
-
-export const apiUpdateAuthByAdmin = createSchema.partial().extend({
- email: z.string().nullable(),
- password: z.string().nullable(),
- image: z.string().optional(),
- id: z.string().min(1),
-});
-
-export const apiFindOneAuth = createSchema
- .pick({
- id: true,
- })
- .required();
-
-export const apiVerify2FA = createSchema
- .extend({
- pin: z.string().min(6),
- secret: z.string().min(1),
- })
- .pick({
- pin: true,
- secret: true,
- })
- .required();
-
-export const apiVerifyLogin2FA = createSchema
- .extend({
- pin: z.string().min(6),
- })
- .pick({
- pin: true,
- id: true,
- })
- .required();
diff --git a/packages/server/src/db/schema/bitbucket.ts b/packages/server/src/db/schema/bitbucket.ts
index 393cb1e7..0311202d 100644
--- a/packages/server/src/db/schema/bitbucket.ts
+++ b/packages/server/src/db/schema/bitbucket.ts
@@ -61,5 +61,5 @@ export const apiUpdateBitbucket = createSchema.extend({
name: z.string().min(1),
bitbucketUsername: z.string().optional(),
bitbucketWorkspaceName: z.string().optional(),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
diff --git a/packages/server/src/db/schema/certificate.ts b/packages/server/src/db/schema/certificate.ts
index 1df61be8..bf72f7db 100644
--- a/packages/server/src/db/schema/certificate.ts
+++ b/packages/server/src/db/schema/certificate.ts
@@ -3,7 +3,7 @@ import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { server } from "./server";
import { generateAppName } from "./utils";
@@ -20,27 +20,24 @@ export const certificates = pgTable("certificate", {
.$defaultFn(() => generateAppName("certificate"))
.unique(),
autoRenew: boolean("autoRenew"),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
-export const certificatesRelations = relations(
- certificates,
- ({ one, many }) => ({
- server: one(server, {
- fields: [certificates.serverId],
- references: [server.serverId],
- }),
- admin: one(admins, {
- fields: [certificates.adminId],
- references: [admins.adminId],
- }),
+export const certificatesRelations = relations(certificates, ({ one }) => ({
+ server: one(server, {
+ fields: [certificates.serverId],
+ references: [server.serverId],
}),
-);
+ organization: one(organization, {
+ fields: [certificates.organizationId],
+ references: [organization.id],
+ }),
+}));
export const apiCreateCertificate = createInsertSchema(certificates, {
name: z.string().min(1),
diff --git a/packages/server/src/db/schema/dbml.ts b/packages/server/src/db/schema/dbml.ts
new file mode 100644
index 00000000..72a75814
--- /dev/null
+++ b/packages/server/src/db/schema/dbml.ts
@@ -0,0 +1,7 @@
+import { pgGenerate } from "drizzle-dbml-generator"; // Using Postgres for this example
+import * as schema from "./index";
+
+const out = "./schema.dbml";
+const relational = true;
+
+pgGenerate({ schema, out, relational });
diff --git a/packages/server/src/db/schema/deployment.ts b/packages/server/src/db/schema/deployment.ts
index 1be5db5e..4dfed76b 100644
--- a/packages/server/src/db/schema/deployment.ts
+++ b/packages/server/src/db/schema/deployment.ts
@@ -1,4 +1,4 @@
-import { is, relations } from "drizzle-orm";
+import { relations } from "drizzle-orm";
import {
type AnyPgColumn,
boolean,
diff --git a/packages/server/src/db/schema/destination.ts b/packages/server/src/db/schema/destination.ts
index 7d7be614..0aeb1490 100644
--- a/packages/server/src/db/schema/destination.ts
+++ b/packages/server/src/db/schema/destination.ts
@@ -3,7 +3,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { backups } from "./backups";
export const destinations = pgTable("destination", {
@@ -17,20 +17,19 @@ export const destinations = pgTable("destination", {
secretAccessKey: text("secretAccessKey").notNull(),
bucket: text("bucket").notNull(),
region: text("region").notNull(),
- // maybe it can be null
endpoint: text("endpoint").notNull(),
- adminId: text("adminId")
+ organizationId: text("organizationId")
.notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
});
export const destinationsRelations = relations(
destinations,
({ many, one }) => ({
backups: many(backups),
- admin: one(admins, {
- fields: [destinations.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [destinations.organizationId],
+ references: [organization.id],
}),
}),
);
diff --git a/packages/server/src/db/schema/git-provider.ts b/packages/server/src/db/schema/git-provider.ts
index dbbfc183..92230737 100644
--- a/packages/server/src/db/schema/git-provider.ts
+++ b/packages/server/src/db/schema/git-provider.ts
@@ -3,7 +3,7 @@ import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { bitbucket } from "./bitbucket";
import { github } from "./github";
import { gitlab } from "./gitlab";
@@ -24,12 +24,12 @@ export const gitProvider = pgTable("git_provider", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
});
-export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
+export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
github: one(github, {
fields: [gitProvider.gitProviderId],
references: [github.gitProviderId],
@@ -42,9 +42,9 @@ export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
fields: [gitProvider.gitProviderId],
references: [bitbucket.gitProviderId],
}),
- admin: one(admins, {
- fields: [gitProvider.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [gitProvider.organizationId],
+ references: [organization.id],
}),
}));
diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts
index 9c7a079c..5eb7f369 100644
--- a/packages/server/src/db/schema/index.ts
+++ b/packages/server/src/db/schema/index.ts
@@ -1,8 +1,6 @@
export * from "./application";
export * from "./postgres";
export * from "./user";
-export * from "./admin";
-export * from "./auth";
export * from "./project";
export * from "./domain";
export * from "./mariadb";
@@ -30,3 +28,4 @@ export * from "./gitlab";
export * from "./server";
export * from "./utils";
export * from "./preview-deployments";
+export * from "./account";
diff --git a/packages/server/src/db/schema/notification.ts b/packages/server/src/db/schema/notification.ts
index 3e33bfd8..1c8a2d8f 100644
--- a/packages/server/src/db/schema/notification.ts
+++ b/packages/server/src/db/schema/notification.ts
@@ -3,7 +3,7 @@ import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
export const notificationType = pgEnum("notificationType", [
"slack",
@@ -44,9 +44,9 @@ export const notifications = pgTable("notification", {
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
onDelete: "cascade",
}),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
});
export const slack = pgTable("slack", {
@@ -122,9 +122,9 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
fields: [notifications.gotifyId],
references: [gotify.gotifyId],
}),
- admin: one(admins, {
- fields: [notifications.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [notifications.organizationId],
+ references: [organization.id],
}),
}));
@@ -149,7 +149,7 @@ export const apiCreateSlack = notificationsSchema
export const apiUpdateSlack = apiCreateSlack.partial().extend({
notificationId: z.string().min(1),
slackId: z.string(),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestSlackConnection = apiCreateSlack.pick({
@@ -177,7 +177,7 @@ export const apiCreateTelegram = notificationsSchema
export const apiUpdateTelegram = apiCreateTelegram.partial().extend({
notificationId: z.string().min(1),
telegramId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestTelegramConnection = apiCreateTelegram.pick({
@@ -205,7 +205,7 @@ export const apiCreateDiscord = notificationsSchema
export const apiUpdateDiscord = apiCreateDiscord.partial().extend({
notificationId: z.string().min(1),
discordId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestDiscordConnection = apiCreateDiscord
@@ -239,7 +239,7 @@ export const apiCreateEmail = notificationsSchema
export const apiUpdateEmail = apiCreateEmail.partial().extend({
notificationId: z.string().min(1),
emailId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestEmailConnection = apiCreateEmail.pick({
@@ -271,7 +271,7 @@ export const apiCreateGotify = notificationsSchema
export const apiUpdateGotify = apiCreateGotify.partial().extend({
notificationId: z.string().min(1),
gotifyId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestGotifyConnection = apiCreateGotify
diff --git a/packages/server/src/db/schema/project.ts b/packages/server/src/db/schema/project.ts
index 7ed140d6..deeba4ac 100644
--- a/packages/server/src/db/schema/project.ts
+++ b/packages/server/src/db/schema/project.ts
@@ -1,10 +1,9 @@
import { relations } from "drizzle-orm";
-
import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
import { compose } from "./compose";
import { mariadb } from "./mariadb";
@@ -23,9 +22,10 @@ export const projects = pgTable("project", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- adminId: text("adminId")
+
+ organizationId: text("organizationId")
.notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
env: text("env").notNull().default(""),
});
@@ -37,9 +37,9 @@ export const projectRelations = relations(projects, ({ many, one }) => ({
mongo: many(mongo),
redis: many(redis),
compose: many(compose),
- admin: one(admins, {
- fields: [projects.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [projects.organizationId],
+ references: [organization.id],
}),
}));
diff --git a/packages/server/src/db/schema/registry.ts b/packages/server/src/db/schema/registry.ts
index 20544a58..b1874709 100644
--- a/packages/server/src/db/schema/registry.ts
+++ b/packages/server/src/db/schema/registry.ts
@@ -3,7 +3,7 @@ import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
@@ -27,16 +27,12 @@ export const registry = pgTable("registry", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"),
- adminId: text("adminId")
+ organizationId: text("organizationId")
.notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
});
-export const registryRelations = relations(registry, ({ one, many }) => ({
- admin: one(admins, {
- fields: [registry.adminId],
- references: [admins.adminId],
- }),
+export const registryRelations = relations(registry, ({ many }) => ({
applications: many(applications),
}));
@@ -45,7 +41,7 @@ const createSchema = createInsertSchema(registry, {
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
- adminId: z.string().min(1),
+ organizationId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),
diff --git a/packages/server/src/db/schema/schema.dbml b/packages/server/src/db/schema/schema.dbml
new file mode 100644
index 00000000..ce2b5abc
--- /dev/null
+++ b/packages/server/src/db/schema/schema.dbml
@@ -0,0 +1,872 @@
+enum applicationStatus {
+ idle
+ running
+ done
+ error
+}
+
+enum buildType {
+ dockerfile
+ heroku_buildpacks
+ paketo_buildpacks
+ nixpacks
+ static
+}
+
+enum certificateType {
+ letsencrypt
+ none
+}
+
+enum composeType {
+ "docker-compose"
+ stack
+}
+
+enum databaseType {
+ postgres
+ mariadb
+ mysql
+ mongo
+}
+
+enum deploymentStatus {
+ running
+ done
+ error
+}
+
+enum domainType {
+ compose
+ application
+ preview
+}
+
+enum gitProviderType {
+ github
+ gitlab
+ bitbucket
+}
+
+enum mountType {
+ bind
+ volume
+ file
+}
+
+enum notificationType {
+ slack
+ telegram
+ discord
+ email
+ gotify
+}
+
+enum protocolType {
+ tcp
+ udp
+}
+
+enum RegistryType {
+ selfHosted
+ cloud
+}
+
+enum Roles {
+ admin
+ user
+}
+
+enum serverStatus {
+ active
+ inactive
+}
+
+enum serviceType {
+ application
+ postgres
+ mysql
+ mariadb
+ mongo
+ redis
+ compose
+}
+
+enum sourceType {
+ docker
+ git
+ github
+ gitlab
+ bitbucket
+ drop
+}
+
+enum sourceTypeCompose {
+ git
+ github
+ gitlab
+ bitbucket
+ raw
+}
+
+table account {
+ id text [pk, not null]
+ account_id text [not null]
+ provider_id text [not null]
+ user_id text [not null]
+ access_token text
+ refresh_token text
+ id_token text
+ access_token_expires_at timestamp
+ refresh_token_expires_at timestamp
+ scope text
+ password text
+ is2FAEnabled boolean [not null, default: false]
+ created_at timestamp [not null]
+ updated_at timestamp [not null]
+ resetPasswordToken text
+ resetPasswordExpiresAt text
+ confirmationToken text
+ confirmationExpiresAt text
+}
+
+table admin {
+}
+
+table application {
+ applicationId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ env text
+ previewEnv text
+ previewBuildArgs text
+ previewWildcard text
+ previewPort integer [default: 3000]
+ previewHttps boolean [not null, default: false]
+ previewPath text [default: '/']
+ certificateType certificateType [not null, default: 'none']
+ previewLimit integer [default: 3]
+ isPreviewDeploymentsActive boolean [default: false]
+ buildArgs text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ title text
+ enabled boolean
+ subtitle text
+ command text
+ refreshToken text
+ sourceType sourceType [not null, default: 'github']
+ repository text
+ owner text
+ branch text
+ buildPath text [default: '/']
+ autoDeploy boolean
+ gitlabProjectId integer
+ gitlabRepository text
+ gitlabOwner text
+ gitlabBranch text
+ gitlabBuildPath text [default: '/']
+ gitlabPathNamespace text
+ bitbucketRepository text
+ bitbucketOwner text
+ bitbucketBranch text
+ bitbucketBuildPath text [default: '/']
+ username text
+ password text
+ dockerImage text
+ registryUrl text
+ customGitUrl text
+ customGitBranch text
+ customGitBuildPath text
+ customGitSSHKeyId text
+ dockerfile text
+ dockerContextPath text
+ dockerBuildStage text
+ dropBuildPath text
+ healthCheckSwarm json
+ restartPolicySwarm json
+ placementSwarm json
+ updateConfigSwarm json
+ rollbackConfigSwarm json
+ modeSwarm json
+ labelsSwarm json
+ networkSwarm json
+ replicas integer [not null, default: 1]
+ applicationStatus applicationStatus [not null, default: 'idle']
+ buildType buildType [not null, default: 'nixpacks']
+ herokuVersion text [default: '24']
+ publishDirectory text
+ createdAt text [not null]
+ registryId text
+ projectId text [not null]
+ githubId text
+ gitlabId text
+ bitbucketId text
+ serverId text
+}
+
+table auth {
+ id text [pk, not null]
+ email text [not null, unique]
+ password text [not null]
+ rol Roles [not null]
+ image text
+ secret text
+ token text
+ is2FAEnabled boolean [not null, default: false]
+ createdAt text [not null]
+ resetPasswordToken text
+ resetPasswordExpiresAt text
+ confirmationToken text
+ confirmationExpiresAt text
+}
+
+table backup {
+ backupId text [pk, not null]
+ schedule text [not null]
+ enabled boolean
+ database text [not null]
+ prefix text [not null]
+ destinationId text [not null]
+ databaseType databaseType [not null]
+ postgresId text
+ mariadbId text
+ mysqlId text
+ mongoId text
+}
+
+table bitbucket {
+ bitbucketId text [pk, not null]
+ bitbucketUsername text
+ appPassword text
+ bitbucketWorkspaceName text
+ gitProviderId text [not null]
+}
+
+table certificate {
+ certificateId text [pk, not null]
+ name text [not null]
+ certificateData text [not null]
+ privateKey text [not null]
+ certificatePath text [not null, unique]
+ autoRenew boolean
+ userId text
+ serverId text
+}
+
+table compose {
+ composeId text [pk, not null]
+ name text [not null]
+ appName text [not null]
+ description text
+ env text
+ composeFile text [not null, default: '']
+ refreshToken text
+ sourceType sourceTypeCompose [not null, default: 'github']
+ composeType composeType [not null, default: 'docker-compose']
+ repository text
+ owner text
+ branch text
+ autoDeploy boolean
+ gitlabProjectId integer
+ gitlabRepository text
+ gitlabOwner text
+ gitlabBranch text
+ gitlabPathNamespace text
+ bitbucketRepository text
+ bitbucketOwner text
+ bitbucketBranch text
+ customGitUrl text
+ customGitBranch text
+ customGitSSHKeyId text
+ command text [not null, default: '']
+ composePath text [not null, default: './docker-compose.yml']
+ suffix text [not null, default: '']
+ randomize boolean [not null, default: false]
+ isolatedDeployment boolean [not null, default: false]
+ composeStatus applicationStatus [not null, default: 'idle']
+ projectId text [not null]
+ createdAt text [not null]
+ githubId text
+ gitlabId text
+ bitbucketId text
+ serverId text
+}
+
+table deployment {
+ deploymentId text [pk, not null]
+ title text [not null]
+ description text
+ status deploymentStatus [default: 'running']
+ logPath text [not null]
+ applicationId text
+ composeId text
+ serverId text
+ isPreviewDeployment boolean [default: false]
+ previewDeploymentId text
+ createdAt text [not null]
+ errorMessage text
+}
+
+table destination {
+ destinationId text [pk, not null]
+ name text [not null]
+ provider text
+ accessKey text [not null]
+ secretAccessKey text [not null]
+ bucket text [not null]
+ region text [not null]
+ endpoint text [not null]
+ userId text [not null]
+}
+
+table discord {
+ discordId text [pk, not null]
+ webhookUrl text [not null]
+ decoration boolean
+}
+
+table domain {
+ domainId text [pk, not null]
+ host text [not null]
+ https boolean [not null, default: false]
+ port integer [default: 3000]
+ path text [default: '/']
+ serviceName text
+ domainType domainType [default: 'application']
+ uniqueConfigKey serial [not null, increment]
+ createdAt text [not null]
+ composeId text
+ applicationId text
+ previewDeploymentId text
+ certificateType certificateType [not null, default: 'none']
+}
+
+table email {
+ emailId text [pk, not null]
+ smtpServer text [not null]
+ smtpPort integer [not null]
+ username text [not null]
+ password text [not null]
+ fromAddress text [not null]
+ toAddress text[] [not null]
+}
+
+table git_provider {
+ gitProviderId text [pk, not null]
+ name text [not null]
+ providerType gitProviderType [not null, default: 'github']
+ createdAt text [not null]
+ userId text
+}
+
+table github {
+ githubId text [pk, not null]
+ githubAppName text
+ githubAppId integer
+ githubClientId text
+ githubClientSecret text
+ githubInstallationId text
+ githubPrivateKey text
+ githubWebhookSecret text
+ gitProviderId text [not null]
+}
+
+table gitlab {
+ gitlabId text [pk, not null]
+ gitlabUrl text [not null, default: 'https://gitlab.com']
+ application_id text
+ redirect_uri text
+ secret text
+ access_token text
+ refresh_token text
+ group_name text
+ expires_at integer
+ gitProviderId text [not null]
+}
+
+table gotify {
+ gotifyId text [pk, not null]
+ serverUrl text [not null]
+ appToken text [not null]
+ priority integer [not null, default: 5]
+ decoration boolean
+}
+
+table invitation {
+ id text [pk, not null]
+ organization_id text [not null]
+ email text [not null]
+ role text
+ status text [not null]
+ expires_at timestamp [not null]
+ inviter_id text [not null]
+}
+
+table mariadb {
+ mariadbId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ databaseName text [not null]
+ databaseUser text [not null]
+ databasePassword text [not null]
+ rootPassword text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+}
+
+table member {
+ id text [pk, not null]
+ organization_id text [not null]
+ user_id text [not null]
+ role text [not null]
+ created_at timestamp [not null]
+}
+
+table mongo {
+ mongoId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ databaseUser text [not null]
+ databasePassword text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+ replicaSets boolean [default: false]
+}
+
+table mount {
+ mountId text [pk, not null]
+ type mountType [not null]
+ hostPath text
+ volumeName text
+ filePath text
+ content text
+ serviceType serviceType [not null, default: 'application']
+ mountPath text [not null]
+ applicationId text
+ postgresId text
+ mariadbId text
+ mongoId text
+ mysqlId text
+ redisId text
+ composeId text
+}
+
+table mysql {
+ mysqlId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ databaseName text [not null]
+ databaseUser text [not null]
+ databasePassword text [not null]
+ rootPassword text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+}
+
+table notification {
+ notificationId text [pk, not null]
+ name text [not null]
+ appDeploy boolean [not null, default: false]
+ appBuildError boolean [not null, default: false]
+ databaseBackup boolean [not null, default: false]
+ dokployRestart boolean [not null, default: false]
+ dockerCleanup boolean [not null, default: false]
+ serverThreshold boolean [not null, default: false]
+ notificationType notificationType [not null]
+ createdAt text [not null]
+ slackId text
+ telegramId text
+ discordId text
+ emailId text
+ gotifyId text
+ userId text
+}
+
+table organization {
+ id text [pk, not null]
+ name text [not null]
+ slug text [unique]
+ logo text
+ created_at timestamp [not null]
+ metadata text
+ owner_id text [not null]
+}
+
+table port {
+ portId text [pk, not null]
+ publishedPort integer [not null]
+ targetPort integer [not null]
+ protocol protocolType [not null]
+ applicationId text [not null]
+}
+
+table postgres {
+ postgresId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ databaseName text [not null]
+ databaseUser text [not null]
+ databasePassword text [not null]
+ description text
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ externalPort integer
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+}
+
+table preview_deployments {
+ previewDeploymentId text [pk, not null]
+ branch text [not null]
+ pullRequestId text [not null]
+ pullRequestNumber text [not null]
+ pullRequestURL text [not null]
+ pullRequestTitle text [not null]
+ pullRequestCommentId text [not null]
+ previewStatus applicationStatus [not null, default: 'idle']
+ appName text [not null, unique]
+ applicationId text [not null]
+ domainId text
+ createdAt text [not null]
+ expiresAt text
+}
+
+table project {
+ projectId text [pk, not null]
+ name text [not null]
+ description text
+ createdAt text [not null]
+ userId text [not null]
+ env text [not null, default: '']
+}
+
+table redirect {
+ redirectId text [pk, not null]
+ regex text [not null]
+ replacement text [not null]
+ permanent boolean [not null, default: false]
+ uniqueConfigKey serial [not null, increment]
+ createdAt text [not null]
+ applicationId text [not null]
+}
+
+table redis {
+ redisId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ password text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ createdAt text [not null]
+ applicationStatus applicationStatus [not null, default: 'idle']
+ projectId text [not null]
+ serverId text
+}
+
+table registry {
+ registryId text [pk, not null]
+ registryName text [not null]
+ imagePrefix text
+ username text [not null]
+ password text [not null]
+ registryUrl text [not null, default: '']
+ createdAt text [not null]
+ selfHosted RegistryType [not null, default: 'cloud']
+ userId text [not null]
+}
+
+table security {
+ securityId text [pk, not null]
+ username text [not null]
+ password text [not null]
+ createdAt text [not null]
+ applicationId text [not null]
+
+ indexes {
+ (username, applicationId) [name: 'security_username_applicationId_unique', unique]
+ }
+}
+
+table server {
+ serverId text [pk, not null]
+ name text [not null]
+ description text
+ ipAddress text [not null]
+ port integer [not null]
+ username text [not null, default: 'root']
+ appName text [not null]
+ enableDockerCleanup boolean [not null, default: false]
+ createdAt text [not null]
+ userId text [not null]
+ serverStatus serverStatus [not null, default: 'active']
+ command text [not null, default: '']
+ sshKeyId text
+ metricsConfig jsonb [not null, default: `{"server":{"type":"Remote","refreshRate":60,"port":4500,"token":"","urlCallback":"","cronJob":"","retentionDays":2,"thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
+}
+
+table session {
+ id text [pk, not null]
+ expires_at timestamp [not null]
+ token text [not null, unique]
+ created_at timestamp [not null]
+ updated_at timestamp [not null]
+ ip_address text
+ user_agent text
+ user_id text [not null]
+ impersonated_by text
+ active_organization_id text
+}
+
+table slack {
+ slackId text [pk, not null]
+ webhookUrl text [not null]
+ channel text
+}
+
+table "ssh-key" {
+ sshKeyId text [pk, not null]
+ privateKey text [not null, default: '']
+ publicKey text [not null]
+ name text [not null]
+ description text
+ createdAt text [not null]
+ lastUsedAt text
+ userId text
+}
+
+table telegram {
+ telegramId text [pk, not null]
+ botToken text [not null]
+ chatId text [not null]
+}
+
+table user {
+ id text [pk, not null]
+ name text [not null, default: '']
+ token text [not null]
+ isRegistered boolean [not null, default: false]
+ expirationDate text [not null]
+ createdAt text [not null]
+ canCreateProjects boolean [not null, default: false]
+ canAccessToSSHKeys boolean [not null, default: false]
+ canCreateServices boolean [not null, default: false]
+ canDeleteProjects boolean [not null, default: false]
+ canDeleteServices boolean [not null, default: false]
+ canAccessToDocker boolean [not null, default: false]
+ canAccessToAPI boolean [not null, default: false]
+ canAccessToGitProviders boolean [not null, default: false]
+ canAccessToTraefikFiles boolean [not null, default: false]
+ accesedProjects text[] [not null, default: `ARRAY[]::text[]`]
+ accesedServices text[] [not null, default: `ARRAY[]::text[]`]
+ email text [not null, unique]
+ email_verified boolean [not null]
+ image text
+ role text
+ banned boolean
+ ban_reason text
+ ban_expires timestamp
+ updated_at timestamp [not null]
+ serverIp text
+ certificateType certificateType [not null, default: 'none']
+ host text
+ letsEncryptEmail text
+ sshPrivateKey text
+ enableDockerCleanup boolean [not null, default: false]
+ enableLogRotation boolean [not null, default: false]
+ enablePaidFeatures boolean [not null, default: false]
+ metricsConfig jsonb [not null, default: `{"server":{"type":"Dokploy","refreshRate":60,"port":4500,"token":"","retentionDays":2,"cronJob":"","urlCallback":"","thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
+ cleanupCacheApplications boolean [not null, default: false]
+ cleanupCacheOnPreviews boolean [not null, default: false]
+ cleanupCacheOnCompose boolean [not null, default: false]
+ stripeCustomerId text
+ stripeSubscriptionId text
+ serversQuantity integer [not null, default: 0]
+}
+
+table verification {
+ id text [pk, not null]
+ identifier text [not null]
+ value text [not null]
+ expires_at timestamp [not null]
+ created_at timestamp
+ updated_at timestamp
+}
+
+ref: mount.applicationId > application.applicationId
+
+ref: mount.postgresId > postgres.postgresId
+
+ref: mount.mariadbId > mariadb.mariadbId
+
+ref: mount.mongoId > mongo.mongoId
+
+ref: mount.mysqlId > mysql.mysqlId
+
+ref: mount.redisId > redis.redisId
+
+ref: mount.composeId > compose.composeId
+
+ref: application.projectId > project.projectId
+
+ref: application.customGitSSHKeyId > "ssh-key".sshKeyId
+
+ref: application.registryId > registry.registryId
+
+ref: application.githubId - github.githubId
+
+ref: application.gitlabId - gitlab.gitlabId
+
+ref: application.bitbucketId - bitbucket.bitbucketId
+
+ref: application.serverId > server.serverId
+
+ref: backup.destinationId > destination.destinationId
+
+ref: backup.postgresId > postgres.postgresId
+
+ref: backup.mariadbId > mariadb.mariadbId
+
+ref: backup.mysqlId > mysql.mysqlId
+
+ref: backup.mongoId > mongo.mongoId
+
+ref: git_provider.gitProviderId - bitbucket.gitProviderId
+
+ref: certificate.serverId > server.serverId
+
+ref: certificate.userId - user.id
+
+ref: compose.projectId > project.projectId
+
+ref: compose.customGitSSHKeyId > "ssh-key".sshKeyId
+
+ref: compose.githubId - github.githubId
+
+ref: compose.gitlabId - gitlab.gitlabId
+
+ref: compose.bitbucketId - bitbucket.bitbucketId
+
+ref: compose.serverId > server.serverId
+
+ref: deployment.applicationId > application.applicationId
+
+ref: deployment.composeId > compose.composeId
+
+ref: deployment.serverId > server.serverId
+
+ref: deployment.previewDeploymentId > preview_deployments.previewDeploymentId
+
+ref: destination.userId - user.id
+
+ref: domain.applicationId > application.applicationId
+
+ref: domain.composeId > compose.composeId
+
+ref: preview_deployments.domainId - domain.domainId
+
+ref: github.gitProviderId - git_provider.gitProviderId
+
+ref: gitlab.gitProviderId - git_provider.gitProviderId
+
+ref: git_provider.userId - user.id
+
+ref: mariadb.projectId > project.projectId
+
+ref: mariadb.serverId > server.serverId
+
+ref: mongo.projectId > project.projectId
+
+ref: mongo.serverId > server.serverId
+
+ref: mysql.projectId > project.projectId
+
+ref: mysql.serverId > server.serverId
+
+ref: notification.slackId - slack.slackId
+
+ref: notification.telegramId - telegram.telegramId
+
+ref: notification.discordId - discord.discordId
+
+ref: notification.emailId - email.emailId
+
+ref: notification.gotifyId - gotify.gotifyId
+
+ref: notification.userId - user.id
+
+ref: port.applicationId > application.applicationId
+
+ref: postgres.projectId > project.projectId
+
+ref: postgres.serverId > server.serverId
+
+ref: preview_deployments.applicationId > application.applicationId
+
+ref: project.userId - user.id
+
+ref: redirect.applicationId > application.applicationId
+
+ref: redis.projectId > project.projectId
+
+ref: redis.serverId > server.serverId
+
+ref: registry.userId - user.id
+
+ref: security.applicationId > application.applicationId
+
+ref: server.userId - user.id
+
+ref: server.sshKeyId > "ssh-key".sshKeyId
+
+ref: "ssh-key".userId - user.id
\ No newline at end of file
diff --git a/packages/server/src/db/schema/server.ts b/packages/server/src/db/schema/server.ts
index c4ec6a09..26bb4632 100644
--- a/packages/server/src/db/schema/server.ts
+++ b/packages/server/src/db/schema/server.ts
@@ -10,8 +10,7 @@ import {
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
import { certificates } from "./certificate";
import { compose } from "./compose";
@@ -40,12 +39,10 @@ export const server = pgTable("server", {
.notNull()
.$defaultFn(() => generateAppName("server")),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
- createdAt: text("createdAt")
+ createdAt: text("createdAt").notNull(),
+ organizationId: text("organizationId")
.notNull()
- .$defaultFn(() => new Date().toISOString()),
- adminId: text("adminId")
- .notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
serverStatus: serverStatus("serverStatus").notNull().default("active"),
command: text("command").notNull().default(""),
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
@@ -100,10 +97,6 @@ export const server = pgTable("server", {
});
export const serverRelations = relations(server, ({ one, many }) => ({
- admin: one(admins, {
- fields: [server.adminId],
- references: [admins.adminId],
- }),
deployments: many(deployments),
sshKey: one(sshKeys, {
fields: [server.sshKeyId],
@@ -117,6 +110,10 @@ export const serverRelations = relations(server, ({ one, many }) => ({
mysql: many(mysql),
postgres: many(postgres),
certificates: many(certificates),
+ organization: one(organization, {
+ fields: [server.organizationId],
+ references: [organization.id],
+ }),
}));
const createSchema = createInsertSchema(server, {
diff --git a/packages/server/src/db/schema/session.ts b/packages/server/src/db/schema/session.ts
index 1b6d8cc1..f7c12dae 100644
--- a/packages/server/src/db/schema/session.ts
+++ b/packages/server/src/db/schema/session.ts
@@ -1,13 +1,18 @@
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
-import { auth } from "./auth";
+import { users_temp } from "./user";
-export const sessionTable = pgTable("session", {
+// OLD TABLE
+export const session = pgTable("session_temp", {
id: text("id").primaryKey(),
+ expiresAt: timestamp("expires_at").notNull(),
+ token: text("token").notNull().unique(),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
- .references(() => auth.id, { onDelete: "cascade" }),
- expiresAt: timestamp("expires_at", {
- withTimezone: true,
- mode: "date",
- }).notNull(),
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ impersonatedBy: text("impersonated_by"),
+ activeOrganizationId: text("active_organization_id"),
});
diff --git a/packages/server/src/db/schema/source.ts b/packages/server/src/db/schema/source.ts
deleted file mode 100644
index 6618ced7..00000000
--- a/packages/server/src/db/schema/source.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { pgTable, text } from "drizzle-orm/pg-core";
-import { createInsertSchema } from "drizzle-zod";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-
-export const source = pgTable("project", {
- projectId: text("projectId")
- .notNull()
- .primaryKey()
- .$defaultFn(() => nanoid()),
- name: text("name").notNull(),
- description: text("description"),
- createdAt: text("createdAt")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
-});
-
-const createSchema = createInsertSchema(source, {
- name: z.string().min(1),
- description: z.string(),
- projectId: z.string(),
-});
-
-export const apiCreate = createSchema.pick({
- name: true,
- description: true,
-});
diff --git a/packages/server/src/db/schema/ssh-key.ts b/packages/server/src/db/schema/ssh-key.ts
index e4842851..8a66d6d9 100644
--- a/packages/server/src/db/schema/ssh-key.ts
+++ b/packages/server/src/db/schema/ssh-key.ts
@@ -3,7 +3,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { sshKeyCreate, sshKeyType } from "../validations";
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
import { compose } from "./compose";
import { server } from "./server";
@@ -21,18 +21,18 @@ export const sshKeys = pgTable("ssh-key", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
lastUsedAt: text("lastUsedAt"),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
});
export const sshKeysRelations = relations(sshKeys, ({ many, one }) => ({
applications: many(applications),
compose: many(compose),
servers: many(server),
- admin: one(admins, {
- fields: [sshKeys.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [sshKeys.organizationId],
+ references: [organization.id],
}),
}));
@@ -48,7 +48,7 @@ export const apiCreateSshKey = createSchema
description: true,
privateKey: true,
publicKey: true,
- adminId: true,
+ organizationId: true,
})
.merge(sshKeyCreate.pick({ privateKey: true }));
diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts
index 735898f9..9307127a 100644
--- a/packages/server/src/db/schema/user.ts
+++ b/packages/server/src/db/schema/user.ts
@@ -1,10 +1,18 @@
-import { relations, sql } from "drizzle-orm";
-import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
+import { relations } from "drizzle-orm";
+import {
+ boolean,
+ integer,
+ jsonb,
+ pgTable,
+ text,
+ timestamp,
+} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
-import { auth } from "./auth";
+import { account, organization, apikey } from "./account";
+import { projects } from "./project";
+import { certificateType } from "./shared";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects.
@@ -12,75 +20,115 @@ import { auth } from "./auth";
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
-export const users = pgTable("user", {
- userId: text("userId")
+// OLD TABLE
+
+// TEMP
+export const users_temp = pgTable("user_temp", {
+ id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
-
- token: text("token").notNull(),
+ name: text("name").notNull().default(""),
isRegistered: boolean("isRegistered").notNull().default(false),
- expirationDate: timestamp("expirationDate", {
- precision: 3,
- mode: "string",
- }).notNull(),
- createdAt: text("createdAt")
+ expirationDate: text("expirationDate")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- canCreateProjects: boolean("canCreateProjects").notNull().default(false),
- canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
- canCreateServices: boolean("canCreateServices").notNull().default(false),
- canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
- canDeleteServices: boolean("canDeleteServices").notNull().default(false),
- canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
- canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
- canAccessToGitProviders: boolean("canAccessToGitProviders")
+ createdAt2: text("createdAt")
+ .notNull()
+ .$defaultFn(() => new Date().toISOString()),
+ createdAt: timestamp("created_at").defaultNow(),
+ // Auth
+ twoFactorEnabled: boolean("two_factor_enabled"),
+ email: text("email").notNull().unique(),
+ emailVerified: boolean("email_verified").notNull(),
+ image: text("image"),
+ banned: boolean("banned"),
+ banReason: text("ban_reason"),
+ banExpires: timestamp("ban_expires"),
+ updatedAt: timestamp("updated_at").notNull(),
+ // Admin
+ serverIp: text("serverIp"),
+ certificateType: certificateType("certificateType").notNull().default("none"),
+ host: text("host"),
+ letsEncryptEmail: text("letsEncryptEmail"),
+ sshPrivateKey: text("sshPrivateKey"),
+ enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
+ enableLogRotation: boolean("enableLogRotation").notNull().default(false),
+ // Metrics
+ enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
+ metricsConfig: jsonb("metricsConfig")
+ .$type<{
+ server: {
+ type: "Dokploy" | "Remote";
+ refreshRate: number;
+ port: number;
+ token: string;
+ urlCallback: string;
+ retentionDays: number;
+ cronJob: string;
+ thresholds: {
+ cpu: number;
+ memory: number;
+ };
+ };
+ containers: {
+ refreshRate: number;
+ services: {
+ include: string[];
+ exclude: string[];
+ };
+ };
+ }>()
+ .notNull()
+ .default({
+ server: {
+ type: "Dokploy",
+ refreshRate: 60,
+ port: 4500,
+ token: "",
+ retentionDays: 2,
+ cronJob: "",
+ urlCallback: "",
+ thresholds: {
+ cpu: 0,
+ memory: 0,
+ },
+ },
+ containers: {
+ refreshRate: 60,
+ services: {
+ include: [],
+ exclude: [],
+ },
+ },
+ }),
+ cleanupCacheApplications: boolean("cleanupCacheApplications")
.notNull()
.default(false),
- canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
+ cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
.notNull()
.default(false),
- accessedProjects: text("accesedProjects")
- .array()
+ cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
.notNull()
- .default(sql`ARRAY[]::text[]`),
- accessedServices: text("accesedServices")
- .array()
- .notNull()
- .default(sql`ARRAY[]::text[]`),
- adminId: text("adminId")
- .notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
- authId: text("authId")
- .notNull()
- .references(() => auth.id, { onDelete: "cascade" }),
+ .default(false),
+ stripeCustomerId: text("stripeCustomerId"),
+ stripeSubscriptionId: text("stripeSubscriptionId"),
+ serversQuantity: integer("serversQuantity").notNull().default(0),
});
-export const usersRelations = relations(users, ({ one }) => ({
- auth: one(auth, {
- fields: [users.authId],
- references: [auth.id],
- }),
- admin: one(admins, {
- fields: [users.adminId],
- references: [admins.adminId],
+export const usersRelations = relations(users_temp, ({ one, many }) => ({
+ account: one(account, {
+ fields: [users_temp.id],
+ references: [account.userId],
}),
+ organizations: many(organization),
+ projects: many(projects),
+ apiKeys: many(apikey),
}));
-const createSchema = createInsertSchema(users, {
- userId: z.string().min(1),
- authId: z.string().min(1),
- token: z.string().min(1),
+const createSchema = createInsertSchema(users_temp, {
+ id: z.string().min(1),
isRegistered: z.boolean().optional(),
- adminId: z.string(),
- accessedProjects: z.array(z.string()).optional(),
- accessedServices: z.array(z.string()).optional(),
- canCreateProjects: z.boolean().optional(),
- canCreateServices: z.boolean().optional(),
- canDeleteProjects: z.boolean().optional(),
- canDeleteServices: z.boolean().optional(),
- canAccessToDocker: z.boolean().optional(),
- canAccessToTraefikFiles: z.boolean().optional(),
});
export const apiCreateUserInvitation = createSchema.pick({}).extend({
@@ -89,41 +137,172 @@ export const apiCreateUserInvitation = createSchema.pick({}).extend({
export const apiRemoveUser = createSchema
.pick({
- authId: true,
+ id: true,
})
.required();
export const apiFindOneToken = createSchema
- .pick({
- token: true,
- })
- .required();
+ .pick({})
+ .required()
+ .extend({
+ token: z.string().min(1),
+ });
export const apiAssignPermissions = createSchema
.pick({
- userId: true,
- canCreateProjects: true,
- canCreateServices: true,
- canDeleteProjects: true,
- canDeleteServices: true,
- accessedProjects: true,
- accessedServices: true,
- canAccessToTraefikFiles: true,
- canAccessToDocker: true,
- canAccessToAPI: true,
- canAccessToSSHKeys: true,
- canAccessToGitProviders: true,
+ id: true,
+ // canCreateProjects: true,
+ // canCreateServices: true,
+ // canDeleteProjects: true,
+ // canDeleteServices: true,
+ // accessedProjects: true,
+ // accessedServices: true,
+ // canAccessToTraefikFiles: true,
+ // canAccessToDocker: true,
+ // canAccessToAPI: true,
+ // canAccessToSSHKeys: true,
+ // canAccessToGitProviders: true,
+ })
+ .extend({
+ accessedProjects: z.array(z.string()).optional(),
+ accessedServices: z.array(z.string()).optional(),
+ canCreateProjects: z.boolean().optional(),
+ canCreateServices: z.boolean().optional(),
+ canDeleteProjects: z.boolean().optional(),
+ canDeleteServices: z.boolean().optional(),
+ canAccessToDocker: z.boolean().optional(),
+ canAccessToTraefikFiles: z.boolean().optional(),
+ canAccessToAPI: z.boolean().optional(),
+ canAccessToSSHKeys: z.boolean().optional(),
+ canAccessToGitProviders: z.boolean().optional(),
})
.required();
export const apiFindOneUser = createSchema
.pick({
- userId: true,
+ id: true,
})
.required();
export const apiFindOneUserByAuth = createSchema
.pick({
- authId: true,
+ // authId: true,
})
.required();
+export const apiSaveSSHKey = createSchema
+ .pick({
+ sshPrivateKey: true,
+ })
+ .required();
+
+export const apiAssignDomain = createSchema
+ .pick({
+ host: true,
+ certificateType: true,
+ letsEncryptEmail: true,
+ })
+ .required()
+ .partial({
+ letsEncryptEmail: true,
+ });
+
+export const apiUpdateDockerCleanup = createSchema
+ .pick({
+ enableDockerCleanup: true,
+ })
+ .required()
+ .extend({
+ serverId: z.string().optional(),
+ });
+
+export const apiTraefikConfig = z.object({
+ traefikConfig: z.string().min(1),
+});
+
+export const apiModifyTraefikConfig = z.object({
+ path: z.string().min(1),
+ traefikConfig: z.string().min(1),
+ serverId: z.string().optional(),
+});
+export const apiReadTraefikConfig = z.object({
+ path: z.string().min(1),
+ serverId: z.string().optional(),
+});
+
+export const apiEnableDashboard = z.object({
+ enableDashboard: z.boolean().optional(),
+ serverId: z.string().optional(),
+});
+
+export const apiServerSchema = z
+ .object({
+ serverId: z.string().optional(),
+ })
+ .optional();
+
+export const apiReadStatsLogs = z.object({
+ page: z
+ .object({
+ pageIndex: z.number(),
+ pageSize: z.number(),
+ })
+ .optional(),
+ status: z.string().array().optional(),
+ search: z.string().optional(),
+ sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
+});
+
+export const apiUpdateWebServerMonitoring = z.object({
+ metricsConfig: z
+ .object({
+ server: z.object({
+ refreshRate: z.number().min(2),
+ port: z.number().min(1),
+ token: z.string(),
+ urlCallback: z.string().url(),
+ retentionDays: z.number().min(1),
+ cronJob: z.string().min(1),
+ thresholds: z.object({
+ cpu: z.number().min(0),
+ memory: z.number().min(0),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number().min(2),
+ services: z.object({
+ include: z.array(z.string()).optional(),
+ exclude: z.array(z.string()).optional(),
+ }),
+ }),
+ })
+ .required(),
+});
+
+export const apiUpdateUser = createSchema.partial().extend({
+ password: z.string().optional(),
+ currentPassword: z.string().optional(),
+ metricsConfig: z
+ .object({
+ server: z.object({
+ type: z.enum(["Dokploy", "Remote"]),
+ refreshRate: z.number(),
+ port: z.number(),
+ token: z.string(),
+ urlCallback: z.string(),
+ retentionDays: z.number(),
+ cronJob: z.string(),
+ thresholds: z.object({
+ cpu: z.number(),
+ memory: z.number(),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number(),
+ services: z.object({
+ include: z.array(z.string()),
+ exclude: z.array(z.string()),
+ }),
+ }),
+ })
+ .optional(),
+});
diff --git a/packages/server/src/emails/emails/build-failed.tsx b/packages/server/src/emails/emails/build-failed.tsx
index b3d99919..79e7b718 100644
--- a/packages/server/src/emails/emails/build-failed.tsx
+++ b/packages/server/src/emails/emails/build-failed.tsx
@@ -12,7 +12,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
projectName: string;
diff --git a/packages/server/src/emails/emails/build-success.tsx b/packages/server/src/emails/emails/build-success.tsx
index eadf7c44..d9e500ab 100644
--- a/packages/server/src/emails/emails/build-success.tsx
+++ b/packages/server/src/emails/emails/build-success.tsx
@@ -12,7 +12,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
projectName: string;
diff --git a/packages/server/src/emails/emails/database-backup.tsx b/packages/server/src/emails/emails/database-backup.tsx
index 2bdf944c..754d4d98 100644
--- a/packages/server/src/emails/emails/database-backup.tsx
+++ b/packages/server/src/emails/emails/database-backup.tsx
@@ -10,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
projectName: string;
diff --git a/packages/server/src/emails/emails/docker-cleanup.tsx b/packages/server/src/emails/emails/docker-cleanup.tsx
index 05d93ed7..985406ae 100644
--- a/packages/server/src/emails/emails/docker-cleanup.tsx
+++ b/packages/server/src/emails/emails/docker-cleanup.tsx
@@ -1,6 +1,5 @@
import {
Body,
- Button,
Container,
Head,
Heading,
@@ -11,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
message: string;
diff --git a/packages/server/src/emails/emails/dokploy-restart.tsx b/packages/server/src/emails/emails/dokploy-restart.tsx
index 1ad3d600..db4edd69 100644
--- a/packages/server/src/emails/emails/dokploy-restart.tsx
+++ b/packages/server/src/emails/emails/dokploy-restart.tsx
@@ -10,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
date: string;
diff --git a/packages/server/src/emails/emails/notion-magic-link.tsx b/packages/server/src/emails/emails/notion-magic-link.tsx
index b2286c34..f4071ce0 100644
--- a/packages/server/src/emails/emails/notion-magic-link.tsx
+++ b/packages/server/src/emails/emails/notion-magic-link.tsx
@@ -9,7 +9,6 @@ import {
Preview,
Text,
} from "@react-email/components";
-import * as React from "react";
interface NotionMagicLinkEmailProps {
loginCode?: string;
diff --git a/packages/server/src/emails/emails/plaid-verify-identity.tsx b/packages/server/src/emails/emails/plaid-verify-identity.tsx
index 650ab486..88cf893d 100644
--- a/packages/server/src/emails/emails/plaid-verify-identity.tsx
+++ b/packages/server/src/emails/emails/plaid-verify-identity.tsx
@@ -9,7 +9,6 @@ import {
Section,
Text,
} from "@react-email/components";
-import * as React from "react";
interface PlaidVerifyIdentityEmailProps {
validationCode?: string;
diff --git a/packages/server/src/emails/emails/stripe-welcome.tsx b/packages/server/src/emails/emails/stripe-welcome.tsx
index 9377853b..dbf02ea0 100644
--- a/packages/server/src/emails/emails/stripe-welcome.tsx
+++ b/packages/server/src/emails/emails/stripe-welcome.tsx
@@ -11,7 +11,6 @@ import {
Section,
Text,
} from "@react-email/components";
-import * as React from "react";
const baseUrl = process.env.VERCEL_URL!;
diff --git a/packages/server/src/emails/emails/vercel-invite-user.tsx b/packages/server/src/emails/emails/vercel-invite-user.tsx
index 53b31987..79f50cd7 100644
--- a/packages/server/src/emails/emails/vercel-invite-user.tsx
+++ b/packages/server/src/emails/emails/vercel-invite-user.tsx
@@ -15,7 +15,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
interface VercelInviteUserEmailProps {
username?: string;
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index 71549a38..f74b8d9d 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -1,7 +1,4 @@
-export * from "./auth/auth";
-export * from "./auth/token";
export * from "./auth/random-password";
-// export * from "./db";
export * from "./services/admin";
export * from "./services/user";
export * from "./services/project";
@@ -30,7 +27,6 @@ export * from "./services/ssh-key";
export * from "./services/git-provider";
export * from "./services/bitbucket";
export * from "./services/github";
-export * from "./services/auth";
export * from "./services/gitlab";
export * from "./services/server";
export * from "./services/application";
@@ -118,3 +114,5 @@ export * from "./monitoring/utils";
export * from "./db/validations/domain";
export * from "./db/validations/index";
export * from "./utils/gpu-setup";
+
+export * from "./lib/auth";
diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts
new file mode 100644
index 00000000..1efa1730
--- /dev/null
+++ b/packages/server/src/lib/auth.ts
@@ -0,0 +1,304 @@
+import type { IncomingMessage } from "node:http";
+import * as bcrypt from "bcrypt";
+import { betterAuth } from "better-auth";
+import { drizzleAdapter } from "better-auth/adapters/drizzle";
+import { organization, twoFactor, apiKey } from "better-auth/plugins";
+import { and, desc, eq } from "drizzle-orm";
+import { db } from "../db";
+import * as schema from "../db/schema";
+import { sendEmail } from "../verification/send-verification-email";
+import { IS_CLOUD } from "../constants";
+
+const { handler, api } = betterAuth({
+ database: drizzleAdapter(db, {
+ provider: "pg",
+ schema: schema,
+ }),
+ logger: {
+ disabled: process.env.NODE_ENV === "production",
+ },
+ appName: "Dokploy",
+ socialProviders: {
+ github: {
+ clientId: process.env.GITHUB_CLIENT_ID as string,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
+ },
+ google: {
+ clientId: process.env.GOOGLE_CLIENT_ID as string,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
+ },
+ },
+ emailVerification: {
+ sendOnSignUp: true,
+ autoSignInAfterVerification: true,
+ sendVerificationEmail: async ({ user, url }) => {
+ if (IS_CLOUD) {
+ await sendEmail({
+ email: user.email,
+ subject: "Verify your email",
+ text: `
+ Click the link to verify your email: Verify Email
+ `,
+ });
+ }
+ },
+ },
+ emailAndPassword: {
+ enabled: true,
+ autoSignIn: !IS_CLOUD,
+ requireEmailVerification: IS_CLOUD,
+ password: {
+ async hash(password) {
+ return bcrypt.hashSync(password, 10);
+ },
+ async verify({ hash, password }) {
+ return bcrypt.compareSync(password, hash);
+ },
+ },
+ sendResetPassword: async ({ user, url }) => {
+ await sendEmail({
+ email: user.email,
+ subject: "Reset your password",
+ text: `
+ Click the link to reset your password: Reset Password
+ `,
+ });
+ },
+ },
+ databaseHooks: {
+ user: {
+ create: {
+ after: async (user) => {
+ const isAdminPresent = await db.query.member.findFirst({
+ where: eq(schema.member.role, "owner"),
+ });
+
+ if (IS_CLOUD || !isAdminPresent) {
+ await db.transaction(async (tx) => {
+ const organization = await tx
+ .insert(schema.organization)
+ .values({
+ name: "My Organization",
+ ownerId: user.id,
+ createdAt: new Date(),
+ })
+ .returning()
+ .then((res) => res[0]);
+
+ await tx.insert(schema.member).values({
+ userId: user.id,
+ organizationId: organization?.id || "",
+ role: "owner",
+ createdAt: new Date(),
+ });
+ });
+ }
+ },
+ },
+ },
+ session: {
+ create: {
+ before: async (session) => {
+ const member = await db.query.member.findFirst({
+ where: eq(schema.member.userId, session.userId),
+ orderBy: desc(schema.member.createdAt),
+ with: {
+ organization: true,
+ },
+ });
+
+ return {
+ data: {
+ ...session,
+ activeOrganizationId: member?.organization.id,
+ },
+ };
+ },
+ },
+ },
+ },
+ user: {
+ modelName: "users_temp",
+ additionalFields: {
+ role: {
+ type: "string",
+ // required: true,
+ input: false,
+ },
+ ownerId: {
+ type: "string",
+ // required: true,
+ input: false,
+ },
+ },
+ },
+
+ plugins: [
+ apiKey({
+ enableMetadata: true,
+ }),
+ twoFactor(),
+ organization({
+ async sendInvitationEmail(data, _request) {
+ if (IS_CLOUD) {
+ const host =
+ process.env.NODE_ENV === "development"
+ ? "http://localhost:3000"
+ : "https://dokploy.com";
+ const inviteLink = `${host}/invitation?token=${data.id}`;
+
+ await sendEmail({
+ email: data.email,
+ subject: "Invitation to join organization",
+ text: `
+ You are invited to join ${data.organization.name} on Dokploy. Click the link to accept the invitation: Accept Invitation
+ `,
+ });
+ }
+ },
+ }),
+ ],
+});
+
+export const auth = {
+ handler,
+ createApiKey: api.createApiKey,
+};
+
+export const validateRequest = async (request: IncomingMessage) => {
+ const apiKey = request.headers["x-api-key"] as string;
+ if (apiKey) {
+ try {
+ const { valid, key, error } = await api.verifyApiKey({
+ body: {
+ key: apiKey,
+ },
+ });
+
+ if (error) {
+ throw new Error(error.message || "Error verifying API key");
+ }
+ if (!valid || !key) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ const apiKeyRecord = await db.query.apikey.findFirst({
+ where: eq(schema.apikey.id, key.id),
+ with: {
+ user: true,
+ },
+ });
+
+ if (!apiKeyRecord) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ const organizationId = JSON.parse(
+ apiKeyRecord.metadata || "{}",
+ ).organizationId;
+
+ if (!organizationId) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ const member = await db.query.member.findFirst({
+ where: and(
+ eq(schema.member.userId, apiKeyRecord.user.id),
+ eq(schema.member.organizationId, organizationId),
+ ),
+ with: {
+ organization: true,
+ },
+ });
+
+ const {
+ id,
+ name,
+ email,
+ emailVerified,
+ image,
+ createdAt,
+ updatedAt,
+ twoFactorEnabled,
+ } = apiKeyRecord.user;
+
+ const mockSession = {
+ session: {
+ user: {
+ id: apiKeyRecord.user.id,
+ email: apiKeyRecord.user.email,
+ name: apiKeyRecord.user.name,
+ },
+ activeOrganizationId: organizationId || "",
+ },
+ user: {
+ id,
+ name,
+ email,
+ emailVerified,
+ image,
+ createdAt,
+ updatedAt,
+ twoFactorEnabled,
+ role: member?.role || "member",
+ ownerId: member?.organization.ownerId || apiKeyRecord.user.id,
+ },
+ };
+
+ return mockSession;
+ } catch (error) {
+ console.error("Error verifying API key", error);
+ return {
+ session: null,
+ user: null,
+ };
+ }
+ }
+
+ // If no API key, proceed with normal session validation
+ const session = await api.getSession({
+ headers: new Headers({
+ cookie: request.headers.cookie || "",
+ }),
+ });
+
+ if (!session?.session || !session.user) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ if (session?.user) {
+ const member = await db.query.member.findFirst({
+ where: and(
+ eq(schema.member.userId, session.user.id),
+ eq(
+ schema.member.organizationId,
+ session.session.activeOrganizationId || "",
+ ),
+ ),
+ with: {
+ organization: true,
+ },
+ });
+
+ session.user.role = member?.role || "member";
+ if (member) {
+ session.user.ownerId = member.organization.ownerId;
+ } else {
+ session.user.ownerId = session.user.id;
+ }
+ }
+
+ return session;
+};
diff --git a/packages/server/src/monitoring/utils.ts b/packages/server/src/monitoring/utils.ts
index 561cc835..147ade0a 100644
--- a/packages/server/src/monitoring/utils.ts
+++ b/packages/server/src/monitoring/utils.ts
@@ -73,7 +73,7 @@ export const readStatsFile = async (
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
const data = await promises.readFile(filePath, "utf-8");
return JSON.parse(data);
- } catch (error) {
+ } catch (_error) {
return [];
}
};
@@ -108,7 +108,7 @@ export const readLastValueStatsFile = async (
const data = await promises.readFile(filePath, "utf-8");
const stats = JSON.parse(data);
return stats[stats.length - 1] || null;
- } catch (error) {
+ } catch (_error) {
return null;
}
};
diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts
index b6d9c7fb..3509868b 100644
--- a/packages/server/src/services/admin.ts
+++ b/packages/server/src/services/admin.ts
@@ -1,124 +1,56 @@
-import { randomBytes } from "node:crypto";
import { db } from "@dokploy/server/db";
import {
- admins,
- type apiCreateUserInvitation,
- auth,
- users,
+ invitation,
+ member,
+ organization,
+ users_temp,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
-import * as bcrypt from "bcrypt";
import { eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
-export type Admin = typeof admins.$inferSelect;
-export const createInvitation = async (
- input: typeof apiCreateUserInvitation._type,
- adminId: string,
-) => {
- await db.transaction(async (tx) => {
- const result = await tx
- .insert(auth)
- .values({
- email: input.email.toLowerCase(),
- rol: "user",
- password: bcrypt.hashSync("01231203012312", 10),
- })
- .returning()
- .then((res) => res[0]);
-
- if (!result) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- });
- }
- const expiresIn24Hours = new Date();
- expiresIn24Hours.setDate(expiresIn24Hours.getDate() + 1);
- const token = randomBytes(32).toString("hex");
- await tx
- .insert(users)
- .values({
- adminId: adminId,
- authId: result.id,
- token,
- expirationDate: expiresIn24Hours.toISOString(),
- })
- .returning();
+export const findUserById = async (userId: string) => {
+ const user = await db.query.users_temp.findFirst({
+ where: eq(users_temp.id, userId),
+ // with: {
+ // account: true,
+ // },
});
-};
-
-export const findAdminById = async (adminId: string) => {
- const admin = await db.query.admins.findFirst({
- where: eq(admins.adminId, adminId),
- });
- if (!admin) {
+ if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
- message: "Admin not found",
+ message: "User not found",
});
}
- return admin;
+ return user;
};
-export const updateAdmin = async (
- authId: string,
- adminData: Partial,
-) => {
- const admin = await db
- .update(admins)
- .set({
- ...adminData,
- })
- .where(eq(admins.authId, authId))
- .returning()
- .then((res) => res[0]);
-
- return admin;
-};
-
-export const updateAdminById = async (
- adminId: string,
- adminData: Partial,
-) => {
- const admin = await db
- .update(admins)
- .set({
- ...adminData,
- })
- .where(eq(admins.adminId, adminId))
- .returning()
- .then((res) => res[0]);
-
- return admin;
+export const findOrganizationById = async (organizationId: string) => {
+ const organizationResult = await db.query.organization.findFirst({
+ where: eq(organization.id, organizationId),
+ });
+ return organizationResult;
};
export const isAdminPresent = async () => {
- const admin = await db.query.admins.findFirst();
+ const admin = await db.query.member.findFirst({
+ where: eq(member.role, "owner"),
+ });
+
if (!admin) {
return false;
}
return true;
};
-export const findAdminByAuthId = async (authId: string) => {
- const admin = await db.query.admins.findFirst({
- where: eq(admins.authId, authId),
+export const findAdmin = async () => {
+ const admin = await db.query.member.findFirst({
+ where: eq(member.role, "owner"),
with: {
- users: true,
+ user: true,
},
});
- if (!admin) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Admin not found",
- });
- }
- return admin;
-};
-export const findAdmin = async () => {
- const admin = await db.query.admins.findFirst({});
if (!admin) {
throw new TRPCError({
code: "NOT_FOUND",
@@ -129,14 +61,15 @@ export const findAdmin = async () => {
};
export const getUserByToken = async (token: string) => {
- const user = await db.query.users.findFirst({
- where: eq(users.token, token),
- with: {
- auth: {
- columns: {
- password: false,
- },
- },
+ const user = await db.query.invitation.findFirst({
+ where: eq(invitation.id, token),
+ columns: {
+ id: true,
+ email: true,
+ status: true,
+ expiresAt: true,
+ role: true,
+ inviterId: true,
},
});
@@ -146,34 +79,23 @@ export const getUserByToken = async (token: string) => {
message: "Invitation not found",
});
}
+
+ const userAlreadyExists = await db.query.users_temp.findFirst({
+ where: eq(users_temp.email, user?.email || ""),
+ });
+
+ const { expiresAt, ...rest } = user;
return {
- ...user,
- isExpired: user.isRegistered,
+ ...rest,
+ isExpired: user.expiresAt < new Date(),
+ userAlreadyExists: !!userAlreadyExists,
};
};
-export const removeUserByAuthId = async (authId: string) => {
+export const removeUserById = async (userId: string) => {
await db
- .delete(auth)
- .where(eq(auth.id, authId))
- .returning()
- .then((res) => res[0]);
-};
-
-export const removeAdminByAuthId = async (authId: string) => {
- const admin = await findAdminByAuthId(authId);
- if (!admin) return null;
-
- // First delete all associated users
- const users = admin.users;
-
- for (const user of users) {
- await removeUserByAuthId(user.authId);
- }
- // Then delete the auth record which will cascade delete the admin
- return await db
- .delete(auth)
- .where(eq(auth.id, authId))
+ .delete(users_temp)
+ .where(eq(users_temp.id, userId))
.returning()
.then((res) => res[0]);
};
@@ -184,8 +106,8 @@ export const getDokployUrl = async () => {
}
const admin = await findAdmin();
- if (admin.host) {
- return `https://${admin.host}`;
+ if (admin.user.host) {
+ return `https://${admin.user.host}`;
}
- return `http://${admin.serverIp}:${process.env.PORT}`;
+ return `http://${admin.user.serverIp}:${process.env.PORT}`;
};
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index c102e8ed..425a6adb 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -4,7 +4,6 @@ import {
type apiCreateApplication,
applications,
buildAppName,
- cleanAppName,
} from "@dokploy/server/db/schema";
import { getAdvancedStats } from "@dokploy/server/monitoring/utils";
import {
@@ -28,7 +27,6 @@ import {
getCustomGitCloneCommand,
} from "@dokploy/server/utils/providers/git";
import {
- authGithub,
cloneGithubRepository,
getGithubCloneCommand,
} from "@dokploy/server/utils/providers/github";
@@ -40,7 +38,7 @@ import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
-import { findAdminById, getDokployUrl } from "./admin";
+import { getDokployUrl } from "./admin";
import {
createDeployment,
createDeploymentPreview,
@@ -58,7 +56,6 @@ import {
updatePreviewDeployment,
} from "./preview-deployment";
import { validUniqueServerAppName } from "./project";
-import { cleanupFullDocker } from "./settings";
export type Application = typeof applications.$inferSelect;
export const createApplication = async (
@@ -185,11 +182,11 @@ export const deployApplication = async ({
});
try {
- const admin = await findAdminById(application.project.adminId);
+ // const admin = await findUserById(application.project.userId);
- if (admin.cleanupCacheApplications) {
- await cleanupFullDocker(application?.serverId);
- }
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
if (application.sourceType === "github") {
await cloneGithubRepository({
@@ -220,7 +217,7 @@ export const deployApplication = async ({
applicationName: application.name,
applicationType: "application",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
domains: application.domains,
});
} catch (error) {
@@ -233,7 +230,7 @@ export const deployApplication = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
});
throw error;
@@ -260,11 +257,11 @@ export const rebuildApplication = async ({
});
try {
- const admin = await findAdminById(application.project.adminId);
+ // const admin = await findUserById(application.project.userId);
- if (admin.cleanupCacheApplications) {
- await cleanupFullDocker(application?.serverId);
- }
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
@@ -309,11 +306,11 @@ export const deployRemoteApplication = async ({
try {
if (application.serverId) {
- const admin = await findAdminById(application.project.adminId);
+ // const admin = await findUserById(application.project.userId);
- if (admin.cleanupCacheApplications) {
- await cleanupFullDocker(application?.serverId);
- }
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
@@ -352,7 +349,7 @@ export const deployRemoteApplication = async ({
applicationName: application.name,
applicationType: "application",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
domains: application.domains,
});
} catch (error) {
@@ -376,7 +373,7 @@ export const deployRemoteApplication = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
});
throw error;
@@ -454,11 +451,11 @@ export const deployPreviewApplication = async ({
application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.buildArgs = application.previewBuildArgs;
- const admin = await findAdminById(application.project.adminId);
+ // const admin = await findUserById(application.project.userId);
- if (admin.cleanupCacheOnPreviews) {
- await cleanupFullDocker(application?.serverId);
- }
+ // if (admin.cleanupCacheOnPreviews) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
if (application.sourceType === "github") {
await cloneGithubRepository({
@@ -568,11 +565,11 @@ export const deployRemotePreviewApplication = async ({
application.buildArgs = application.previewBuildArgs;
if (application.serverId) {
- const admin = await findAdminById(application.project.adminId);
+ // const admin = await findUserById(application.project.userId);
- if (admin.cleanupCacheOnPreviews) {
- await cleanupFullDocker(application?.serverId);
- }
+ // if (admin.cleanupCacheOnPreviews) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
@@ -637,11 +634,11 @@ export const rebuildRemoteApplication = async ({
try {
if (application.serverId) {
- const admin = await findAdminById(application.project.adminId);
+ // const admin = await findUserById(application.project.userId);
- if (admin.cleanupCacheApplications) {
- await cleanupFullDocker(application?.serverId);
- }
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
if (application.sourceType !== "docker") {
let command = "set -e;";
command += getBuildCommand(application, deployment.logPath);
diff --git a/packages/server/src/services/auth.ts b/packages/server/src/services/auth.ts
deleted file mode 100644
index 65a01c41..00000000
--- a/packages/server/src/services/auth.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import { randomBytes } from "node:crypto";
-import { db } from "@dokploy/server/db";
-import {
- admins,
- type apiCreateAdmin,
- type apiCreateUser,
- auth,
- users,
-} from "@dokploy/server/db/schema";
-import { getPublicIpWithFallback } from "@dokploy/server/wss/utils";
-import { TRPCError } from "@trpc/server";
-import * as bcrypt from "bcrypt";
-import { eq } from "drizzle-orm";
-import encode from "hi-base32";
-import { TOTP } from "otpauth";
-import QRCode from "qrcode";
-import { IS_CLOUD } from "../constants";
-
-export type Auth = typeof auth.$inferSelect;
-
-export const createAdmin = async (input: typeof apiCreateAdmin._type) => {
- return await db.transaction(async (tx) => {
- const hashedPassword = bcrypt.hashSync(input.password, 10);
- const newAuth = await tx
- .insert(auth)
- .values({
- email: input.email.toLowerCase(),
- password: hashedPassword,
- rol: "admin",
- })
- .returning()
- .then((res) => res[0]);
-
- if (!newAuth) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- });
- }
-
- await tx
- .insert(admins)
- .values({
- authId: newAuth.id,
- ...(!IS_CLOUD && {
- serverIp:
- process.env.ADVERTISE_ADDR || (await getPublicIpWithFallback()),
- }),
- })
- .returning();
-
- return newAuth;
- });
-};
-
-export const createUser = async (input: typeof apiCreateUser._type) => {
- return await db.transaction(async (tx) => {
- const hashedPassword = bcrypt.hashSync(input.password, 10);
- const res = await tx
- .update(auth)
- .set({
- password: hashedPassword,
- })
- .where(eq(auth.id, input.id))
- .returning()
- .then((res) => res[0]);
-
- if (!res) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- });
- }
-
- const user = await tx
- .update(users)
- .set({
- isRegistered: true,
- expirationDate: undefined,
- })
- .where(eq(users.token, input.token))
- .returning()
- .then((res) => res[0]);
-
- return user;
- });
-};
-
-export const findAuthByEmail = async (email: string) => {
- const result = await db.query.auth.findFirst({
- where: eq(auth.email, email),
- });
- if (!result) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- return result;
-};
-
-export const findAuthById = async (authId: string) => {
- const result = await db.query.auth.findFirst({
- where: eq(auth.id, authId),
- columns: {
- password: false,
- },
- });
- if (!result) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Auth not found",
- });
- }
- return result;
-};
-
-export const updateAuthById = async (
- authId: string,
- authData: Partial,
-) => {
- const result = await db
- .update(auth)
- .set({
- ...authData,
- })
- .where(eq(auth.id, authId))
- .returning();
-
- return result[0];
-};
-
-export const generate2FASecret = async (authId: string) => {
- const auth = await findAuthById(authId);
-
- const base32_secret = generateBase32Secret();
-
- const totp = new TOTP({
- issuer: "Dokploy",
- label: `${auth?.email}`,
- algorithm: "SHA1",
- digits: 6,
- secret: base32_secret,
- });
-
- const otpauth_url = totp.toString();
-
- const qrUrl = await QRCode.toDataURL(otpauth_url);
-
- return {
- qrCodeUrl: qrUrl,
- secret: base32_secret,
- };
-};
-
-export const verify2FA = async (
- auth: Omit,
- secret: string,
- pin: string,
-) => {
- const totp = new TOTP({
- issuer: "Dokploy",
- label: `${auth?.email}`,
- algorithm: "SHA1",
- digits: 6,
- secret: secret,
- });
-
- const delta = totp.validate({ token: pin });
-
- if (delta === null) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid 2FA code",
- });
- }
- return auth;
-};
-
-const generateBase32Secret = () => {
- const buffer = randomBytes(15);
- const base32 = encode.encode(buffer).replace(/=/g, "").substring(0, 24);
- return base32;
-};
diff --git a/packages/server/src/services/backup.ts b/packages/server/src/services/backup.ts
index ef3d0446..32705786 100644
--- a/packages/server/src/services/backup.ts
+++ b/packages/server/src/services/backup.ts
@@ -2,8 +2,6 @@ import { db } from "@dokploy/server/db";
import { type apiCreateBackup, backups } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
-import { IS_CLOUD } from "../constants";
-import { removeScheduleBackup, scheduleBackup } from "../utils/backups/utils";
export type Backup = typeof backups.$inferSelect;
diff --git a/packages/server/src/services/bitbucket.ts b/packages/server/src/services/bitbucket.ts
index 21807156..7b5be7d6 100644
--- a/packages/server/src/services/bitbucket.ts
+++ b/packages/server/src/services/bitbucket.ts
@@ -12,14 +12,14 @@ export type Bitbucket = typeof bitbucket.$inferSelect;
export const createBitbucket = async (
input: typeof apiCreateBitbucket._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "bitbucket",
- adminId: adminId,
+ organizationId: organizationId,
name: input.name,
})
.returning()
@@ -74,12 +74,12 @@ export const updateBitbucket = async (
.where(eq(bitbucket.bitbucketId, bitbucketId))
.returning();
- if (input.name || input.adminId) {
+ if (input.name || input.organizationId) {
await tx
.update(gitProvider)
.set({
name: input.name,
- adminId: input.adminId,
+ organizationId: input.organizationId,
})
.where(eq(gitProvider.gitProviderId, input.gitProviderId))
.returning();
diff --git a/packages/server/src/services/certificate.ts b/packages/server/src/services/certificate.ts
index 23177862..f59f1c2a 100644
--- a/packages/server/src/services/certificate.ts
+++ b/packages/server/src/services/certificate.ts
@@ -33,13 +33,13 @@ export const findCertificateById = async (certificateId: string) => {
export const createCertificate = async (
certificateData: z.infer,
- adminId: string,
+ organizationId: string,
) => {
const certificate = await db
.insert(certificates)
.values({
...certificateData,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning();
diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts
index 39bf423f..a3ebc26c 100644
--- a/packages/server/src/services/compose.ts
+++ b/packages/server/src/services/compose.ts
@@ -44,10 +44,9 @@ import {
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils";
-import { findAdminById, getDokployUrl } from "./admin";
+import { getDokployUrl } from "./admin";
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
import { validUniqueServerAppName } from "./project";
-import { cleanupFullDocker } from "./settings";
export type Compose = typeof compose.$inferSelect;
@@ -217,10 +216,10 @@ export const deployCompose = async ({
});
try {
- const admin = await findAdminById(compose.project.adminId);
- if (admin.cleanupCacheOnCompose) {
- await cleanupFullDocker(compose?.serverId);
- }
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
if (compose.sourceType === "github") {
await cloneGithubRepository({
...compose,
@@ -247,7 +246,7 @@ export const deployCompose = async ({
applicationName: compose.name,
applicationType: "compose",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
domains: compose.domains,
});
} catch (error) {
@@ -262,7 +261,7 @@ export const deployCompose = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
});
throw error;
}
@@ -286,10 +285,10 @@ export const rebuildCompose = async ({
});
try {
- const admin = await findAdminById(compose.project.adminId);
- if (admin.cleanupCacheOnCompose) {
- await cleanupFullDocker(compose?.serverId);
- }
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
} else {
@@ -332,10 +331,10 @@ export const deployRemoteCompose = async ({
});
try {
if (compose.serverId) {
- const admin = await findAdminById(compose.project.adminId);
- if (admin.cleanupCacheOnCompose) {
- await cleanupFullDocker(compose?.serverId);
- }
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
let command = "set -e;";
if (compose.sourceType === "github") {
@@ -381,7 +380,7 @@ export const deployRemoteCompose = async ({
applicationName: compose.name,
applicationType: "compose",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
domains: compose.domains,
});
} catch (error) {
@@ -406,7 +405,7 @@ export const deployRemoteCompose = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
});
throw error;
}
@@ -430,10 +429,10 @@ export const rebuildRemoteCompose = async ({
});
try {
- const admin = await findAdminById(compose.project.adminId);
- if (admin.cleanupCacheOnCompose) {
- await cleanupFullDocker(compose?.serverId);
- }
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
}
diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts
index 096bdf19..86d6c88e 100644
--- a/packages/server/src/services/deployment.ts
+++ b/packages/server/src/services/deployment.ts
@@ -12,7 +12,7 @@ import {
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
import { TRPCError } from "@trpc/server";
import { format } from "date-fns";
-import { and, desc, eq, isNull } from "drizzle-orm";
+import { desc, eq } from "drizzle-orm";
import {
type Application,
findApplicationById,
@@ -278,9 +278,11 @@ export const removeDeployment = async (deploymentId: string) => {
.returning();
return deployment[0];
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error creating the deployment";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this deployment",
+ message,
});
}
};
@@ -535,9 +537,11 @@ export const createServerDeployment = async (
}
return deploymentCreate[0];
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error creating the deployment";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error creating the deployment",
+ message,
});
}
};
diff --git a/packages/server/src/services/destination.ts b/packages/server/src/services/destination.ts
index 892c9354..e66f8695 100644
--- a/packages/server/src/services/destination.ts
+++ b/packages/server/src/services/destination.ts
@@ -10,13 +10,13 @@ export type Destination = typeof destinations.$inferSelect;
export const createDestintation = async (
input: typeof apiCreateDestination._type,
- adminId: string,
+ organizationId: string,
) => {
const newDestination = await db
.insert(destinations)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -46,14 +46,14 @@ export const findDestinationById = async (destinationId: string) => {
export const removeDestinationById = async (
destinationId: string,
- adminId: string,
+ organizationId: string,
) => {
const result = await db
.delete(destinations)
.where(
and(
eq(destinations.destinationId, destinationId),
- eq(destinations.adminId, adminId),
+ eq(destinations.organizationId, organizationId),
),
)
.returning();
@@ -73,7 +73,7 @@ export const updateDestinationById = async (
.where(
and(
eq(destinations.destinationId, destinationId),
- eq(destinations.adminId, destinationData.adminId || ""),
+ eq(destinations.organizationId, destinationData.organizationId || ""),
),
)
.returning();
diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts
index 597c03fa..a4a3b0b5 100644
--- a/packages/server/src/services/docker.ts
+++ b/packages/server/src/services/docker.ts
@@ -98,7 +98,7 @@ export const getConfig = async (
const config = JSON.parse(stdout);
return config;
- } catch (error) {}
+ } catch (_error) {}
};
export const getContainersByAppNameMatch = async (
@@ -156,7 +156,7 @@ export const getContainersByAppNameMatch = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -214,7 +214,7 @@ export const getStackContainersByAppName = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -274,7 +274,7 @@ export const getServiceContainersByAppName = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -325,7 +325,7 @@ export const getContainersByAppLabel = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -344,7 +344,7 @@ export const containerRestart = async (containerId: string) => {
const config = JSON.parse(stdout);
return config;
- } catch (error) {}
+ } catch (_error) {}
};
export const getSwarmNodes = async (serverId?: string) => {
@@ -373,7 +373,7 @@ export const getSwarmNodes = async (serverId?: string) => {
.split("\n")
.map((line) => JSON.parse(line));
return nodesArray;
- } catch (error) {}
+ } catch (_error) {}
};
export const getNodeInfo = async (nodeId: string, serverId?: string) => {
@@ -399,7 +399,7 @@ export const getNodeInfo = async (nodeId: string, serverId?: string) => {
const nodeInfo = JSON.parse(stdout);
return nodeInfo;
- } catch (error) {}
+ } catch (_error) {}
};
export const getNodeApplications = async (serverId?: string) => {
@@ -431,7 +431,7 @@ export const getNodeApplications = async (serverId?: string) => {
.filter((service) => !service.Name.startsWith("dokploy-"));
return appArray;
- } catch (error) {}
+ } catch (_error) {}
};
export const getApplicationInfo = async (
@@ -464,5 +464,5 @@ export const getApplicationInfo = async (
.map((line) => JSON.parse(line));
return appArray;
- } catch (error) {}
+ } catch (_error) {}
};
diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts
index b99c4869..d2e23c06 100644
--- a/packages/server/src/services/domain.ts
+++ b/packages/server/src/services/domain.ts
@@ -4,7 +4,7 @@ import { manageDomain } from "@dokploy/server/utils/traefik/domain";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { type apiCreateDomain, domains } from "../db/schema";
-import { findAdmin, findAdminById } from "./admin";
+import { findUserById } from "./admin";
import { findApplicationById } from "./application";
import { findServerById } from "./server";
@@ -40,7 +40,7 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
export const generateTraefikMeDomain = async (
appName: string,
- adminId: string,
+ userId: string,
serverId?: string,
) => {
if (serverId) {
@@ -57,7 +57,7 @@ export const generateTraefikMeDomain = async (
projectName: appName,
});
}
- const admin = await findAdminById(adminId);
+ const admin = await findUserById(userId);
return generateRandomDomain({
serverIp: admin?.serverIp || "",
projectName: appName,
@@ -126,7 +126,6 @@ export const updateDomainById = async (
export const removeDomainById = async (domainId: string) => {
await findDomainById(domainId);
- // TODO: fix order
const result = await db
.delete(domains)
.where(eq(domains.domainId, domainId))
diff --git a/packages/server/src/services/github.ts b/packages/server/src/services/github.ts
index a5d9d863..19deb2b2 100644
--- a/packages/server/src/services/github.ts
+++ b/packages/server/src/services/github.ts
@@ -12,14 +12,14 @@ import { updatePreviewDeployment } from "./preview-deployment";
export type Github = typeof github.$inferSelect;
export const createGithub = async (
input: typeof apiCreateGithub._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "github",
- adminId: adminId,
+ organizationId: organizationId,
name: input.name,
})
.returning()
@@ -119,7 +119,7 @@ export const issueCommentExists = async ({
comment_id: comment_id,
});
return true;
- } catch (error) {
+ } catch (_error) {
return false;
}
};
diff --git a/packages/server/src/services/gitlab.ts b/packages/server/src/services/gitlab.ts
index 8e1362c9..fdca2775 100644
--- a/packages/server/src/services/gitlab.ts
+++ b/packages/server/src/services/gitlab.ts
@@ -1,9 +1,7 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateGitlab,
- type bitbucket,
gitProvider,
- type github,
gitlab,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
@@ -13,14 +11,14 @@ export type Gitlab = typeof gitlab.$inferSelect;
export const createGitlab = async (
input: typeof apiCreateGitlab._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "gitlab",
- adminId: adminId,
+ organizationId: organizationId,
name: input.name,
})
.returning()
diff --git a/packages/server/src/services/mariadb.ts b/packages/server/src/services/mariadb.ts
index 8257b587..00be29d6 100644
--- a/packages/server/src/services/mariadb.ts
+++ b/packages/server/src/services/mariadb.ts
@@ -4,7 +4,7 @@ import {
backups,
mariadb,
} from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMariadb } from "@dokploy/server/utils/databases/mariadb";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/mongo.ts b/packages/server/src/services/mongo.ts
index 031a6013..0ac4cc63 100644
--- a/packages/server/src/services/mongo.ts
+++ b/packages/server/src/services/mongo.ts
@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateMongo, backups, mongo } from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMongo } from "@dokploy/server/utils/databases/mongo";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/mount.ts b/packages/server/src/services/mount.ts
index 38e82d1a..836feace 100644
--- a/packages/server/src/services/mount.ts
+++ b/packages/server/src/services/mount.ts
@@ -123,8 +123,8 @@ export const updateMount = async (
mountId: string,
mountData: Partial,
) => {
- return await db.transaction(async (transaction) => {
- const mount = await db
+ return await db.transaction(async (tx) => {
+ const mount = await tx
.update(mounts)
.set({
...mountData,
@@ -211,7 +211,7 @@ export const deleteFileMount = async (mountId: string) => {
} else {
await removeFileOrDirectory(fullPath);
}
- } catch (error) {}
+ } catch (_error) {}
};
export const getBaseFilesPath = async (mountId: string) => {
diff --git a/packages/server/src/services/notification.ts b/packages/server/src/services/notification.ts
index f30d77ae..16ba2085 100644
--- a/packages/server/src/services/notification.ts
+++ b/packages/server/src/services/notification.ts
@@ -24,7 +24,7 @@ export type Notification = typeof notifications.$inferSelect;
export const createSlackNotification = async (
input: typeof apiCreateSlack._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newSlack = await tx
@@ -54,7 +54,7 @@ export const createSlackNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "slack",
- adminId: adminId,
+ organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -84,7 +84,7 @@ export const updateSlackNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
@@ -114,7 +114,7 @@ export const updateSlackNotification = async (
export const createTelegramNotification = async (
input: typeof apiCreateTelegram._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newTelegram = await tx
@@ -145,7 +145,7 @@ export const createTelegramNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "telegram",
- adminId: adminId,
+ organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -175,7 +175,7 @@ export const updateTelegramNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
@@ -206,7 +206,7 @@ export const updateTelegramNotification = async (
export const createDiscordNotification = async (
input: typeof apiCreateDiscord._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newDiscord = await tx
@@ -236,7 +236,7 @@ export const createDiscordNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "discord",
- adminId: adminId,
+ organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -266,7 +266,7 @@ export const updateDiscordNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
@@ -296,7 +296,7 @@ export const updateDiscordNotification = async (
export const createEmailNotification = async (
input: typeof apiCreateEmail._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newEmail = await tx
@@ -330,7 +330,7 @@ export const createEmailNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "email",
- adminId: adminId,
+ organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -360,7 +360,7 @@ export const updateEmailNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
@@ -394,7 +394,7 @@ export const updateEmailNotification = async (
export const createGotifyNotification = async (
input: typeof apiCreateGotify._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newGotify = await tx
@@ -426,7 +426,7 @@ export const createGotifyNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "gotify",
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -455,7 +455,7 @@ export const updateGotifyNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
diff --git a/packages/server/src/services/postgres.ts b/packages/server/src/services/postgres.ts
index 682d3f78..75b81c50 100644
--- a/packages/server/src/services/postgres.ts
+++ b/packages/server/src/services/postgres.ts
@@ -4,7 +4,7 @@ import {
backups,
postgres,
} from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildPostgres } from "@dokploy/server/utils/databases/postgres";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/preview-deployment.ts b/packages/server/src/services/preview-deployment.ts
index ab38c17c..a1ffca4b 100644
--- a/packages/server/src/services/preview-deployment.ts
+++ b/packages/server/src/services/preview-deployment.ts
@@ -2,23 +2,20 @@ import { db } from "@dokploy/server/db";
import {
type apiCreatePreviewDeployment,
deployments,
+ organization,
previewDeployments,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, desc, eq } from "drizzle-orm";
-import { slugify } from "../setup/server-setup";
-import { generatePassword, generateRandomDomain } from "../templates/utils";
+import { generatePassword } from "../templates/utils";
import { removeService } from "../utils/docker/utils";
import { removeDirectoryCode } from "../utils/filesystem/directory";
import { authGithub } from "../utils/providers/github";
import { removeTraefikConfig } from "../utils/traefik/application";
import { manageDomain } from "../utils/traefik/domain";
-import { findAdminById } from "./admin";
+import { findUserById } from "./admin";
import { findApplicationById } from "./application";
-import {
- removeDeployments,
- removeDeploymentsByPreviewDeploymentId,
-} from "./deployment";
+import { removeDeploymentsByPreviewDeploymentId } from "./deployment";
import { createDomain } from "./domain";
import { type Github, getIssueComment } from "./github";
@@ -106,13 +103,17 @@ export const removePreviewDeployment = async (previewDeploymentId: string) => {
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_error) {}
}
return deployment[0];
} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this preview deployment";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this preview deployment",
+ message,
});
}
};
@@ -154,11 +155,14 @@ export const createPreviewDeployment = async (
const application = await findApplicationById(schema.applicationId);
const appName = `preview-${application.appName}-${generatePassword(6)}`;
+ const org = await db.query.organization.findFirst({
+ where: eq(organization.id, application.project.organizationId),
+ });
const generateDomain = await generateWildcardDomain(
application.previewWildcard || "*.traefik.me",
appName,
application.server?.ipAddress || "",
- application.project.adminId,
+ org?.ownerId || "",
);
const octokit = authGithub(application?.github as Github);
@@ -250,7 +254,7 @@ const generateWildcardDomain = async (
baseDomain: string,
appName: string,
serverIp: string,
- adminId: string,
+ userId: string,
): Promise => {
if (!baseDomain.startsWith("*.")) {
throw new Error('The base domain must start with "*."');
@@ -268,7 +272,7 @@ const generateWildcardDomain = async (
}
if (!ip) {
- const admin = await findAdminById(adminId);
+ const admin = await findUserById(userId);
ip = admin?.serverIp || "";
}
diff --git a/packages/server/src/services/project.ts b/packages/server/src/services/project.ts
index adaa07ea..b740834b 100644
--- a/packages/server/src/services/project.ts
+++ b/packages/server/src/services/project.ts
@@ -16,13 +16,13 @@ export type Project = typeof projects.$inferSelect;
export const createProject = async (
input: typeof apiCreateProject._type,
- adminId: string,
+ organizationId: string,
) => {
const newProject = await db
.insert(projects)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
diff --git a/packages/server/src/services/redirect.ts b/packages/server/src/services/redirect.ts
index f16dbe42..1896105f 100644
--- a/packages/server/src/services/redirect.ts
+++ b/packages/server/src/services/redirect.ts
@@ -6,7 +6,7 @@ import {
updateRedirectMiddleware,
} from "@dokploy/server/utils/traefik/redirect";
import { TRPCError } from "@trpc/server";
-import { desc, eq } from "drizzle-orm";
+import { eq } from "drizzle-orm";
import type { z } from "zod";
import { findApplicationById } from "./application";
export type Redirect = typeof redirects.$inferSelect;
@@ -114,9 +114,11 @@ export const updateRedirectById = async (
return redirect;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this redirect";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this redirect",
+ message,
});
}
};
diff --git a/packages/server/src/services/redis.ts b/packages/server/src/services/redis.ts
index e0dbbe02..9f4a1f9e 100644
--- a/packages/server/src/services/redis.ts
+++ b/packages/server/src/services/redis.ts
@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateRedis, redis } from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildRedis } from "@dokploy/server/utils/databases/redis";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts
index 2bcf3a4a..6468cd97 100644
--- a/packages/server/src/services/registry.ts
+++ b/packages/server/src/services/registry.ts
@@ -12,14 +12,14 @@ export type Registry = typeof registry.$inferSelect;
export const createRegistry = async (
input: typeof apiCreateRegistry._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newRegistry = await tx
.insert(registry)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -112,9 +112,11 @@ export const updateRegistry = async (
return response;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this registry";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this registry",
+ message,
});
}
};
@@ -135,9 +137,11 @@ export const findRegistryById = async (registryId: string) => {
return registryResponse;
};
-export const findAllRegistryByAdminId = async (adminId: string) => {
+export const findAllRegistryByOrganizationId = async (
+ organizationId: string,
+) => {
const registryResponse = await db.query.registry.findMany({
- where: eq(registry.adminId, adminId),
+ where: eq(registry.organizationId, organizationId),
});
return registryResponse;
};
diff --git a/packages/server/src/services/security.ts b/packages/server/src/services/security.ts
index 5efca19f..d6947b88 100644
--- a/packages/server/src/services/security.ts
+++ b/packages/server/src/services/security.ts
@@ -76,9 +76,11 @@ export const deleteSecurityById = async (securityId: string) => {
await removeSecurityMiddleware(application, result);
return result;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error removing this security";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error removing this security",
+ message,
});
}
};
@@ -98,9 +100,11 @@ export const updateSecurityById = async (
return response[0];
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this security";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this security",
+ message,
});
}
};
diff --git a/packages/server/src/services/server.ts b/packages/server/src/services/server.ts
index 081b19fa..a4d5c5d8 100644
--- a/packages/server/src/services/server.ts
+++ b/packages/server/src/services/server.ts
@@ -1,19 +1,24 @@
import { db } from "@dokploy/server/db";
-import { type apiCreateServer, server } from "@dokploy/server/db/schema";
+import {
+ type apiCreateServer,
+ organization,
+ server,
+} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
-import { desc, eq } from "drizzle-orm";
+import { eq } from "drizzle-orm";
export type Server = typeof server.$inferSelect;
export const createServer = async (
input: typeof apiCreateServer._type,
- adminId: string,
+ organizationId: string,
) => {
const newServer = await db
.insert(server)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
+ createdAt: new Date().toISOString(),
})
.returning()
.then((value) => value[0]);
@@ -45,12 +50,16 @@ export const findServerById = async (serverId: string) => {
return currentServer;
};
-export const findServersByAdminId = async (adminId: string) => {
- const servers = await db.query.server.findMany({
- where: eq(server.adminId, adminId),
- orderBy: desc(server.createdAt),
+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;
};
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index dd5b4e18..75613be0 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -5,8 +5,6 @@ import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
-import { findAdminById } from "./admin";
-// import packageInfo from "../../../package.json";
export interface IUpdateData {
latestVersion: string | null;
@@ -171,7 +169,6 @@ echo "$json_output"
const result = JSON.parse(stdout);
return result;
}
- const items = readdirSync(dirPath, { withFileTypes: true });
const stack = [dirPath];
const result: TreeDataItem[] = [];
diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts
index d8d9862c..39ac95ce 100644
--- a/packages/server/src/services/user.ts
+++ b/packages/server/src/services/user.ts
@@ -1,80 +1,53 @@
import { db } from "@dokploy/server/db";
-import { users } from "@dokploy/server/db/schema";
+import { apikey, member, users_temp } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
-import { eq } from "drizzle-orm";
+import { and, eq } from "drizzle-orm";
+import { auth } from "../lib/auth";
-export type User = typeof users.$inferSelect;
+export type User = typeof users_temp.$inferSelect;
-export const findUserById = async (userId: string) => {
- const user = await db.query.users.findFirst({
- where: eq(users.userId, userId),
- });
- if (!user) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- return user;
-};
-
-export const findUserByAuthId = async (authId: string) => {
- const user = await db.query.users.findFirst({
- where: eq(users.authId, authId),
- with: {
- auth: true,
- },
- });
- if (!user) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- return user;
-};
-
-export const findUsers = async (adminId: string) => {
- const currentUsers = await db.query.users.findMany({
- where: eq(users.adminId, adminId),
- with: {
- auth: {
- columns: {
- secret: false,
- },
- },
- },
- });
- return currentUsers;
-};
-
-export const addNewProject = async (authId: string, projectId: string) => {
- const user = await findUserByAuthId(authId);
+export const addNewProject = async (
+ userId: string,
+ projectId: string,
+ organizationId: string,
+) => {
+ const userR = await findMemberById(userId, organizationId);
await db
- .update(users)
+ .update(member)
.set({
- accessedProjects: [...user.accessedProjects, projectId],
+ accessedProjects: [...userR.accessedProjects, projectId],
})
- .where(eq(users.authId, authId));
+ .where(
+ and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
+ );
};
-export const addNewService = async (authId: string, serviceId: string) => {
- const user = await findUserByAuthId(authId);
+export const addNewService = async (
+ userId: string,
+ serviceId: string,
+ organizationId: string,
+) => {
+ const userR = await findMemberById(userId, organizationId);
await db
- .update(users)
+ .update(member)
.set({
- accessedServices: [...user.accessedServices, serviceId],
+ accessedServices: [...userR.accessedServices, serviceId],
})
- .where(eq(users.authId, authId));
+ .where(
+ and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
+ );
};
export const canPerformCreationService = async (
userId: string,
projectId: string,
+ organizationId: string,
) => {
- const { accessedProjects, canCreateServices } =
- await findUserByAuthId(userId);
+ const { accessedProjects, canCreateServices } = await findMemberById(
+ userId,
+ organizationId,
+ );
const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) {
@@ -87,8 +60,9 @@ export const canPerformCreationService = async (
export const canPerformAccessService = async (
userId: string,
serviceId: string,
+ organizationId: string,
) => {
- const { accessedServices } = await findUserByAuthId(userId);
+ const { accessedServices } = await findMemberById(userId, organizationId);
const haveAccessToService = accessedServices.includes(serviceId);
if (haveAccessToService) {
@@ -99,11 +73,14 @@ export const canPerformAccessService = async (
};
export const canPeformDeleteService = async (
- authId: string,
+ userId: string,
serviceId: string,
+ organizationId: string,
) => {
- const { accessedServices, canDeleteServices } =
- await findUserByAuthId(authId);
+ const { accessedServices, canDeleteServices } = await findMemberById(
+ userId,
+ organizationId,
+ );
const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) {
@@ -113,8 +90,11 @@ export const canPeformDeleteService = async (
return false;
};
-export const canPerformCreationProject = async (authId: string) => {
- const { canCreateProjects } = await findUserByAuthId(authId);
+export const canPerformCreationProject = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const { canCreateProjects } = await findMemberById(userId, organizationId);
if (canCreateProjects) {
return true;
@@ -123,8 +103,11 @@ export const canPerformCreationProject = async (authId: string) => {
return false;
};
-export const canPerformDeleteProject = async (authId: string) => {
- const { canDeleteProjects } = await findUserByAuthId(authId);
+export const canPerformDeleteProject = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const { canDeleteProjects } = await findMemberById(userId, organizationId);
if (canDeleteProjects) {
return true;
@@ -134,10 +117,11 @@ export const canPerformDeleteProject = async (authId: string) => {
};
export const canPerformAccessProject = async (
- authId: string,
+ userId: string,
projectId: string,
+ organizationId: string,
) => {
- const { accessedProjects } = await findUserByAuthId(authId);
+ const { accessedProjects } = await findMemberById(userId, organizationId);
const haveAccessToProject = accessedProjects.includes(projectId);
@@ -147,26 +131,45 @@ export const canPerformAccessProject = async (
return false;
};
-export const canAccessToTraefikFiles = async (authId: string) => {
- const { canAccessToTraefikFiles } = await findUserByAuthId(authId);
+export const canAccessToTraefikFiles = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const { canAccessToTraefikFiles } = await findMemberById(
+ userId,
+ organizationId,
+ );
return canAccessToTraefikFiles;
};
export const checkServiceAccess = async (
- authId: string,
+ userId: string,
serviceId: string,
+ organizationId: string,
action = "access" as "access" | "create" | "delete",
) => {
let hasPermission = false;
switch (action) {
case "create":
- hasPermission = await canPerformCreationService(authId, serviceId);
+ hasPermission = await canPerformCreationService(
+ userId,
+ serviceId,
+ organizationId,
+ );
break;
case "access":
- hasPermission = await canPerformAccessService(authId, serviceId);
+ hasPermission = await canPerformAccessService(
+ userId,
+ serviceId,
+ organizationId,
+ );
break;
case "delete":
- hasPermission = await canPeformDeleteService(authId, serviceId);
+ hasPermission = await canPeformDeleteService(
+ userId,
+ serviceId,
+ organizationId,
+ );
break;
default:
hasPermission = false;
@@ -182,6 +185,7 @@ export const checkServiceAccess = async (
export const checkProjectAccess = async (
authId: string,
action: "create" | "delete" | "access",
+ organizationId: string,
projectId?: string,
) => {
let hasPermission = false;
@@ -190,13 +194,14 @@ export const checkProjectAccess = async (
hasPermission = await canPerformAccessProject(
authId,
projectId as string,
+ organizationId,
);
break;
case "create":
- hasPermission = await canPerformCreationProject(authId);
+ hasPermission = await canPerformCreationProject(authId, organizationId);
break;
case "delete":
- hasPermission = await canPerformDeleteProject(authId);
+ hasPermission = await canPerformDeleteProject(authId, organizationId);
break;
default:
hasPermission = false;
@@ -208,3 +213,82 @@ export const checkProjectAccess = async (
});
}
};
+
+export const findMemberById = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const result = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, userId),
+ eq(member.organizationId, organizationId),
+ ),
+ with: {
+ user: true,
+ },
+ });
+
+ if (!result) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Permission denied",
+ });
+ }
+ return result;
+};
+
+export const updateUser = async (userId: string, userData: Partial) => {
+ const user = await db
+ .update(users_temp)
+ .set({
+ ...userData,
+ })
+ .where(eq(users_temp.id, userId))
+ .returning()
+ .then((res) => res[0]);
+
+ return user;
+};
+
+export const createApiKey = async (
+ userId: string,
+ input: {
+ name: string;
+ prefix?: string;
+ expiresIn?: number;
+ metadata: {
+ organizationId: string;
+ };
+ rateLimitEnabled?: boolean;
+ rateLimitTimeWindow?: number;
+ rateLimitMax?: number;
+ remaining?: number;
+ refillAmount?: number;
+ refillInterval?: number;
+ },
+) => {
+ const apiKey = await auth.createApiKey({
+ body: {
+ name: input.name,
+ expiresIn: input.expiresIn,
+ prefix: input.prefix,
+ rateLimitEnabled: input.rateLimitEnabled,
+ rateLimitTimeWindow: input.rateLimitTimeWindow,
+ rateLimitMax: input.rateLimitMax,
+ remaining: input.remaining,
+ refillAmount: input.refillAmount,
+ refillInterval: input.refillInterval,
+ userId,
+ },
+ });
+
+ if (input.metadata) {
+ await db
+ .update(apikey)
+ .set({
+ metadata: JSON.stringify(input.metadata),
+ })
+ .where(eq(apikey.id, apiKey.id));
+ }
+ return apiKey;
+};
diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts
index f72b2244..75b9a928 100644
--- a/packages/server/src/setup/monitoring-setup.ts
+++ b/packages/server/src/setup/monitoring-setup.ts
@@ -1,7 +1,7 @@
import { findServerById } from "@dokploy/server/services/server";
import type { ContainerCreateOptions } from "dockerode";
import { IS_CLOUD } from "../constants";
-import { findAdminById } from "../services/admin";
+import { findUserById } from "../services/admin";
import { getDokployImageTag } from "../services/settings";
import { pullImage, pullRemoteImage } from "../utils/docker/utils";
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
@@ -66,7 +66,7 @@ export const setupMonitoring = async (serverId: string) => {
await container.inspect();
await container.remove({ force: true });
console.log("Removed existing container");
- } catch (error) {
+ } catch (_error) {
// Container doesn't exist, continue
}
@@ -80,8 +80,8 @@ export const setupMonitoring = async (serverId: string) => {
}
};
-export const setupWebMonitoring = async (adminId: string) => {
- const admin = await findAdminById(adminId);
+export const setupWebMonitoring = async (userId: string) => {
+ const user = await findUserById(userId);
const containerName = "dokploy-monitoring";
let imageName = "dokploy/monitoring:latest";
@@ -96,7 +96,7 @@ export const setupWebMonitoring = async (adminId: string) => {
const settings: ContainerCreateOptions = {
name: containerName,
- Env: [`METRICS_CONFIG=${JSON.stringify(admin?.metricsConfig)}`],
+ Env: [`METRICS_CONFIG=${JSON.stringify(user?.metricsConfig)}`],
Image: imageName,
HostConfig: {
// Memory: 100 * 1024 * 1024, // 100MB en bytes
@@ -104,9 +104,9 @@ export const setupWebMonitoring = async (adminId: string) => {
// CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
// Privileged: true,
PortBindings: {
- [`${admin.metricsConfig.server.port}/tcp`]: [
+ [`${user?.metricsConfig?.server?.port}/tcp`]: [
{
- HostPort: admin.metricsConfig.server.port.toString(),
+ HostPort: user?.metricsConfig?.server?.port.toString(),
},
],
},
@@ -120,7 +120,7 @@ export const setupWebMonitoring = async (adminId: string) => {
// NetworkMode: "host",
},
ExposedPorts: {
- [`${admin.metricsConfig.server.port}/tcp`]: {},
+ [`${user?.metricsConfig?.server?.port}/tcp`]: {},
},
};
const docker = await getRemoteDocker();
@@ -135,7 +135,7 @@ export const setupWebMonitoring = async (adminId: string) => {
await container.inspect();
await container.remove({ force: true });
console.log("Removed existing container");
- } catch (error) {}
+ } catch (_error) {}
await docker.createContainer(settings);
const newContainer = docker.getContainer(containerName);
diff --git a/packages/server/src/setup/postgres-setup.ts b/packages/server/src/setup/postgres-setup.ts
index b5794c2b..cf162f1e 100644
--- a/packages/server/src/setup/postgres-setup.ts
+++ b/packages/server/src/setup/postgres-setup.ts
@@ -54,10 +54,16 @@ export const initializePostgres = async () => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
-
console.log("Postgres Started ✅");
- } catch (error) {
- await docker.createService(settings);
+ } catch (_) {
+ try {
+ await docker.createService(settings);
+ } catch (error: any) {
+ if (error?.statusCode !== 409) {
+ throw error;
+ }
+ console.log("Postgres service already exists, continuing...");
+ }
console.log("Postgres Not Found: Starting ✅");
}
};
diff --git a/packages/server/src/setup/redis-setup.ts b/packages/server/src/setup/redis-setup.ts
index 1c3b545a..7366546d 100644
--- a/packages/server/src/setup/redis-setup.ts
+++ b/packages/server/src/setup/redis-setup.ts
@@ -52,8 +52,15 @@ export const initializeRedis = async () => {
...settings,
});
console.log("Redis Started ✅");
- } catch (error) {
- await docker.createService(settings);
+ } catch (_) {
+ try {
+ await docker.createService(settings);
+ } catch (error: any) {
+ if (error?.statusCode !== 409) {
+ throw error;
+ }
+ console.log("Redis service already exists, continuing...");
+ }
console.log("Redis Not Found: Starting ✅");
}
};
diff --git a/packages/server/src/setup/server-audit.ts b/packages/server/src/setup/server-audit.ts
index df00e9a7..b9283c31 100644
--- a/packages/server/src/setup/server-audit.ts
+++ b/packages/server/src/setup/server-audit.ts
@@ -89,7 +89,7 @@ export const serverAudit = async (serverId: string) => {
.on("data", (data: string) => {
output += data;
})
- .stderr.on("data", (data) => {});
+ .stderr.on("data", (_data) => {});
});
})
.on("error", (err) => {
diff --git a/packages/server/src/setup/server-validate.ts b/packages/server/src/setup/server-validate.ts
index 4ca21df8..c86206b6 100644
--- a/packages/server/src/setup/server-validate.ts
+++ b/packages/server/src/setup/server-validate.ts
@@ -128,7 +128,7 @@ export const serverValidate = async (serverId: string) => {
.on("data", (data: string) => {
output += data;
})
- .stderr.on("data", (data) => {});
+ .stderr.on("data", (_data) => {});
});
})
.on("error", (err) => {
diff --git a/packages/server/src/setup/setup.ts b/packages/server/src/setup/setup.ts
index c5987702..eeef32dd 100644
--- a/packages/server/src/setup/setup.ts
+++ b/packages/server/src/setup/setup.ts
@@ -18,7 +18,7 @@ export const dockerSwarmInitialized = async () => {
await docker.swarmInspect();
return true;
- } catch (e) {
+ } catch (_e) {
return false;
}
};
@@ -41,7 +41,7 @@ export const dockerNetworkInitialized = async () => {
try {
await docker.getNetwork("dokploy-network").inspect();
return true;
- } catch (e) {
+ } catch (_e) {
return false;
}
};
diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts
index 1d60e577..e8d01942 100644
--- a/packages/server/src/setup/traefik-setup.ts
+++ b/packages/server/src/setup/traefik-setup.ts
@@ -127,8 +127,15 @@ export const initializeTraefik = async ({
});
console.log("Traefik Started ✅");
- } catch (error) {
- await docker.createService(settings);
+ } catch (_) {
+ try {
+ await docker.createService(settings);
+ } catch (error: any) {
+ if (error?.statusCode !== 409) {
+ throw error;
+ }
+ console.log("Traefik service already exists, continuing...");
+ }
console.log("Traefik Not Found: Starting ✅");
}
};
diff --git a/packages/server/src/types/with.ts b/packages/server/src/types/with.ts
index c4826f73..467020a2 100644
--- a/packages/server/src/types/with.ts
+++ b/packages/server/src/types/with.ts
@@ -36,7 +36,7 @@ type AnyObj = Record;
type ZodObj = {
[key in keyof T]: z.ZodType;
};
-const zObject = (arg: ZodObj) => z.object(arg);
+const _zObject = (arg: ZodObj) => z.object(arg);
// const goodDogScheme = zObject({
// // prueba: schema.selectDatabaseSchema,
diff --git a/packages/server/src/utils/access-log/handler.ts b/packages/server/src/utils/access-log/handler.ts
index b1fd925c..69d0cc68 100644
--- a/packages/server/src/utils/access-log/handler.ts
+++ b/packages/server/src/utils/access-log/handler.ts
@@ -1,8 +1,8 @@
import { IS_CLOUD, paths } from "@dokploy/server/constants";
-import { updateAdmin } from "@dokploy/server/services/admin";
import { type RotatingFileStream, createStream } from "rotating-file-stream";
-import { db } from "../../db";
import { execAsync } from "../process/execAsync";
+import { findAdmin } from "@dokploy/server/services/admin";
+import { updateUser } from "@dokploy/server/services/user";
class LogRotationManager {
private static instance: LogRotationManager;
@@ -30,17 +30,16 @@ class LogRotationManager {
}
private async getStateFromDB(): Promise {
- const setting = await db.query.admins.findFirst({});
- return setting?.enableLogRotation ?? false;
+ const admin = await findAdmin();
+ return admin?.user.enableLogRotation ?? false;
}
private async setStateInDB(active: boolean): Promise {
- const admin = await db.query.admins.findFirst({});
-
+ const admin = await findAdmin();
if (!admin) {
return;
}
- await updateAdmin(admin?.authId, {
+ await updateUser(admin.user.id, {
enableLogRotation: active,
});
}
diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts
index 922232a0..7699a42e 100644
--- a/packages/server/src/utils/backups/index.ts
+++ b/packages/server/src/utils/backups/index.ts
@@ -1,4 +1,3 @@
-import { findAdmin } from "@dokploy/server/services/admin";
import { getAllServers } from "@dokploy/server/services/server";
import { scheduleJob } from "node-schedule";
import { db } from "../../db/index";
@@ -12,13 +11,14 @@ import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres";
+import { findAdmin } from "../../services/admin";
export const initCronJobs = async () => {
console.log("Setting up cron jobs....");
const admin = await findAdmin();
- if (admin?.enableDockerCleanup) {
+ if (admin?.user.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
@@ -26,7 +26,7 @@ export const initCronJobs = async () => {
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
- await sendDockerCleanupNotifications(admin.adminId);
+ await sendDockerCleanupNotifications(admin.user.id);
});
}
@@ -43,7 +43,7 @@ export const initCronJobs = async () => {
await cleanUpDockerBuilder(serverId);
await cleanUpSystemPrune(serverId);
await sendDockerCleanupNotifications(
- admin.adminId,
+ admin.user.id,
`Docker cleanup for Server ${name} (${serverId})`,
);
});
diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts
index 79cba9c5..56c2919c 100644
--- a/packages/server/src/utils/backups/mariadb.ts
+++ b/packages/server/src/utils/backups/mariadb.ts
@@ -49,7 +49,7 @@ export const runMariadbBackup = async (
projectName: project.name,
databaseType: "mariadb",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
console.log(error);
@@ -60,7 +60,7 @@ export const runMariadbBackup = async (
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts
index ddd1b889..a40ec4f4 100644
--- a/packages/server/src/utils/backups/mongo.ts
+++ b/packages/server/src/utils/backups/mongo.ts
@@ -46,7 +46,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
projectName: project.name,
databaseType: "mongodb",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
console.log(error);
@@ -57,7 +57,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts
index b505204c..1272fc3e 100644
--- a/packages/server/src/utils/backups/mysql.ts
+++ b/packages/server/src/utils/backups/mysql.ts
@@ -1,4 +1,3 @@
-import { unlink } from "node:fs/promises";
import path from "node:path";
import type { BackupSchedule } from "@dokploy/server/services/backup";
import type { MySql } from "@dokploy/server/services/mysql";
@@ -46,7 +45,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
projectName: project.name,
databaseType: "mysql",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
console.log(error);
@@ -57,7 +56,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts
index e9609fc8..5ada2aa9 100644
--- a/packages/server/src/utils/backups/postgres.ts
+++ b/packages/server/src/utils/backups/postgres.ts
@@ -49,7 +49,7 @@ export const runPostgresBackup = async (
projectName: project.name,
databaseType: "postgres",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
await sendDatabaseBackupNotifications({
@@ -59,7 +59,7 @@ export const runPostgresBackup = async (
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts
index 0d78ff96..c76f7962 100644
--- a/packages/server/src/utils/backups/utils.ts
+++ b/packages/server/src/utils/backups/utils.ts
@@ -28,7 +28,7 @@ export const removeScheduleBackup = (backupId: string) => {
};
export const getS3Credentials = (destination: Destination) => {
- const { accessKey, secretAccessKey, bucket, region, endpoint, provider } =
+ const { accessKey, secretAccessKey, region, endpoint, provider } =
destination;
const rcloneFlags = [
`--s3-access-key-id=${accessKey}`,
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index 838cf74e..19e7d152 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -2,7 +2,6 @@ import {
createWriteStream,
existsSync,
mkdirSync,
- readFileSync,
writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
@@ -99,8 +98,7 @@ export const getBuildComposeCommand = async (
logPath: string,
) => {
const { COMPOSE_PATH } = paths(true);
- const { sourceType, appName, mounts, composeType, domains, composePath } =
- compose;
+ const { sourceType, appName, mounts, composeType, domains } = compose;
const command = createCommand(compose);
const envCommand = getCreateEnvFileCommand(compose);
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts
index d6748275..d777b1a3 100644
--- a/packages/server/src/utils/builders/index.ts
+++ b/packages/server/src/utils/builders/index.ts
@@ -197,7 +197,7 @@ export const mechanizeDockerContainer = async (
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts
index 56560e4e..c13f82a5 100644
--- a/packages/server/src/utils/builders/nixpacks.ts
+++ b/packages/server/src/utils/builders/nixpacks.ts
@@ -91,7 +91,7 @@ export const getNixpacksCommand = (
application: ApplicationNested,
logPath: string,
) => {
- const { env, appName, publishDirectory, serverId } = application;
+ const { env, appName, publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;
diff --git a/packages/server/src/utils/databases/mariadb.ts b/packages/server/src/utils/databases/mariadb.ts
index d1b41fc3..ead5a618 100644
--- a/packages/server/src/utils/databases/mariadb.ts
+++ b/packages/server/src/utils/databases/mariadb.ts
@@ -98,7 +98,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts
index 5af58eef..ace9c972 100644
--- a/packages/server/src/utils/databases/mongo.ts
+++ b/packages/server/src/utils/databases/mongo.ts
@@ -152,7 +152,7 @@ ${command ?? "wait $MONGOD_PID"}`;
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/databases/mysql.ts b/packages/server/src/utils/databases/mysql.ts
index 5a691177..de28cfe6 100644
--- a/packages/server/src/utils/databases/mysql.ts
+++ b/packages/server/src/utils/databases/mysql.ts
@@ -104,7 +104,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/databases/redis.ts b/packages/server/src/utils/databases/redis.ts
index 724069a1..aef86280 100644
--- a/packages/server/src/utils/databases/redis.ts
+++ b/packages/server/src/utils/databases/redis.ts
@@ -95,7 +95,7 @@ export const buildRedis = async (redis: RedisNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts
index 8a1b0608..c4ced3f4 100644
--- a/packages/server/src/utils/docker/domain.ts
+++ b/packages/server/src/utils/docker/domain.ts
@@ -109,7 +109,7 @@ export const loadDockerComposeRemote = async (
if (!stdout) return null;
const parsedConfig = load(stdout) as ComposeSpecification;
return parsedConfig;
- } catch (err) {
+ } catch (_err) {
return null;
}
};
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index 062e0722..71b7e4aa 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -100,7 +100,7 @@ export const containerExists = async (containerName: string) => {
try {
await container.inspect();
return true;
- } catch (error) {
+ } catch (_error) {
return false;
}
};
@@ -240,7 +240,7 @@ export const startServiceRemote = async (serverId: string, appName: string) => {
export const removeService = async (
appName: string,
serverId?: string | null,
- deleteVolumes = false,
+ _deleteVolumes = false,
) => {
try {
const command = `docker service rm ${appName}`;
diff --git a/packages/server/src/utils/gpu-setup.ts b/packages/server/src/utils/gpu-setup.ts
index 6a6611b4..a815a00c 100644
--- a/packages/server/src/utils/gpu-setup.ts
+++ b/packages/server/src/utils/gpu-setup.ts
@@ -34,7 +34,7 @@ export async function checkGPUStatus(serverId?: string): Promise {
...gpuInfo,
...cudaInfo,
};
- } catch (error) {
+ } catch (_error) {
return {
driverInstalled: false,
driverVersion: undefined,
@@ -315,7 +315,7 @@ const setupLocalServer = async (daemonConfig: any) => {
try {
await execAsync(setupCommands);
- } catch (error) {
+ } catch (_error) {
throw new Error(
"Failed to configure GPU support. Please ensure you have sudo privileges and try again.",
);
diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts
index 95936652..c873c9ab 100644
--- a/packages/server/src/utils/notifications/build-error.ts
+++ b/packages/server/src/utils/notifications/build-error.ts
@@ -18,7 +18,7 @@ interface Props {
applicationType: string;
errorMessage: string;
buildLink: string;
- adminId: string;
+ organizationId: string;
}
export const sendBuildErrorNotifications = async ({
@@ -27,14 +27,14 @@ export const sendBuildErrorNotifications = async ({
applicationType,
errorMessage,
buildLink,
- adminId,
+ organizationId,
}: Props) => {
const date = new Date();
const unixDate = ~~(Number(date) / 1000);
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appBuildError, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts
index 960f7a6a..ac470c49 100644
--- a/packages/server/src/utils/notifications/build-success.ts
+++ b/packages/server/src/utils/notifications/build-success.ts
@@ -18,7 +18,7 @@ interface Props {
applicationName: string;
applicationType: string;
buildLink: string;
- adminId: string;
+ organizationId: string;
domains: Domain[];
}
@@ -27,7 +27,7 @@ export const sendBuildSuccessNotifications = async ({
applicationName,
applicationType,
buildLink,
- adminId,
+ organizationId,
domains,
}: Props) => {
const date = new Date();
@@ -35,7 +35,7 @@ export const sendBuildSuccessNotifications = async ({
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appDeploy, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts
index 0b1d61f7..37a4a1ff 100644
--- a/packages/server/src/utils/notifications/database-backup.ts
+++ b/packages/server/src/utils/notifications/database-backup.ts
@@ -1,4 +1,3 @@
-import { error } from "node:console";
import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
@@ -19,13 +18,13 @@ export const sendDatabaseBackupNotifications = async ({
databaseType,
type,
errorMessage,
- adminId,
+ organizationId,
}: {
projectName: string;
applicationName: string;
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
type: "error" | "success";
- adminId: string;
+ organizationId: string;
errorMessage?: string;
}) => {
const date = new Date();
@@ -33,7 +32,7 @@ export const sendDatabaseBackupNotifications = async ({
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.databaseBackup, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts
index b60e3b0a..b3959ccc 100644
--- a/packages/server/src/utils/notifications/docker-cleanup.ts
+++ b/packages/server/src/utils/notifications/docker-cleanup.ts
@@ -13,7 +13,7 @@ import {
} from "./utils";
export const sendDockerCleanupNotifications = async (
- adminId: string,
+ organizationId: string,
message = "Docker cleanup for dokploy",
) => {
const date = new Date();
@@ -21,7 +21,7 @@ export const sendDockerCleanupNotifications = async (
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.dockerCleanup, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/server-threshold.ts b/packages/server/src/utils/notifications/server-threshold.ts
index 32ff9b55..2e63ba25 100644
--- a/packages/server/src/utils/notifications/server-threshold.ts
+++ b/packages/server/src/utils/notifications/server-threshold.ts
@@ -18,7 +18,7 @@ interface ServerThresholdPayload {
}
export const sendServerThresholdNotifications = async (
- adminId: string,
+ organizationId: string,
payload: ServerThresholdPayload,
) => {
const date = new Date(payload.Timestamp);
@@ -27,7 +27,7 @@ export const sendServerThresholdNotifications = async (
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.serverThreshold, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/process/execAsync.ts b/packages/server/src/utils/process/execAsync.ts
index 19a16ac1..aee1e821 100644
--- a/packages/server/src/utils/process/execAsync.ts
+++ b/packages/server/src/utils/process/execAsync.ts
@@ -27,7 +27,7 @@ export const execAsyncRemote = async (
throw err;
}
stream
- .on("close", (code: number, signal: string) => {
+ .on("close", (code: number, _signal: string) => {
conn.end();
if (code === 0) {
resolve({ stdout, stderr });
diff --git a/packages/server/src/utils/providers/bitbucket.ts b/packages/server/src/utils/providers/bitbucket.ts
index 7059e65f..dd98a93b 100644
--- a/packages/server/src/utils/providers/bitbucket.ts
+++ b/packages/server/src/utils/providers/bitbucket.ts
@@ -176,7 +176,6 @@ export const getBitbucketCloneCommand = async (
bitbucketBranch,
bitbucketId,
serverId,
- bitbucket,
} = entity;
if (!serverId) {
diff --git a/packages/server/src/utils/providers/git.ts b/packages/server/src/utils/providers/git.ts
index 8f8a3830..c26af3af 100644
--- a/packages/server/src/utils/providers/git.ts
+++ b/packages/server/src/utils/providers/git.ts
@@ -320,7 +320,7 @@ export const cloneGitRawRepository = async (entity: {
outputPath,
"--progress",
],
- (data) => {},
+ (_data) => {},
{
env: {
...process.env,
diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts
index 096f9e28..c380a920 100644
--- a/packages/server/src/utils/providers/gitlab.ts
+++ b/packages/server/src/utils/providers/gitlab.ts
@@ -162,8 +162,6 @@ export const getGitlabCloneCommand = async (
) => {
const {
appName,
- gitlabRepository,
- gitlabOwner,
gitlabPathNamespace,
gitlabBranch,
gitlabId,
@@ -268,7 +266,7 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
- return kind === "user";
+ return kind === "member";
});
const mappedRepositories = filteredRepos.map((repo: any) => {
return {
@@ -328,14 +326,7 @@ export const getGitlabBranches = async (input: {
};
export const cloneRawGitlabRepository = async (entity: Compose) => {
- const {
- appName,
- gitlabRepository,
- gitlabOwner,
- gitlabBranch,
- gitlabId,
- gitlabPathNamespace,
- } = entity;
+ const { appName, gitlabBranch, gitlabId, gitlabPathNamespace } = entity;
if (!gitlabId) {
throw new TRPCError({
@@ -442,7 +433,7 @@ export const testGitlabConnection = async (
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
- return kind === "user";
+ return kind === "member";
});
return filteredRepos.length;
diff --git a/packages/server/src/utils/traefik/application.ts b/packages/server/src/utils/traefik/application.ts
index 4434d858..61150abf 100644
--- a/packages/server/src/utils/traefik/application.ts
+++ b/packages/server/src/utils/traefik/application.ts
@@ -67,7 +67,7 @@ export const removeTraefikConfig = async (
if (fs.existsSync(configPath)) {
await fs.promises.unlink(configPath);
}
- } catch (error) {}
+ } catch (_error) {}
};
export const removeTraefikConfigRemote = async (
@@ -78,7 +78,7 @@ export const removeTraefikConfigRemote = async (
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
await execAsyncRemote(serverId, `rm ${configPath}`);
- } catch (error) {}
+ } catch (_error) {}
};
export const loadOrCreateConfig = (appName: string): FileConfig => {
@@ -110,7 +110,7 @@ export const loadOrCreateConfigRemote = async (
http: { routers: {}, services: {} },
};
return parsedConfig;
- } catch (err) {
+ } catch (_err) {
return fileConfig;
}
};
@@ -132,7 +132,7 @@ export const readRemoteConfig = async (serverId: string, appName: string) => {
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
if (!stdout) return null;
return stdout;
- } catch (err) {
+ } catch (_err) {
return null;
}
};
diff --git a/packages/server/src/utils/traefik/domain.ts b/packages/server/src/utils/traefik/domain.ts
index a6c878e7..1ae3c05a 100644
--- a/packages/server/src/utils/traefik/domain.ts
+++ b/packages/server/src/utils/traefik/domain.ts
@@ -122,13 +122,25 @@ export const createRouterConfig = async (
if ((entryPoint === "websecure" && https) || !https) {
// redirects
for (const redirect of redirects) {
- const middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
+ let middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
+ if (domain.domainType === "preview") {
+ middlewareName = `redirect-${appName.replace(
+ /^preview-(.+)-[^-]+$/,
+ "$1",
+ )}-${redirect.uniqueConfigKey}`;
+ }
routerConfig.middlewares?.push(middlewareName);
}
// security
if (security.length > 0) {
- const middlewareName = `auth-${appName}`;
+ let middlewareName = `auth-${appName}`;
+ if (domain.domainType === "preview") {
+ middlewareName = `auth-${appName.replace(
+ /^preview-(.+)-[^-]+$/,
+ "$1",
+ )}`;
+ }
routerConfig.middlewares?.push(middlewareName);
}
}
diff --git a/packages/server/src/utils/traefik/middleware.ts b/packages/server/src/utils/traefik/middleware.ts
index 60345f66..934d637e 100644
--- a/packages/server/src/utils/traefik/middleware.ts
+++ b/packages/server/src/utils/traefik/middleware.ts
@@ -95,7 +95,7 @@ export const loadRemoteMiddlewares = async (serverId: string) => {
}
const config = load(stdout) as FileConfig;
return config;
- } catch (error) {
+ } catch (_) {
throw new Error(`File not found: ${configPath}`);
}
};
diff --git a/packages/server/src/utils/traefik/web-server.ts b/packages/server/src/utils/traefik/web-server.ts
index 0aa4d35d..78046c67 100644
--- a/packages/server/src/utils/traefik/web-server.ts
+++ b/packages/server/src/utils/traefik/web-server.ts
@@ -1,14 +1,14 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
-import type { Admin } from "@dokploy/server/services/admin";
+import type { User } from "@dokploy/server/services/user";
import { dump, load } from "js-yaml";
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
import type { FileConfig } from "./file-types";
import type { MainTraefikConfig } from "./types";
export const updateServerTraefik = (
- admin: Admin | null,
+ user: User | null,
newHost: string | null,
) => {
const appName = "dokploy";
@@ -22,7 +22,7 @@ export const updateServerTraefik = (
if (currentRouterConfig && newHost) {
currentRouterConfig.rule = `Host(\`${newHost}\`)`;
- if (admin?.certificateType === "letsencrypt") {
+ if (user?.certificateType === "letsencrypt") {
config.http.routers[`${appName}-router-app-secure`] = {
...currentRouterConfig,
entryPoints: ["websecure"],
diff --git a/packages/server/src/verification/send-verification-email.tsx b/packages/server/src/verification/send-verification-email.tsx
new file mode 100644
index 00000000..c673c0f7
--- /dev/null
+++ b/packages/server/src/verification/send-verification-email.tsx
@@ -0,0 +1,51 @@
+import {
+ sendDiscordNotification,
+ sendEmailNotification,
+} from "../utils/notifications/utils";
+export const sendEmail = async ({
+ email,
+ subject,
+ text,
+}: {
+ email: string;
+ subject: string;
+ text: string;
+}) => {
+ await sendEmailNotification(
+ {
+ fromAddress: process.env.SMTP_FROM_ADDRESS || "",
+ toAddresses: [email],
+ smtpServer: process.env.SMTP_SERVER || "",
+ smtpPort: Number(process.env.SMTP_PORT),
+ username: process.env.SMTP_USERNAME || "",
+ password: process.env.SMTP_PASSWORD || "",
+ },
+ subject,
+ text,
+ );
+
+ return true;
+};
+
+export const sendDiscordNotificationWelcome = async (email: string) => {
+ await sendDiscordNotification(
+ {
+ webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
+ },
+ {
+ title: "New User Registered",
+ color: 0x00ff00,
+ fields: [
+ {
+ name: "Email",
+ value: email,
+ inline: true,
+ },
+ ],
+ timestamp: new Date(),
+ footer: {
+ text: "Dokploy User Registration Notification",
+ },
+ },
+ );
+};
diff --git a/packages/server/tsconfig.server.json b/packages/server/tsconfig.server.json
index 7f349eb8..33777c02 100644
--- a/packages/server/tsconfig.server.json
+++ b/packages/server/tsconfig.server.json
@@ -1,6 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
+ "disableSizeLimit": true,
"module": "ESNext",
"outDir": "dist/",
"target": "ESNext",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c651464..03fb11f7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -238,6 +238,9 @@ importers:
bcrypt:
specifier: 5.1.1
version: 5.1.1(encoding@0.1.13)
+ better-auth:
+ specifier: 1.2.0
+ version: 1.2.0(typescript@5.5.3)
bl:
specifier: 6.0.11
version: 6.0.11
@@ -273,10 +276,10 @@ importers:
version: 16.4.5
drizzle-orm:
specifier: ^0.39.1
- version: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
+ version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
drizzle-zod:
specifier: 0.5.1
- version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
+ version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
fancy-ansi:
specifier: ^0.1.3
version: 0.1.3
@@ -505,7 +508,7 @@ importers:
version: 16.4.5
drizzle-orm:
specifier: ^0.39.1
- version: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
+ version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
hono:
specifier: ^4.5.8
version: 4.5.8
@@ -546,6 +549,9 @@ importers:
packages/server:
dependencies:
+ '@better-auth/utils':
+ specifier: 0.2.3
+ version: 0.2.3
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
@@ -555,6 +561,12 @@ importers:
'@octokit/auth-app':
specifier: ^6.0.4
version: 6.1.1
+ '@oslojs/crypto':
+ specifier: 1.0.1
+ version: 1.0.1
+ '@oslojs/encoding':
+ specifier: 1.1.0
+ version: 1.1.0
'@react-email/components':
specifier: ^0.0.21
version: 0.0.21(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -567,6 +579,9 @@ importers:
bcrypt:
specifier: 5.1.1
version: 5.1.1(encoding@0.1.13)
+ better-auth:
+ specifier: 1.2.0
+ version: 1.2.0(typescript@5.5.3)
bl:
specifier: 6.0.11
version: 6.0.11
@@ -582,12 +597,15 @@ importers:
dotenv:
specifier: 16.4.5
version: 16.4.5
+ drizzle-dbml-generator:
+ specifier: 0.10.0
+ version: 0.10.0(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))
drizzle-orm:
specifier: ^0.39.1
- version: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
+ version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
drizzle-zod:
specifier: 0.5.1
- version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
+ version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
hi-base32:
specifier: ^0.5.1
version: 0.5.1
@@ -748,6 +766,12 @@ packages:
'@balena/dockerignore@1.0.2':
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
+ '@better-auth/utils@0.2.3':
+ resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==}
+
+ '@better-fetch/fetch@1.1.15':
+ resolution: {integrity: sha512-0Bl8YYj1f8qCTNHeSn5+1DWv2hy7rLBrQ8rS8Y9XYloiwZEfc3k4yspIG0llRxafxqhGCwlGRg+F8q1HZRCMXA==}
+
'@biomejs/biome@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
engines: {node: '>=14.21.3'}
@@ -1518,6 +1542,9 @@ packages:
'@hapi/bourne@3.0.0':
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
+ '@hexagon/base64@1.1.28':
+ resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
+
'@hono/node-server@1.12.1':
resolution: {integrity: sha512-C9l+08O8xtXB7Ppmy8DjBFH1hYji7JKzsU32Yt1poIIbdPp6S7aOI8IldDHD9YFJ55lv2c21ovNrmxatlHfhAg==}
engines: {node: '>=18.14.1'}
@@ -1697,6 +1724,9 @@ packages:
'@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
+ '@levischuck/tiny-cbor@0.2.11':
+ resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
+
'@lezer/common@1.2.1':
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
@@ -1807,10 +1837,17 @@ packages:
cpu: [x64]
os: [win32]
+ '@noble/ciphers@0.6.0':
+ resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
+
'@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
engines: {node: ^14.21.3 || >=16}
+ '@noble/hashes@1.7.1':
+ resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
+ engines: {node: ^14.21.3 || >=16}
+
'@node-rs/argon2-android-arm-eabi@1.7.0':
resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==}
engines: {node: '>= 10'}
@@ -2136,6 +2173,33 @@ packages:
'@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
+ '@oslojs/asn1@1.0.0':
+ resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
+
+ '@oslojs/binary@1.0.0':
+ resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
+
+ '@oslojs/crypto@1.0.1':
+ resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
+
+ '@oslojs/encoding@1.1.0':
+ resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
+
+ '@peculiar/asn1-android@2.3.15':
+ resolution: {integrity: sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==}
+
+ '@peculiar/asn1-ecc@2.3.15':
+ resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
+
+ '@peculiar/asn1-rsa@2.3.15':
+ resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
+
+ '@peculiar/asn1-schema@2.3.15':
+ resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
+
+ '@peculiar/asn1-x509@2.3.15':
+ resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -3147,6 +3211,13 @@ packages:
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
+ '@simplewebauthn/browser@13.1.0':
+ resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
+
+ '@simplewebauthn/server@13.1.1':
+ resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
+ engines: {node: '>=20.0.0'}
+
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -3762,6 +3833,10 @@ packages:
asn1@0.2.6:
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
+ asn1js@3.0.5:
+ resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==}
+ engines: {node: '>=12.0.0'}
+
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -3804,6 +3879,12 @@ packages:
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
+ better-auth@1.2.0:
+ resolution: {integrity: sha512-eIRGOXfix25bh4fgs8jslZAZssufpIkxfEeEokQu5G4wICoDee1wPctkFb8v80PvhtI4dPm28SuAoZaAdRc6Wg==}
+
+ better-call@1.0.3:
+ resolution: {integrity: sha512-DUKImKoDIy5UtCvQbHTg0wuBRse6gu1Yvznn7+1B3I5TeY8sclRPFce0HI+4WF2bcb+9PqmkET8nXZubrHQh9A==}
+
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -4389,6 +4470,11 @@ packages:
resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==}
engines: {node: '>=4'}
+ drizzle-dbml-generator@0.10.0:
+ resolution: {integrity: sha512-cMZq9E3U3RlmE0uBeXyc6oWJ0royOkC6HiTlc9LDeMe+W87poZTzKoNYUyAxZrs4Q1RQtob+cGKiefV4ZoI8HA==}
+ peerDependencies:
+ drizzle-orm: '>=0.36.0'
+
drizzle-kit@0.30.4:
resolution: {integrity: sha512-B2oJN5UkvwwNHscPWXDG5KqAixu7AUzZ3qbe++KU9SsQ+cZWR4DXEPYcvWplyFAno0dhRJECNEhNxiDmFaPGyQ==}
hasBin: true
@@ -5120,6 +5206,9 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
+ jose@5.9.6:
+ resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
+
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
@@ -5223,6 +5312,10 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ kysely@0.27.5:
+ resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==}
+ engines: {node: '>=14.0.0'}
+
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
@@ -5608,6 +5701,10 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ nanostores@0.11.3:
+ resolution: {integrity: sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
@@ -6058,6 +6155,13 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ pvtsutils@1.3.6:
+ resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
+
+ pvutils@1.1.3:
+ resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
+ engines: {node: '>=6.0.0'}
+
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
@@ -6403,6 +6507,9 @@ packages:
resolution: {integrity: sha512-cfmm3tqdnbuYw2FBmRTPBDaohYEbMJ3211T35o6eZdr4d7v69+ZeK1Av84Br7FLj2dlzyeZSbN6qTuXXE6dawQ==}
engines: {node: '>=14.0'}
+ rou3@0.5.1:
+ resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6460,6 +6567,9 @@ packages:
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+ set-cookie-parser@2.7.1:
+ resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -6872,6 +6982,9 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
tsx@4.16.2:
resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==}
engines: {node: '>=18.0.0'}
@@ -7004,6 +7117,14 @@ packages:
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+ valibot@1.0.0-beta.15:
+ resolution: {integrity: sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw==}
+ peerDependencies:
+ typescript: '>=5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
@@ -7237,6 +7358,9 @@ packages:
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
+ zod@3.24.1:
+ resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
+
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -7266,6 +7390,12 @@ snapshots:
'@balena/dockerignore@1.0.2': {}
+ '@better-auth/utils@0.2.3':
+ dependencies:
+ uncrypto: 0.1.3
+
+ '@better-fetch/fetch@1.1.15': {}
+
'@biomejs/biome@1.9.4':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.4
@@ -7832,6 +7962,8 @@ snapshots:
'@hapi/bourne@3.0.0': {}
+ '@hexagon/base64@1.1.28': {}
+
'@hono/node-server@1.12.1': {}
'@hono/zod-validator@0.3.0(hono@4.5.8)(zod@3.23.8)':
@@ -7981,6 +8113,8 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {}
+ '@levischuck/tiny-cbor@0.2.11': {}
+
'@lezer/common@1.2.1': {}
'@lezer/highlight@1.2.0':
@@ -8071,8 +8205,12 @@ snapshots:
'@next/swc-win32-x64-msvc@15.0.1':
optional: true
+ '@noble/ciphers@0.6.0': {}
+
'@noble/hashes@1.5.0': {}
+ '@noble/hashes@1.7.1': {}
+
'@node-rs/argon2-android-arm-eabi@1.7.0':
optional: true
@@ -8401,6 +8539,52 @@ snapshots:
'@one-ini/wasm@0.1.1': {}
+ '@oslojs/asn1@1.0.0':
+ dependencies:
+ '@oslojs/binary': 1.0.0
+
+ '@oslojs/binary@1.0.0': {}
+
+ '@oslojs/crypto@1.0.1':
+ dependencies:
+ '@oslojs/asn1': 1.0.0
+ '@oslojs/binary': 1.0.0
+
+ '@oslojs/encoding@1.1.0': {}
+
+ '@peculiar/asn1-android@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ asn1js: 3.0.5
+ tslib: 2.8.1
+
+ '@peculiar/asn1-ecc@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ '@peculiar/asn1-x509': 2.3.15
+ asn1js: 3.0.5
+ tslib: 2.8.1
+
+ '@peculiar/asn1-rsa@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ '@peculiar/asn1-x509': 2.3.15
+ asn1js: 3.0.5
+ tslib: 2.8.1
+
+ '@peculiar/asn1-schema@2.3.15':
+ dependencies:
+ asn1js: 3.0.5
+ pvtsutils: 1.3.6
+ tslib: 2.8.1
+
+ '@peculiar/asn1-x509@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ asn1js: 3.0.5
+ pvtsutils: 1.3.6
+ tslib: 2.8.1
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -9394,6 +9578,18 @@ snapshots:
domhandler: 5.0.3
selderee: 0.11.0
+ '@simplewebauthn/browser@13.1.0': {}
+
+ '@simplewebauthn/server@13.1.1':
+ dependencies:
+ '@hexagon/base64': 1.1.28
+ '@levischuck/tiny-cbor': 0.2.11
+ '@peculiar/asn1-android': 2.3.15
+ '@peculiar/asn1-ecc': 2.3.15
+ '@peculiar/asn1-rsa': 2.3.15
+ '@peculiar/asn1-schema': 2.3.15
+ '@peculiar/asn1-x509': 2.3.15
+
'@sinclair/typebox@0.27.8': {}
'@sindresorhus/is@5.6.0': {}
@@ -10304,6 +10500,12 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
+ asn1js@3.0.5:
+ dependencies:
+ pvtsutils: 1.3.6
+ pvutils: 1.1.3
+ tslib: 2.8.1
+
assertion-error@1.1.0: {}
async-await-queue@2.1.4: {}
@@ -10352,6 +10554,31 @@ snapshots:
before-after-hook@2.2.3: {}
+ better-auth@1.2.0(typescript@5.5.3):
+ dependencies:
+ '@better-auth/utils': 0.2.3
+ '@better-fetch/fetch': 1.1.15
+ '@noble/ciphers': 0.6.0
+ '@noble/hashes': 1.7.1
+ '@simplewebauthn/browser': 13.1.0
+ '@simplewebauthn/server': 13.1.1
+ better-call: 1.0.3
+ defu: 6.1.4
+ jose: 5.9.6
+ kysely: 0.27.5
+ nanostores: 0.11.3
+ valibot: 1.0.0-beta.15(typescript@5.5.3)
+ zod: 3.24.1
+ transitivePeerDependencies:
+ - typescript
+
+ better-call@1.0.3:
+ dependencies:
+ '@better-fetch/fetch': 1.1.15
+ rou3: 0.5.1
+ set-cookie-parser: 2.7.1
+ uncrypto: 0.1.3
+
binary-extensions@2.3.0: {}
bindings@1.5.0:
@@ -10948,6 +11175,10 @@ snapshots:
drange@1.1.1: {}
+ drizzle-dbml-generator@0.10.0(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)):
+ dependencies:
+ drizzle-orm: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
+
drizzle-kit@0.30.4:
dependencies:
'@drizzle-team/brocli': 0.10.2
@@ -10957,16 +11188,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
- drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7):
+ drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7):
optionalDependencies:
'@types/react': 18.3.5
+ kysely: 0.27.5
postgres: 3.4.4
react: 18.2.0
sqlite3: 5.1.7
- drizzle-zod@0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8):
+ drizzle-zod@0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8):
dependencies:
- drizzle-orm: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
+ drizzle-orm: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
zod: 3.23.8
eastasianwidth@0.2.0: {}
@@ -11686,6 +11918,8 @@ snapshots:
jiti@1.21.6: {}
+ jose@5.9.6: {}
+
joycon@3.1.1: {}
js-base64@3.7.7: {}
@@ -11843,6 +12077,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ kysely@0.27.5: {}
+
leac@0.6.0: {}
lefthook-darwin-arm64@1.8.4:
@@ -12215,6 +12451,8 @@ snapshots:
nanoid@3.3.7: {}
+ nanostores@0.11.3: {}
+
napi-build-utils@1.0.2:
optional: true
@@ -12692,6 +12930,12 @@ snapshots:
punycode@2.3.1: {}
+ pvtsutils@1.3.6:
+ dependencies:
+ tslib: 2.8.1
+
+ pvutils@1.1.3: {}
+
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
@@ -13054,6 +13298,8 @@ snapshots:
rotating-file-stream@3.2.3: {}
+ rou3@0.5.1: {}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -13106,6 +13352,8 @@ snapshots:
set-blocking@2.0.0: {}
+ set-cookie-parser@2.7.1: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -13617,6 +13865,8 @@ snapshots:
tslib@2.6.3: {}
+ tslib@2.8.1: {}
+
tsx@4.16.2:
dependencies:
esbuild: 0.21.5
@@ -13736,6 +13986,10 @@ snapshots:
v8-compile-cache-lib@3.0.1:
optional: true
+ valibot@1.0.0-beta.15(typescript@5.5.3):
+ optionalDependencies:
+ typescript: 5.5.3
+
victory-vendor@36.9.2:
dependencies:
'@types/d3-array': 3.2.1
@@ -13994,3 +14248,5 @@ snapshots:
zod: 3.23.8
zod@3.23.8: {}
+
+ zod@3.24.1: {}