Merge branch 'canary' into 1345-domain-not-working-after-server-restart-or-traefik-reload

This commit is contained in:
Mauricio Siu
2025-03-02 02:34:10 -06:00
376 changed files with 26817 additions and 4623 deletions

View File

@@ -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" }),
// });

View File

@@ -28,13 +28,25 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"rotating-file-stream": "3.2.3",
"@ai-sdk/anthropic": "^1.0.6",
"@ai-sdk/azure": "^1.0.15",
"@ai-sdk/cohere": "^1.0.6",
"@ai-sdk/deepinfra": "^0.0.4",
"@ai-sdk/mistral": "^1.0.6",
"@ai-sdk/openai": "^1.0.12",
"@ai-sdk/openai-compatible": "^0.0.13",
"@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",
"@faker-js/faker": "^8.4.1",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
"@react-email/components": "^0.0.21",
"@trpc/server": "^10.43.6",
"adm-zip": "^0.5.14",
"ai": "^4.0.23",
"bcrypt": "5.1.1",
"bl": "6.0.11",
"boxen": "^7.1.1",
@@ -53,22 +65,20 @@
"node-schedule": "2.1.1",
"nodemailer": "6.9.14",
"octokit": "3.1.2",
"ollama-ai-provider": "^1.1.0",
"otpauth": "^9.2.3",
"postgres": "3.4.4",
"public-ip": "6.0.2",
"qrcode": "^1.5.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"rotating-file-stream": "3.2.3",
"slugify": "^1.6.6",
"ssh2": "1.15.0",
"ws": "8.16.0",
"zod": "^3.23.4",
"ssh2": "1.15.0"
"zod": "^3.23.4"
},
"devDependencies": {
"esbuild-plugin-alias": "0.2.1",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.1",
"tsc-alias": "1.8.10",
"@types/adm-zip": "^0.5.5",
"@types/bcrypt": "5.0.2",
"@types/dockerode": "3.3.23",
@@ -81,11 +91,15 @@
"@types/qrcode": "^1.5.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/ssh2": "1.15.1",
"@types/ws": "8.5.10",
"drizzle-kit": "^0.30.4",
"esbuild": "0.20.2",
"esbuild-plugin-alias": "0.2.1",
"postcss": "^8.4.31",
"typescript": "^5.4.2",
"@types/ssh2": "1.15.1"
"tailwindcss": "^3.4.1",
"tsc-alias": "1.8.10",
"tsx": "^4.7.1",
"typescript": "^5.4.2"
}
}

View File

@@ -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<DatabaseUser, "id"> & {
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;
}

View File

@@ -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,
}),
};
};

View File

@@ -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],
}),
}));

View File

@@ -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(),
});

View File

@@ -0,0 +1,82 @@
import { relations } from "drizzle-orm";
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
export const ai = pgTable("ai", {
aiId: text("aiId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull(),
apiUrl: text("apiUrl").notNull(),
apiKey: text("apiKey").notNull(),
model: text("model").notNull(),
isEnabled: boolean("isEnabled").notNull().default(true),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }), // Admin ID who created the AI settings
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
});
export const aiRelations = relations(ai, ({ one }) => ({
organization: one(organization, {
fields: [ai.organizationId],
references: [organization.id],
}),
}));
const createSchema = createInsertSchema(ai, {
name: z.string().min(1, { message: "Name is required" }),
apiUrl: z.string().url({ message: "Please enter a valid URL" }),
apiKey: z.string().min(1, { message: "API Key is required" }),
model: z.string().min(1, { message: "Model is required" }),
isEnabled: z.boolean().optional(),
});
export const apiCreateAi = createSchema
.pick({
name: true,
apiUrl: true,
apiKey: true,
model: true,
isEnabled: true,
})
.required();
export const apiUpdateAi = createSchema
.partial()
.extend({
aiId: z.string().min(1),
})
.omit({ organizationId: true });
export const deploySuggestionSchema = z.object({
projectId: z.string().min(1),
id: z.string().min(1),
dockerCompose: z.string().min(1),
envVariables: z.string(),
serverId: z.string().optional(),
name: z.string().min(1),
description: z.string(),
domains: z
.array(
z.object({
host: z.string().min(1),
port: z.number().min(1),
serviceName: z.string().min(1),
}),
)
.optional(),
configFiles: z
.array(
z.object({
filePath: z.string().min(1),
content: z.string().min(1),
}),
)
.optional(),
});

View File

@@ -44,7 +44,6 @@ export const buildType = pgEnum("buildType", [
"static",
]);
// TODO: refactor this types
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;

View File

@@ -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();

View File

@@ -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(),
});

View File

@@ -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),

View File

@@ -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 });

View File

@@ -1,4 +1,4 @@
import { is, relations } from "drizzle-orm";
import { relations } from "drizzle-orm";
import {
type AnyPgColumn,
boolean,

View File

@@ -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],
}),
}),
);

View File

@@ -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],
}),
}));

View File

@@ -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,5 @@ export * from "./gitlab";
export * from "./server";
export * from "./utils";
export * from "./preview-deployments";
export * from "./ai";
export * from "./account";

View File

@@ -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", {
@@ -65,6 +65,7 @@ export const telegram = pgTable("telegram", {
.$defaultFn(() => nanoid()),
botToken: text("botToken").notNull(),
chatId: text("chatId").notNull(),
messageThreadId: text("messageThreadId"),
});
export const discord = pgTable("discord", {
@@ -121,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],
}),
}));
@@ -148,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({
@@ -169,18 +170,20 @@ export const apiCreateTelegram = notificationsSchema
.extend({
botToken: z.string().min(1),
chatId: z.string().min(1),
messageThreadId: z.string(),
})
.required();
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({
botToken: true,
chatId: true,
messageThreadId: true,
});
export const apiCreateDiscord = notificationsSchema
@@ -202,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
@@ -236,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({
@@ -268,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

View File

@@ -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],
}),
}));

View File

@@ -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(),

View File

@@ -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

View File

@@ -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, {

View File

@@ -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"),
});

View File

@@ -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,
});

View File

@@ -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 }));

View File

@@ -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(),
});

View File

@@ -12,7 +12,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;

View File

@@ -12,7 +12,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;

View File

@@ -10,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;

View File

@@ -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;

View File

@@ -10,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
date: string;

View File

@@ -9,7 +9,6 @@ import {
Preview,
Text,
} from "@react-email/components";
import * as React from "react";
interface NotionMagicLinkEmailProps {
loginCode?: string;

View File

@@ -9,7 +9,6 @@ import {
Section,
Text,
} from "@react-email/components";
import * as React from "react";
interface PlaidVerifyIdentityEmailProps {
validationCode?: string;

View File

@@ -11,7 +11,6 @@ import {
Section,
Text,
} from "@react-email/components";
import * as React from "react";
const baseUrl = process.env.VERCEL_URL!;

View File

@@ -15,7 +15,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
interface VercelInviteUserEmailProps {
username?: string;

View File

@@ -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";

View File

@@ -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: `
<p>Click the link to verify your email: <a href="${url}">Verify Email</a></p>
`,
});
}
},
},
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: `
<p>Click the link to reset your password: <a href="${url}">Reset Password</a></p>
`,
});
},
},
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: `
<p>You are invited to join ${data.organization.name} on Dokploy. Click the link to accept the invitation: <a href="${inviteLink}">Accept Invitation</a></p>
`,
});
}
},
}),
],
});
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;
};

View File

@@ -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;
}
};

View File

@@ -1,124 +1,59 @@
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<Admin>,
) => {
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<Admin>,
) => {
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),
with: {
owner: true,
},
});
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 +64,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 +82,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 +109,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}`;
};

View File

@@ -0,0 +1,212 @@
import { db } from "@dokploy/server/db";
import { ai } from "@dokploy/server/db/schema";
import { selectAIProvider } from "@dokploy/server/utils/ai/select-ai-provider";
import { TRPCError } from "@trpc/server";
import { generateObject } from "ai";
import { desc, eq } from "drizzle-orm";
import { z } from "zod";
import { IS_CLOUD } from "../constants";
import { findServerById } from "./server";
import { findOrganizationById } from "./admin";
export const getAiSettingsByOrganizationId = async (organizationId: string) => {
const aiSettings = await db.query.ai.findMany({
where: eq(ai.organizationId, organizationId),
orderBy: desc(ai.createdAt),
});
return aiSettings;
};
export const getAiSettingById = async (aiId: string) => {
const aiSetting = await db.query.ai.findFirst({
where: eq(ai.aiId, aiId),
});
if (!aiSetting) {
throw new TRPCError({
code: "NOT_FOUND",
message: "AI settings not found",
});
}
return aiSetting;
};
export const saveAiSettings = async (organizationId: string, settings: any) => {
const aiId = settings.aiId;
return db
.insert(ai)
.values({
aiId,
organizationId,
...settings,
})
.onConflictDoUpdate({
target: ai.aiId,
set: {
...settings,
},
});
};
export const deleteAiSettings = async (aiId: string) => {
return db.delete(ai).where(eq(ai.aiId, aiId));
};
interface Props {
organizationId: string;
aiId: string;
input: string;
serverId?: string | undefined;
}
export const suggestVariants = async ({
organizationId,
aiId,
input,
serverId,
}: Props) => {
try {
const aiSettings = await getAiSettingById(aiId);
if (!aiSettings || !aiSettings.isEnabled) {
throw new TRPCError({
code: "NOT_FOUND",
message: "AI features are not enabled for this configuration",
});
}
const provider = selectAIProvider(aiSettings);
const model = provider(aiSettings.model);
let ip = "";
if (!IS_CLOUD) {
const organization = await findOrganizationById(organizationId);
ip = organization?.owner.serverIp || "";
}
if (serverId) {
const server = await findServerById(serverId);
ip = server.ipAddress;
} else if (process.env.NODE_ENV === "development") {
ip = "127.0.0.1";
}
const { object } = await generateObject({
model,
output: "array",
schema: z.object({
id: z.string(),
name: z.string(),
shortDescription: z.string(),
description: z.string(),
}),
prompt: `
Act as advanced DevOps engineer and generate a list of open source projects what can cover users needs(up to 3 items), the suggestion
should include id, name, shortDescription, and description. Use slug of title for id.
Important rules for the response:
1. The description field should ONLY contain a plain text description of the project, its features, and use cases
2. Do NOT include any code snippets, configuration examples, or installation instructions in the description
3. The shortDescription should be a single-line summary focusing on the main technologies
User wants to create a new project with the following details, it should be installable in docker and can be docker compose generated for it:
${input}
`,
});
if (object?.length) {
const result = [];
for (const suggestion of object) {
try {
const { object: docker } = await generateObject({
model,
output: "object",
schema: z.object({
dockerCompose: z.string(),
envVariables: z.array(
z.object({
name: z.string(),
value: z.string(),
}),
),
domains: z.array(
z.object({
host: z.string(),
port: z.number(),
serviceName: z.string(),
}),
),
configFiles: z.array(
z.object({
content: z.string(),
filePath: z.string(),
}),
),
}),
prompt: `
Act as advanced DevOps engineer and generate docker compose with environment variables and domain configurations needed to install the following project.
Return the docker compose as a YAML string and environment variables configuration. Follow these rules:
Docker Compose Rules:
1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml
2. Use complex values for passwords/secrets variables
3. Don't set container_name field in services
4. Don't set version field in the docker compose
5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead
6. If a service depends on a database or other service, INCLUDE that service in the docker-compose
7. Make sure all required services are defined in the docker-compose
Volume Mounting and Configuration Rules:
1. DO NOT create configuration files unless the service CANNOT work without them
2. Most services can work with just environment variables - USE THEM FIRST
3. Ask yourself: "Can this be configured with an environment variable instead?"
4. If and ONLY IF a config file is absolutely required:
- Keep it minimal with only critical settings
- Use "../files/" prefix for all mounts
- Format: "../files/folder:/container/path"
5. DO NOT add configuration files for:
- Default configurations that work out of the box
- Settings that can be handled by environment variables
- Proxy or routing configurations (these are handled elsewhere)
Environment Variables Rules:
1. For the envVariables array, provide ACTUAL example values, not placeholders
2. Use realistic example values (e.g., "admin@example.com" for emails, "mypassword123" for passwords)
3. DO NOT use \${VARIABLE_NAME-default} syntax in the envVariables values
4. ONLY include environment variables that are actually used in the docker-compose
5. Every environment variable referenced in the docker-compose MUST have a corresponding entry in envVariables
6. Do not include environment variables for services that don't exist in the docker-compose
For each service that needs to be exposed to the internet:
1. Define a domain configuration with:
- host: the domain name for the service in format: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me
- port: the internal port the service runs on
- serviceName: the name of the service in the docker-compose
2. Make sure the service is properly configured to work with the specified port
Project details:
${suggestion?.description}
`,
});
if (!!docker && !!docker.dockerCompose) {
result.push({
...suggestion,
...docker,
});
}
} catch (error) {
console.error("Error in docker compose generation:", error);
}
}
return result;
}
throw new TRPCError({
code: "NOT_FOUND",
message: "No suggestions found",
});
} catch (error) {
console.error("Error in suggestVariants:", error);
throw error;
}
};

View File

@@ -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);

View File

@@ -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<Auth>,
) => {
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<Auth, "password">,
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;
};

View File

@@ -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;

View File

@@ -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();

View File

@@ -33,13 +33,13 @@ export const findCertificateById = async (certificateId: string) => {
export const createCertificate = async (
certificateData: z.infer<typeof apiCreateCertificate>,
adminId: string,
organizationId: string,
) => {
const certificate = await db
.insert(certificates)
.values({
...certificateData,
adminId: adminId,
organizationId: organizationId,
})
.returning();

View File

@@ -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);
}

View File

@@ -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,
});
}
};

View File

@@ -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();

View File

@@ -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 [];
};
@@ -331,7 +331,7 @@ export const getContainersByAppLabel = async (
});
return containers || [];
} catch (error) {}
} catch (_error) {}
return [];
};
@@ -350,7 +350,7 @@ export const containerRestart = async (containerId: string) => {
const config = JSON.parse(stdout);
return config;
} catch (error) {}
} catch (_error) {}
};
export const getSwarmNodes = async (serverId?: string) => {
@@ -379,7 +379,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) => {
@@ -405,7 +405,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) => {
@@ -437,7 +437,7 @@ export const getNodeApplications = async (serverId?: string) => {
.filter((service) => !service.Name.startsWith("dokploy-"));
return appArray;
} catch (error) {}
} catch (_error) {}
};
export const getApplicationInfo = async (
@@ -470,5 +470,5 @@ export const getApplicationInfo = async (
.map((line) => JSON.parse(line));
return appArray;
} catch (error) {}
} catch (_error) {}
};

View File

@@ -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))

View File

@@ -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;
}
};

View File

@@ -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()

View File

@@ -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";

View File

@@ -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";

View File

@@ -123,8 +123,8 @@ export const updateMount = async (
mountId: string,
mountData: Partial<Mount>,
) => {
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) => {

View File

@@ -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
@@ -122,6 +122,7 @@ export const createTelegramNotification = async (
.values({
botToken: input.botToken,
chatId: input.chatId,
messageThreadId: input.messageThreadId,
})
.returning()
.then((value) => value[0]);
@@ -144,7 +145,7 @@ export const createTelegramNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "telegram",
adminId: adminId,
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -174,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))
@@ -193,6 +194,7 @@ export const updateTelegramNotification = async (
.set({
botToken: input.botToken,
chatId: input.chatId,
messageThreadId: input.messageThreadId,
})
.where(eq(telegram.telegramId, input.telegramId))
.returning()
@@ -204,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
@@ -234,7 +236,7 @@ export const createDiscordNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "discord",
adminId: adminId,
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -264,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))
@@ -294,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
@@ -328,7 +330,7 @@ export const createEmailNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "email",
adminId: adminId,
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
@@ -358,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))
@@ -392,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
@@ -424,7 +426,7 @@ export const createGotifyNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "gotify",
adminId: adminId,
organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -453,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()

View File

@@ -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";

View File

@@ -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<string> => {
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 || "";
}

View File

@@ -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]);

View File

@@ -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,
});
}
};

View File

@@ -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";

View File

@@ -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;
};

View File

@@ -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,
});
}
};

View File

@@ -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;
};

View File

@@ -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[] = [];

View File

@@ -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<User>) => {
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;
};

View File

@@ -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);

View File

@@ -55,7 +55,7 @@ export const initializePostgres = async () => {
...settings,
});
console.log("Postgres Started ✅");
} catch (error) {
} catch (_) {
try {
await docker.createService(settings);
} catch (error: any) {

View File

@@ -52,7 +52,7 @@ export const initializeRedis = async () => {
...settings,
});
console.log("Redis Started ✅");
} catch (error) {
} catch (_) {
try {
await docker.createService(settings);
} catch (error: any) {

View File

@@ -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) => {

View File

@@ -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) => {

View File

@@ -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;
}
};

View File

@@ -36,7 +36,7 @@ type AnyObj = Record<PropertyKey, unknown>;
type ZodObj<T extends AnyObj> = {
[key in keyof T]: z.ZodType<T[key]>;
};
const zObject = <T extends AnyObj>(arg: ZodObj<T>) => z.object(arg);
const _zObject = <T extends AnyObj>(arg: ZodObj<T>) => z.object(arg);
// const goodDogScheme = zObject<UserWithPosts>({
// // prueba: schema.selectDatabaseSchema,

View File

@@ -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<boolean> {
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<void> {
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,
});
}

View File

@@ -0,0 +1 @@
export * from "./select-ai-provider";

View File

@@ -0,0 +1,73 @@
import { createAnthropic } from "@ai-sdk/anthropic";
import { createAzure } from "@ai-sdk/azure";
import { createCohere } from "@ai-sdk/cohere";
import { createDeepInfra } from "@ai-sdk/deepinfra";
import { createMistral } from "@ai-sdk/mistral";
import { createOpenAI } from "@ai-sdk/openai";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createOllama } from "ollama-ai-provider";
function getProviderName(apiUrl: string) {
if (apiUrl.includes("api.openai.com")) return "openai";
if (apiUrl.includes("azure.com")) return "azure";
if (apiUrl.includes("api.anthropic.com")) return "anthropic";
if (apiUrl.includes("api.cohere.ai")) return "cohere";
if (apiUrl.includes("api.perplexity.ai")) return "perplexity";
if (apiUrl.includes("api.mistral.ai")) return "mistral";
if (apiUrl.includes("localhost:11434") || apiUrl.includes("ollama"))
return "ollama";
if (apiUrl.includes("api.deepinfra.com")) return "deepinfra";
throw new Error(`Unsupported AI provider for URL: ${apiUrl}`);
}
export function selectAIProvider(config: { apiUrl: string; apiKey: string }) {
const providerName = getProviderName(config.apiUrl);
switch (providerName) {
case "openai":
return createOpenAI({
apiKey: config.apiKey,
baseURL: config.apiUrl,
});
case "azure":
return createAzure({
apiKey: config.apiKey,
baseURL: config.apiUrl,
});
case "anthropic":
return createAnthropic({
apiKey: config.apiKey,
baseURL: config.apiUrl,
});
case "cohere":
return createCohere({
baseURL: config.apiUrl,
apiKey: config.apiKey,
});
case "perplexity":
return createOpenAICompatible({
name: "perplexity",
baseURL: config.apiUrl,
headers: {
Authorization: `Bearer ${config.apiKey}`,
},
});
case "mistral":
return createMistral({
baseURL: config.apiUrl,
apiKey: config.apiKey,
});
case "ollama":
return createOllama({
// optional settings, e.g.
baseURL: config.apiUrl,
});
case "deepinfra":
return createDeepInfra({
baseURL: config.apiUrl,
apiKey: config.apiKey,
});
default:
throw new Error(`Unsupported AI provider: ${providerName}`);
}
}

View File

@@ -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})`,
);
});

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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}`,

View File

@@ -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");

View File

@@ -197,7 +197,7 @@ export const mechanizeDockerContainer = async (
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
} catch (error) {
} catch (_error) {
await docker.createService(settings);
}
};

View File

@@ -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)}`;

View File

@@ -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);
}
};

View File

@@ -152,7 +152,7 @@ ${command ?? "wait $MONGOD_PID"}`;
version: Number.parseInt(inspect.Version.Index),
...settings,
});
} catch (error) {
} catch (_error) {
await docker.createService(settings);
}
};

View File

@@ -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);
}
};

View File

@@ -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);
}
};

View File

@@ -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;
}
};

View File

@@ -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}`;

View File

@@ -34,7 +34,7 @@ export async function checkGPUStatus(serverId?: string): Promise<GPUInfo> {
...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.",
);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -68,6 +68,7 @@ export const sendTelegramNotification = async (
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: connection.chatId,
message_thread_id: connection.messageThreadId,
text: messageText,
parse_mode: "HTML",
disable_web_page_preview: true,

View File

@@ -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 });

View File

@@ -176,7 +176,6 @@ export const getBitbucketCloneCommand = async (
bitbucketBranch,
bitbucketId,
serverId,
bitbucket,
} = entity;
if (!serverId) {

View File

@@ -320,7 +320,7 @@ export const cloneGitRawRepository = async (entity: {
outputPath,
"--progress",
],
(data) => {},
(_data) => {},
{
env: {
...process.env,

Some files were not shown because too many files have changed in this diff Show More