mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-11 08:55:22 +02:00
Merge branch 'canary' into feat/2931-template-bookmarking
This commit is contained in:
47
packages/server/src/db/constants.ts
Normal file
47
packages/server/src/db/constants.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
export const {
|
||||
DATABASE_URL,
|
||||
POSTGRES_PASSWORD_FILE,
|
||||
POSTGRES_USER = "dokploy",
|
||||
POSTGRES_DB = "dokploy",
|
||||
POSTGRES_HOST = "dokploy-postgres",
|
||||
POSTGRES_PORT = "5432",
|
||||
} = process.env;
|
||||
|
||||
function readSecret(path: string): string {
|
||||
try {
|
||||
return fs.readFileSync(path, "utf8").trim();
|
||||
} catch {
|
||||
throw new Error(`Cannot read secret at ${path}`);
|
||||
}
|
||||
}
|
||||
export let dbUrl: string;
|
||||
if (DATABASE_URL) {
|
||||
// Compatibilidad legacy / overrides
|
||||
dbUrl = DATABASE_URL;
|
||||
} else if (POSTGRES_PASSWORD_FILE) {
|
||||
const password = readSecret(POSTGRES_PASSWORD_FILE);
|
||||
dbUrl = `postgres://${POSTGRES_USER}:${encodeURIComponent(
|
||||
password,
|
||||
)}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`;
|
||||
} else {
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
console.warn(`
|
||||
⚠️ [DEPRECATED DATABASE CONFIG]
|
||||
You are using the legacy hardcoded database credentials.
|
||||
This mode WILL BE REMOVED in a future release.
|
||||
|
||||
Please migrate to Docker Secrets using POSTGRES_PASSWORD_FILE.
|
||||
Please execute this command in your server: curl -sSL https://dokploy.com/security/0.26.6.sh | bash
|
||||
`);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
dbUrl =
|
||||
"postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
|
||||
} else {
|
||||
dbUrl =
|
||||
"postgres://dokploy:amukds4wi9001583845717ad2@localhost:5432/dokploy";
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,40 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import { dbUrl } from "./constants";
|
||||
import * as schema from "./schema";
|
||||
|
||||
declare global {
|
||||
var db: PostgresJsDatabase<typeof schema> | undefined;
|
||||
}
|
||||
export { and, eq };
|
||||
export * from "./schema";
|
||||
|
||||
type Database = PostgresJsDatabase<typeof schema>;
|
||||
|
||||
/**
|
||||
* Evita problemas de redeclaración global en monorepos.
|
||||
* No usamos `declare global`.
|
||||
*/
|
||||
const globalForDb = globalThis as unknown as {
|
||||
db?: Database;
|
||||
};
|
||||
|
||||
let dbConnection: Database;
|
||||
|
||||
export let db: PostgresJsDatabase<typeof schema>;
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
db = drizzle(postgres(process.env.DATABASE_URL!), {
|
||||
// En producción no usamos global cache
|
||||
dbConnection = drizzle(postgres(dbUrl), {
|
||||
schema,
|
||||
});
|
||||
} else {
|
||||
if (!global.db)
|
||||
global.db = drizzle(postgres(process.env.DATABASE_URL!), {
|
||||
// En desarrollo reutilizamos conexión para evitar múltiples conexiones
|
||||
if (!globalForDb.db) {
|
||||
globalForDb.db = drizzle(postgres(dbUrl), {
|
||||
schema,
|
||||
});
|
||||
}
|
||||
|
||||
db = global.db;
|
||||
dbConnection = globalForDb.db;
|
||||
}
|
||||
|
||||
export const db: Database = dbConnection;
|
||||
|
||||
export { dbUrl };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
pgTable,
|
||||
text,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
import { nanoid } from "nanoid";
|
||||
import { projects } from "./project";
|
||||
import { server } from "./server";
|
||||
import { ssoProvider } from "./sso";
|
||||
import { user } from "./user";
|
||||
|
||||
export const account = pgTable("account", {
|
||||
@@ -68,6 +70,36 @@ export const organization = pgTable("organization", {
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const organizationRole = pgTable(
|
||||
"organization_role",
|
||||
{
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
organizationId: text("organization_id")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
role: text("role").notNull(),
|
||||
permission: text("permission").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at").$onUpdate(() => new Date()),
|
||||
},
|
||||
(table) => [
|
||||
index("organizationRole_organizationId_idx").on(table.organizationId),
|
||||
index("organizationRole_role_idx").on(table.role),
|
||||
],
|
||||
);
|
||||
|
||||
export const organizationRoleRelations = relations(
|
||||
organizationRole,
|
||||
({ one }) => ({
|
||||
organization: one(organization, {
|
||||
fields: [organizationRole.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const organizationRelations = relations(
|
||||
organization,
|
||||
({ one, many }) => ({
|
||||
@@ -78,6 +110,8 @@ export const organizationRelations = relations(
|
||||
servers: many(server),
|
||||
projects: many(projects),
|
||||
members: many(member),
|
||||
ssoProviders: many(ssoProvider),
|
||||
roles: many(organizationRole),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -91,7 +125,9 @@ export const member = pgTable("member", {
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
role: text("role").notNull().$type<"owner" | "member" | "admin">(),
|
||||
role: text("role")
|
||||
.notNull()
|
||||
.$type<"owner" | "member" | "admin" | (string & {})>(),
|
||||
createdAt: timestamp("created_at").notNull(),
|
||||
teamId: text("team_id"),
|
||||
isDefault: boolean("is_default").notNull().default(false),
|
||||
@@ -127,6 +163,10 @@ export const member = pgTable("member", {
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`ARRAY[]::text[]`),
|
||||
accessedGitProviders: text("accessedGitProviders")
|
||||
.array()
|
||||
.notNull()
|
||||
.default(sql`ARRAY[]::text[]`),
|
||||
});
|
||||
|
||||
export const memberRelations = relations(member, ({ one }) => ({
|
||||
@@ -146,13 +186,14 @@ export const invitation = pgTable("invitation", {
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
email: text("email").notNull(),
|
||||
role: text("role").$type<"owner" | "member" | "admin">(),
|
||||
role: text("role").$type<"owner" | "member" | "admin" | (string & {})>(),
|
||||
status: text("status").notNull(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
inviterId: text("inviter_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
teamId: text("team_id"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const invitationRelations = relations(invitation, ({ one }) => ({
|
||||
@@ -177,7 +218,8 @@ export const apikey = pgTable("apikey", {
|
||||
start: text("start"),
|
||||
prefix: text("prefix"),
|
||||
key: text("key").notNull(),
|
||||
userId: text("user_id")
|
||||
configId: text("config_id").default("default").notNull(),
|
||||
referenceId: text("reference_id")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
refillInterval: integer("refill_interval"),
|
||||
@@ -199,7 +241,7 @@ export const apikey = pgTable("apikey", {
|
||||
|
||||
export const apikeyRelations = relations(apikey, ({ one }) => ({
|
||||
user: one(user, {
|
||||
fields: [apikey.userId],
|
||||
fields: [apikey.referenceId],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -19,6 +19,7 @@ import { gitea } from "./gitea";
|
||||
import { github } from "./github";
|
||||
import { gitlab } from "./gitlab";
|
||||
import { mounts } from "./mount";
|
||||
import { patch } from "./patch";
|
||||
import { ports } from "./port";
|
||||
import { previewDeployments } from "./preview-deployments";
|
||||
import { redirects } from "./redirects";
|
||||
@@ -43,11 +44,13 @@ import {
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
triggerType,
|
||||
type UlimitsSwarm,
|
||||
UlimitsSwarmSchema,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { sshKeys } from "./ssh-key";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
export const sourceType = pgEnum("sourceType", [
|
||||
"docker",
|
||||
"git",
|
||||
@@ -111,6 +114,7 @@ export const applications = pgTable("application", {
|
||||
enabled: boolean("enabled"),
|
||||
subtitle: text("subtitle"),
|
||||
command: text("command"),
|
||||
args: text("args").array(),
|
||||
refreshToken: text("refreshToken").$defaultFn(() => nanoid()),
|
||||
sourceType: sourceType("sourceType").notNull().default("github"),
|
||||
cleanCache: boolean("cleanCache").default(false),
|
||||
@@ -135,6 +139,7 @@ export const applications = pgTable("application", {
|
||||
giteaBuildPath: text("giteaBuildPath").default("/"),
|
||||
// Bitbucket
|
||||
bitbucketRepository: text("bitbucketRepository"),
|
||||
bitbucketRepositorySlug: text("bitbucketRepositorySlug"),
|
||||
bitbucketOwner: text("bitbucketOwner"),
|
||||
bitbucketBranch: text("bitbucketBranch"),
|
||||
bitbucketBuildPath: text("bitbucketBuildPath").default("/"),
|
||||
@@ -154,7 +159,7 @@ export const applications = pgTable("application", {
|
||||
},
|
||||
),
|
||||
enableSubmodules: boolean("enableSubmodules").notNull().default(false),
|
||||
dockerfile: text("dockerfile"),
|
||||
dockerfile: text("dockerfile").default("Dockerfile"),
|
||||
dockerContextPath: text("dockerContextPath"),
|
||||
dockerBuildStage: text("dockerBuildStage"),
|
||||
// Drop
|
||||
@@ -170,22 +175,30 @@ export const applications = pgTable("application", {
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
ulimitsSwarm: json("ulimitsSwarm").$type<UlimitsSwarm>(),
|
||||
//
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
applicationStatus: applicationStatus("applicationStatus")
|
||||
.notNull()
|
||||
.default("idle"),
|
||||
buildType: buildType("buildType").notNull().default("nixpacks"),
|
||||
railpackVersion: text("railpackVersion").default("0.2.2"),
|
||||
railpackVersion: text("railpackVersion").default("0.15.4"),
|
||||
herokuVersion: text("herokuVersion").default("24"),
|
||||
publishDirectory: text("publishDirectory"),
|
||||
isStaticSpa: boolean("isStaticSpa"),
|
||||
createEnvFile: boolean("createEnvFile").notNull().default(true),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
registryId: text("registryId").references(() => registry.registryId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
rollbackRegistryId: text("rollbackRegistryId").references(
|
||||
() => registry.registryId,
|
||||
{
|
||||
onDelete: "set null",
|
||||
},
|
||||
),
|
||||
environmentId: text("environmentId")
|
||||
.notNull()
|
||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
||||
@@ -204,6 +217,15 @@ export const applications = pgTable("application", {
|
||||
serverId: text("serverId").references(() => server.serverId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
buildServerId: text("buildServerId").references(() => server.serverId, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
buildRegistryId: text("buildRegistryId").references(
|
||||
() => registry.registryId,
|
||||
{
|
||||
onDelete: "set null",
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
export const applicationsRelations = relations(
|
||||
@@ -226,6 +248,7 @@ export const applicationsRelations = relations(
|
||||
registry: one(registry, {
|
||||
fields: [applications.registryId],
|
||||
references: [registry.registryId],
|
||||
relationName: "applicationRegistry",
|
||||
}),
|
||||
github: one(github, {
|
||||
fields: [applications.githubId],
|
||||
@@ -246,13 +269,35 @@ export const applicationsRelations = relations(
|
||||
server: one(server, {
|
||||
fields: [applications.serverId],
|
||||
references: [server.serverId],
|
||||
relationName: "applicationServer",
|
||||
}),
|
||||
buildServer: one(server, {
|
||||
fields: [applications.buildServerId],
|
||||
references: [server.serverId],
|
||||
relationName: "applicationBuildServer",
|
||||
}),
|
||||
buildRegistry: one(registry, {
|
||||
fields: [applications.buildRegistryId],
|
||||
references: [registry.registryId],
|
||||
relationName: "applicationBuildRegistry",
|
||||
}),
|
||||
previewDeployments: many(previewDeployments),
|
||||
rollbackRegistry: one(registry, {
|
||||
fields: [applications.rollbackRegistryId],
|
||||
references: [registry.registryId],
|
||||
relationName: "applicationRollbackRegistry",
|
||||
}),
|
||||
patches: many(patch),
|
||||
}),
|
||||
);
|
||||
|
||||
const createSchema = createInsertSchema(applications, {
|
||||
appName: z.string(),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
createdAt: z.string(),
|
||||
applicationId: z.string(),
|
||||
autoDeploy: z.boolean(),
|
||||
@@ -272,6 +317,7 @@ const createSchema = createInsertSchema(applications, {
|
||||
username: z.string().optional(),
|
||||
isPreviewDeploymentsActive: z.boolean().optional(),
|
||||
password: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
registryUrl: z.string().optional(),
|
||||
customGitSSHKeyId: z.string().optional(),
|
||||
repository: z.string().optional(),
|
||||
@@ -285,6 +331,7 @@ const createSchema = createInsertSchema(applications, {
|
||||
sourceType: z
|
||||
.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"])
|
||||
.optional(),
|
||||
triggerType: z.enum(["push", "tag"]).optional(),
|
||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||
buildType: z.enum([
|
||||
"dockerfile",
|
||||
@@ -298,6 +345,7 @@ const createSchema = createInsertSchema(applications, {
|
||||
herokuVersion: z.string().optional(),
|
||||
publishDirectory: z.string().optional(),
|
||||
isStaticSpa: z.boolean().optional(),
|
||||
createEnvFile: z.boolean().optional(),
|
||||
owner: z.string(),
|
||||
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||
@@ -317,11 +365,13 @@ const createSchema = createInsertSchema(applications, {
|
||||
previewPath: z.string().optional(),
|
||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||
previewRequireCollaboratorPermissions: z.boolean().optional(),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
watchPaths: z.array(z.string()).optional().optional(),
|
||||
previewLabels: z.array(z.string()).optional(),
|
||||
cleanCache: z.boolean().optional(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
enableSubmodules: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiCreateApplication = createSchema.pick({
|
||||
@@ -332,11 +382,9 @@ export const apiCreateApplication = createSchema.pick({
|
||||
serverId: true,
|
||||
});
|
||||
|
||||
export const apiFindOneApplication = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneApplication = z.object({
|
||||
applicationId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiDeployApplication = createSchema
|
||||
.pick({
|
||||
@@ -386,13 +434,13 @@ export const apiSaveGithubProvider = createSchema
|
||||
owner: true,
|
||||
buildPath: true,
|
||||
githubId: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||
});
|
||||
})
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveGitlabProvider = createSchema
|
||||
.pick({
|
||||
@@ -404,10 +452,9 @@ export const apiSaveGitlabProvider = createSchema
|
||||
gitlabId: true,
|
||||
gitlabProjectId: true,
|
||||
gitlabPathNamespace: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveBitbucketProvider = createSchema
|
||||
.pick({
|
||||
@@ -415,12 +462,12 @@ export const apiSaveBitbucketProvider = createSchema
|
||||
bitbucketBuildPath: true,
|
||||
bitbucketOwner: true,
|
||||
bitbucketRepository: true,
|
||||
bitbucketRepositorySlug: true,
|
||||
bitbucketId: true,
|
||||
applicationId: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveGiteaProvider = createSchema
|
||||
.pick({
|
||||
@@ -430,10 +477,9 @@ export const apiSaveGiteaProvider = createSchema
|
||||
giteaOwner: true,
|
||||
giteaRepository: true,
|
||||
giteaId: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveDockerProvider = createSchema
|
||||
.pick({
|
||||
@@ -458,6 +504,7 @@ export const apiSaveGitProvider = createSchema
|
||||
.merge(
|
||||
createSchema.pick({
|
||||
customGitSSHKeyId: true,
|
||||
enableSubmodules: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -467,14 +514,13 @@ export const apiSaveEnvironmentVariables = createSchema
|
||||
env: true,
|
||||
buildArgs: true,
|
||||
buildSecrets: true,
|
||||
createEnvFile: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiFindMonitoringStats = createSchema
|
||||
.pick({
|
||||
appName: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindMonitoringStats = z.object({
|
||||
appName: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateApplication = createSchema
|
||||
.partial()
|
||||
|
||||
94
packages/server/src/db/schema/audit-log.ts
Normal file
94
packages/server/src/db/schema/audit-log.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { organization } from "./account";
|
||||
import { user } from "./user";
|
||||
|
||||
export const auditLog = pgTable(
|
||||
"audit_log",
|
||||
{
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
organizationId: text("organization_id").references(() => organization.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
userId: text("user_id").references(() => user.id, { onDelete: "set null" }),
|
||||
userEmail: text("user_email").notNull(),
|
||||
userRole: text("user_role").notNull(),
|
||||
action: text("action").notNull(),
|
||||
resourceType: text("resource_type").notNull(),
|
||||
resourceId: text("resource_id"),
|
||||
resourceName: text("resource_name"),
|
||||
metadata: text("metadata"),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
},
|
||||
(t) => ({
|
||||
orgIdx: index("auditLog_organizationId_idx").on(t.organizationId),
|
||||
userIdx: index("auditLog_userId_idx").on(t.userId),
|
||||
createdAtIdx: index("auditLog_createdAt_idx").on(t.createdAt),
|
||||
}),
|
||||
);
|
||||
|
||||
export const auditLogRelations = relations(auditLog, ({ one }) => ({
|
||||
organization: one(organization, {
|
||||
fields: [auditLog.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
user: one(user, {
|
||||
fields: [auditLog.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export type AuditLog = typeof auditLog.$inferSelect;
|
||||
export type NewAuditLog = typeof auditLog.$inferInsert;
|
||||
|
||||
export type AuditAction =
|
||||
| "create"
|
||||
| "update"
|
||||
| "delete"
|
||||
| "deploy"
|
||||
| "cancel"
|
||||
| "redeploy"
|
||||
| "login"
|
||||
| "logout"
|
||||
| "restore"
|
||||
| "run"
|
||||
| "start"
|
||||
| "stop"
|
||||
| "reload"
|
||||
| "rebuild"
|
||||
| "move";
|
||||
|
||||
export type AuditResourceType =
|
||||
| "project"
|
||||
| "service"
|
||||
| "environment"
|
||||
| "deployment"
|
||||
| "user"
|
||||
| "customRole"
|
||||
| "domain"
|
||||
| "certificate"
|
||||
| "registry"
|
||||
| "server"
|
||||
| "sshKey"
|
||||
| "gitProvider"
|
||||
| "destination"
|
||||
| "notification"
|
||||
| "settings"
|
||||
| "session"
|
||||
| "port"
|
||||
| "redirect"
|
||||
| "security"
|
||||
| "schedule"
|
||||
| "backup"
|
||||
| "volumeBackup"
|
||||
| "docker"
|
||||
| "swarm"
|
||||
| "previewDeployment"
|
||||
| "organization"
|
||||
| "cluster"
|
||||
| "mount"
|
||||
| "application"
|
||||
| "compose";
|
||||
@@ -15,6 +15,7 @@ import { generateAppName } from ".";
|
||||
import { compose } from "./compose";
|
||||
import { deployments } from "./deployment";
|
||||
import { destinations } from "./destination";
|
||||
import { libsql } from "./libsql";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { mysql } from "./mysql";
|
||||
@@ -26,6 +27,7 @@ export const databaseType = pgEnum("databaseType", [
|
||||
"mysql",
|
||||
"mongo",
|
||||
"web-server",
|
||||
"libsql",
|
||||
]);
|
||||
|
||||
export const backupType = pgEnum("backupType", ["database", "compose"]);
|
||||
@@ -74,6 +76,9 @@ export const backups = pgTable("backup", {
|
||||
mongoId: text("mongoId").references((): AnyPgColumn => mongo.mongoId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
libsqlId: text("libsqlId").references((): AnyPgColumn => libsql.libsqlId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
userId: text("userId").references(() => user.id),
|
||||
// Only for compose backups
|
||||
metadata: jsonb("metadata").$type<
|
||||
@@ -118,6 +123,10 @@ export const backupsRelations = relations(backups, ({ one, many }) => ({
|
||||
fields: [backups.mongoId],
|
||||
references: [mongo.mongoId],
|
||||
}),
|
||||
libsql: one(libsql, {
|
||||
fields: [backups.libsqlId],
|
||||
references: [libsql.libsqlId],
|
||||
}),
|
||||
user: one(user, {
|
||||
fields: [backups.userId],
|
||||
references: [user.id],
|
||||
@@ -137,11 +146,19 @@ const createSchema = createInsertSchema(backups, {
|
||||
database: z.string().min(1),
|
||||
schedule: z.string(),
|
||||
keepLatestCount: z.number().optional(),
|
||||
databaseType: z.enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]),
|
||||
databaseType: z.enum([
|
||||
"postgres",
|
||||
"mariadb",
|
||||
"mysql",
|
||||
"mongo",
|
||||
"web-server",
|
||||
"libsql",
|
||||
]),
|
||||
postgresId: z.string().optional(),
|
||||
mariadbId: z.string().optional(),
|
||||
mysqlId: z.string().optional(),
|
||||
mongoId: z.string().optional(),
|
||||
libsqlId: z.string().optional(),
|
||||
userId: z.string().optional(),
|
||||
metadata: z.any().optional(),
|
||||
});
|
||||
@@ -157,6 +174,7 @@ export const apiCreateBackup = createSchema.pick({
|
||||
mysqlId: true,
|
||||
postgresId: true,
|
||||
mongoId: true,
|
||||
libsqlId: true,
|
||||
databaseType: true,
|
||||
userId: true,
|
||||
backupType: true,
|
||||
@@ -165,11 +183,9 @@ export const apiCreateBackup = createSchema.pick({
|
||||
metadata: true,
|
||||
});
|
||||
|
||||
export const apiFindOneBackup = createSchema
|
||||
.pick({
|
||||
backupId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneBackup = z.object({
|
||||
backupId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveBackup = createSchema
|
||||
.pick({
|
||||
@@ -194,7 +210,14 @@ export const apiUpdateBackup = createSchema
|
||||
|
||||
export const apiRestoreBackup = z.object({
|
||||
databaseId: z.string(),
|
||||
databaseType: z.enum(["postgres", "mysql", "mariadb", "mongo", "web-server"]),
|
||||
databaseType: z.enum([
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"web-server",
|
||||
"libsql",
|
||||
]),
|
||||
backupType: z.enum(["database", "compose"]),
|
||||
databaseName: z.string().min(1),
|
||||
backupFile: z.string().min(1),
|
||||
|
||||
@@ -12,11 +12,12 @@ import { gitea } from "./gitea";
|
||||
import { github } from "./github";
|
||||
import { gitlab } from "./gitlab";
|
||||
import { mounts } from "./mount";
|
||||
import { patch } from "./patch";
|
||||
import { schedules } from "./schedule";
|
||||
import { server } from "./server";
|
||||
import { applicationStatus, triggerType } from "./shared";
|
||||
import { sshKeys } from "./ssh-key";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
|
||||
"git",
|
||||
"github",
|
||||
@@ -56,6 +57,7 @@ export const compose = pgTable("compose", {
|
||||
gitlabPathNamespace: text("gitlabPathNamespace"),
|
||||
// Bitbucket
|
||||
bitbucketRepository: text("bitbucketRepository"),
|
||||
bitbucketRepositorySlug: text("bitbucketRepositorySlug"),
|
||||
bitbucketOwner: text("bitbucketOwner"),
|
||||
bitbucketBranch: text("bitbucketBranch"),
|
||||
// Gitea
|
||||
@@ -142,10 +144,17 @@ export const composeRelations = relations(compose, ({ one, many }) => ({
|
||||
}),
|
||||
backups: many(backups),
|
||||
schedules: many(schedules),
|
||||
patches: many(patch),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(compose, {
|
||||
name: z.string().min(1),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
description: z.string(),
|
||||
env: z.string().optional(),
|
||||
composeFile: z.string().optional(),
|
||||
@@ -155,6 +164,11 @@ const createSchema = createInsertSchema(compose, {
|
||||
composePath: z.string().min(1),
|
||||
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
sourceType: z
|
||||
.enum(["git", "github", "gitlab", "bitbucket", "gitea", "raw"])
|
||||
.optional(),
|
||||
triggerType: z.enum(["push", "tag"]).optional(),
|
||||
composeStatus: z.enum(["idle", "running", "done", "error"]).optional(),
|
||||
});
|
||||
|
||||
export const apiCreateCompose = createSchema.pick({
|
||||
|
||||
@@ -70,6 +70,9 @@ export const deployments = pgTable("deployment", {
|
||||
(): AnyPgColumn => volumeBackups.volumeBackupId,
|
||||
{ onDelete: "cascade" },
|
||||
),
|
||||
buildServerId: text("buildServerId").references(() => server.serverId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
});
|
||||
|
||||
export const deploymentsRelations = relations(deployments, ({ one }) => ({
|
||||
@@ -84,6 +87,12 @@ export const deploymentsRelations = relations(deployments, ({ one }) => ({
|
||||
server: one(server, {
|
||||
fields: [deployments.serverId],
|
||||
references: [server.serverId],
|
||||
relationName: "deploymentServer",
|
||||
}),
|
||||
buildServer: one(server, {
|
||||
fields: [deployments.buildServerId],
|
||||
references: [server.serverId],
|
||||
relationName: "deploymentBuildServer",
|
||||
}),
|
||||
previewDeployment: one(previewDeployments, {
|
||||
fields: [deployments.previewDeploymentId],
|
||||
@@ -115,8 +124,8 @@ const schema = createInsertSchema(deployments, {
|
||||
composeId: z.string(),
|
||||
description: z.string().optional(),
|
||||
previewDeploymentId: z.string(),
|
||||
buildServerId: z.string(),
|
||||
});
|
||||
|
||||
export const apiCreateDeployment = schema
|
||||
.pick({
|
||||
title: true,
|
||||
@@ -200,44 +209,27 @@ export const apiCreateDeploymentVolumeBackup = schema
|
||||
volumeBackupId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindAllByApplication = schema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
})
|
||||
.extend({
|
||||
applicationId: z.string().min(1),
|
||||
})
|
||||
.required();
|
||||
export const apiFindAllByApplication = z.object({
|
||||
applicationId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindAllByCompose = schema
|
||||
.pick({
|
||||
composeId: true,
|
||||
})
|
||||
.extend({
|
||||
composeId: z.string().min(1),
|
||||
})
|
||||
.required();
|
||||
export const apiFindAllByCompose = z.object({
|
||||
composeId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindAllByServer = schema
|
||||
.pick({
|
||||
serverId: true,
|
||||
})
|
||||
.extend({
|
||||
serverId: z.string().min(1),
|
||||
})
|
||||
.required();
|
||||
export const apiFindAllByServer = z.object({
|
||||
serverId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindAllByType = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
type: z.enum([
|
||||
"application",
|
||||
"compose",
|
||||
"server",
|
||||
"schedule",
|
||||
"previewDeployment",
|
||||
"backup",
|
||||
"volumeBackup",
|
||||
]),
|
||||
})
|
||||
.required();
|
||||
export const apiFindAllByType = z.object({
|
||||
id: z.string().min(1),
|
||||
type: z.enum([
|
||||
"application",
|
||||
"compose",
|
||||
"server",
|
||||
"schedule",
|
||||
"previewDeployment",
|
||||
"backup",
|
||||
"volumeBackup",
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -3,6 +3,10 @@ import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
ADDITIONAL_FLAG_ERROR,
|
||||
ADDITIONAL_FLAG_REGEX,
|
||||
} from "../validations/destination";
|
||||
import { organization } from "./account";
|
||||
import { backups } from "./backups";
|
||||
|
||||
@@ -18,6 +22,7 @@ export const destinations = pgTable("destination", {
|
||||
bucket: text("bucket").notNull(),
|
||||
region: text("region").notNull(),
|
||||
endpoint: text("endpoint").notNull(),
|
||||
additionalFlags: text("additionalFlags").array(),
|
||||
organizationId: text("organizationId")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
@@ -44,6 +49,9 @@ const createSchema = createInsertSchema(destinations, {
|
||||
endpoint: z.string(),
|
||||
secretAccessKey: z.string(),
|
||||
region: z.string(),
|
||||
additionalFlags: z
|
||||
.array(z.string().regex(ADDITIONAL_FLAG_REGEX, ADDITIONAL_FLAG_ERROR))
|
||||
.default([]),
|
||||
});
|
||||
|
||||
export const apiCreateDestination = createSchema
|
||||
@@ -55,17 +63,16 @@ export const apiCreateDestination = createSchema
|
||||
region: true,
|
||||
endpoint: true,
|
||||
secretAccessKey: true,
|
||||
additionalFlags: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiFindOneDestination = createSchema
|
||||
.pick({
|
||||
destinationId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneDestination = z.object({
|
||||
destinationId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveDestination = createSchema
|
||||
.pick({
|
||||
@@ -83,6 +90,7 @@ export const apiUpdateDestination = createSchema
|
||||
secretAccessKey: true,
|
||||
destinationId: true,
|
||||
provider: true,
|
||||
additionalFlags: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
|
||||
@@ -31,6 +31,7 @@ export const domains = pgTable("domain", {
|
||||
host: text("host").notNull(),
|
||||
https: boolean("https").notNull().default(false),
|
||||
port: integer("port").default(3000),
|
||||
customEntrypoint: text("customEntrypoint"),
|
||||
path: text("path").default("/"),
|
||||
serviceName: text("serviceName"),
|
||||
domainType: domainType("domainType").default("application"),
|
||||
@@ -70,12 +71,17 @@ export const domainsRelations = relations(domains, ({ one }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(domains, domain._def.schema.shape);
|
||||
const createSchema = createInsertSchema(domains, {
|
||||
...domain.shape,
|
||||
// Override pgEnum so Zod 4 infers only string literals, not numeric enum index
|
||||
domainType: z.enum(["compose", "application", "preview"]).optional(),
|
||||
});
|
||||
|
||||
export const apiCreateDomain = createSchema.pick({
|
||||
host: true,
|
||||
path: true,
|
||||
port: true,
|
||||
customEntrypoint: true,
|
||||
https: true,
|
||||
applicationId: true,
|
||||
certificateType: true,
|
||||
@@ -88,11 +94,9 @@ export const apiCreateDomain = createSchema.pick({
|
||||
stripPath: true,
|
||||
});
|
||||
|
||||
export const apiFindDomain = createSchema
|
||||
.pick({
|
||||
domainId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindDomain = z.object({
|
||||
domainId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindDomainByApplication = createSchema.pick({
|
||||
applicationId: true,
|
||||
@@ -111,6 +115,7 @@ export const apiUpdateDomain = createSchema
|
||||
host: true,
|
||||
path: true,
|
||||
port: true,
|
||||
customEntrypoint: true,
|
||||
https: true,
|
||||
certificateType: true,
|
||||
customCertResolver: true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
import { libsql } from "./libsql";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { mysql } from "./mysql";
|
||||
@@ -26,6 +26,7 @@ export const environments = pgTable("environment", {
|
||||
projectId: text("projectId")
|
||||
.notNull()
|
||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||
isDefault: boolean("isDefault").notNull().default(false),
|
||||
});
|
||||
|
||||
export const environmentRelations = relations(
|
||||
@@ -36,50 +37,40 @@ export const environmentRelations = relations(
|
||||
references: [projects.projectId],
|
||||
}),
|
||||
applications: many(applications),
|
||||
mariadb: many(mariadb),
|
||||
postgres: many(postgres),
|
||||
mysql: many(mysql),
|
||||
redis: many(redis),
|
||||
mongo: many(mongo),
|
||||
compose: many(compose),
|
||||
libsql: many(libsql),
|
||||
mariadb: many(mariadb),
|
||||
mongo: many(mongo),
|
||||
mysql: many(mysql),
|
||||
postgres: many(postgres),
|
||||
redis: many(redis),
|
||||
}),
|
||||
);
|
||||
|
||||
const createSchema = createInsertSchema(environments, {
|
||||
export const apiCreateEnvironment = z.object({
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
projectId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindOneEnvironment = z.object({
|
||||
environmentId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveEnvironment = z.object({
|
||||
environmentId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateEnvironment = z.object({
|
||||
environmentId: z.string().min(1),
|
||||
name: z.string().min(1).optional(),
|
||||
description: z.string().optional(),
|
||||
projectId: z.string().optional(),
|
||||
env: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiDuplicateEnvironment = z.object({
|
||||
environmentId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiCreateEnvironment = createSchema.pick({
|
||||
name: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
});
|
||||
|
||||
export const apiFindOneEnvironment = createSchema
|
||||
.pick({
|
||||
environmentId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiRemoveEnvironment = createSchema
|
||||
.pick({
|
||||
environmentId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateEnvironment = createSchema.partial().extend({
|
||||
environmentId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiDuplicateEnvironment = createSchema
|
||||
.pick({
|
||||
environmentId: true,
|
||||
name: true,
|
||||
description: true,
|
||||
})
|
||||
.required({
|
||||
environmentId: true,
|
||||
name: true,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { organization } from "./account";
|
||||
@@ -33,6 +32,9 @@ export const gitProvider = pgTable("git_provider", {
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: "cascade" }),
|
||||
sharedWithOrganization: boolean("sharedWithOrganization")
|
||||
.notNull()
|
||||
.default(false),
|
||||
});
|
||||
|
||||
export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
|
||||
@@ -62,10 +64,11 @@ export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(gitProvider);
|
||||
export const apiRemoveGitProvider = z.object({
|
||||
gitProviderId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveGitProvider = createSchema
|
||||
.extend({
|
||||
gitProviderId: z.string().min(1),
|
||||
})
|
||||
.pick({ gitProviderId: true });
|
||||
export const apiToggleShareGitProvider = z.object({
|
||||
gitProviderId: z.string().min(1),
|
||||
sharedWithOrganization: z.boolean(),
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ export const gitea = pgTable("gitea", {
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
giteaUrl: text("giteaUrl").default("https://gitea.com").notNull(),
|
||||
giteaInternalUrl: text("giteaInternalUrl"),
|
||||
redirectUri: text("redirect_uri"),
|
||||
clientId: text("client_id"),
|
||||
clientSecret: text("client_secret"),
|
||||
@@ -40,6 +41,7 @@ export const apiCreateGitea = createSchema.extend({
|
||||
redirectUri: z.string().optional(),
|
||||
name: z.string().min(1),
|
||||
giteaUrl: z.string().min(1),
|
||||
giteaInternalUrl: z.string().optional().nullable(),
|
||||
giteaUsername: z.string().optional(),
|
||||
accessToken: z.string().optional(),
|
||||
refreshToken: z.string().optional(),
|
||||
@@ -76,6 +78,7 @@ export const apiUpdateGitea = createSchema.extend({
|
||||
name: z.string().min(1),
|
||||
giteaId: z.string().min(1),
|
||||
giteaUrl: z.string().min(1),
|
||||
giteaInternalUrl: z.string().optional().nullable(),
|
||||
giteaUsername: z.string().optional(),
|
||||
accessToken: z.string().optional(),
|
||||
refreshToken: z.string().optional(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { gitProvider } from "./git-provider";
|
||||
@@ -29,8 +28,7 @@ export const githubProviderRelations = relations(github, ({ one }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(github);
|
||||
export const apiCreateGithub = createSchema.extend({
|
||||
export const apiCreateGithub = z.object({
|
||||
githubAppName: z.string().optional(),
|
||||
githubAppId: z.number().optional(),
|
||||
githubClientId: z.string().optional(),
|
||||
@@ -48,13 +46,11 @@ export const apiFindGithubBranches = z.object({
|
||||
githubId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiFindOneGithub = createSchema
|
||||
.extend({
|
||||
githubId: z.string().min(1),
|
||||
})
|
||||
.pick({ githubId: true });
|
||||
export const apiFindOneGithub = z.object({
|
||||
githubId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateGithub = createSchema.extend({
|
||||
export const apiUpdateGithub = z.object({
|
||||
githubId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
gitProviderId: z.string().min(1),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { integer, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { gitProvider } from "./git-provider";
|
||||
@@ -11,6 +10,7 @@ export const gitlab = pgTable("gitlab", {
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
gitlabUrl: text("gitlabUrl").default("https://gitlab.com").notNull(),
|
||||
gitlabInternalUrl: text("gitlabInternalUrl"),
|
||||
applicationId: text("application_id"),
|
||||
redirectUri: text("redirect_uri"),
|
||||
secret: text("secret"),
|
||||
@@ -30,9 +30,7 @@ export const gitlabProviderRelations = relations(gitlab, ({ one }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(gitlab);
|
||||
|
||||
export const apiCreateGitlab = createSchema.extend({
|
||||
export const apiCreateGitlab = z.object({
|
||||
applicationId: z.string().optional(),
|
||||
secret: z.string().optional(),
|
||||
groupName: z.string().optional(),
|
||||
@@ -41,19 +39,17 @@ export const apiCreateGitlab = createSchema.extend({
|
||||
authId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
gitlabUrl: z.string().min(1),
|
||||
gitlabInternalUrl: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
export const apiFindOneGitlab = createSchema
|
||||
.extend({
|
||||
gitlabId: z.string().min(1),
|
||||
})
|
||||
.pick({ gitlabId: true });
|
||||
export const apiFindOneGitlab = z.object({
|
||||
gitlabId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiGitlabTestConnection = createSchema
|
||||
.extend({
|
||||
groupName: z.string().optional(),
|
||||
})
|
||||
.pick({ gitlabId: true, groupName: true });
|
||||
export const apiGitlabTestConnection = z.object({
|
||||
gitlabId: z.string().min(1),
|
||||
groupName: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiFindGitlabBranches = z.object({
|
||||
id: z.number().optional(),
|
||||
@@ -62,7 +58,7 @@ export const apiFindGitlabBranches = z.object({
|
||||
gitlabId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiUpdateGitlab = createSchema.extend({
|
||||
export const apiUpdateGitlab = z.object({
|
||||
applicationId: z.string().optional(),
|
||||
secret: z.string().optional(),
|
||||
groupName: z.string().optional(),
|
||||
@@ -70,4 +66,6 @@ export const apiUpdateGitlab = createSchema.extend({
|
||||
name: z.string().min(1),
|
||||
gitlabId: z.string().min(1),
|
||||
gitlabUrl: z.string().min(1),
|
||||
gitProviderId: z.string().min(1),
|
||||
gitlabInternalUrl: z.string().optional().nullable(),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./account";
|
||||
export * from "./ai";
|
||||
export * from "./application";
|
||||
export * from "./audit-log";
|
||||
export * from "./backups";
|
||||
export * from "./bitbucket";
|
||||
export * from "./certificate";
|
||||
@@ -13,11 +14,13 @@ export * from "./git-provider";
|
||||
export * from "./gitea";
|
||||
export * from "./github";
|
||||
export * from "./gitlab";
|
||||
export * from "./libsql";
|
||||
export * from "./mariadb";
|
||||
export * from "./mongo";
|
||||
export * from "./mount";
|
||||
export * from "./mysql";
|
||||
export * from "./notification";
|
||||
export * from "./patch";
|
||||
export * from "./port";
|
||||
export * from "./postgres";
|
||||
export * from "./preview-deployments";
|
||||
@@ -32,6 +35,9 @@ export * from "./server";
|
||||
export * from "./session";
|
||||
export * from "./shared";
|
||||
export * from "./ssh-key";
|
||||
export * from "./sso";
|
||||
export * from "./tag";
|
||||
export * from "./user";
|
||||
export * from "./utils";
|
||||
export * from "./volume-backups";
|
||||
export * from "./web-server-settings";
|
||||
|
||||
247
packages/server/src/db/schema/libsql.ts
Normal file
247
packages/server/src/db/schema/libsql.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
boolean,
|
||||
integer,
|
||||
json,
|
||||
pgTable,
|
||||
text,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { backups } from "./backups";
|
||||
import { environments } from "./environment";
|
||||
import { mounts } from "./mount";
|
||||
import { server } from "./server";
|
||||
import {
|
||||
applicationStatus,
|
||||
type EndpointSpecSwarm,
|
||||
EndpointSpecSwarmSchema,
|
||||
type HealthCheckSwarm,
|
||||
HealthCheckSwarmSchema,
|
||||
type LabelsSwarm,
|
||||
LabelsSwarmSchema,
|
||||
type NetworkSwarm,
|
||||
NetworkSwarmSchema,
|
||||
type PlacementSwarm,
|
||||
PlacementSwarmSchema,
|
||||
type RestartPolicySwarm,
|
||||
RestartPolicySwarmSchema,
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
sqldNode,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { generateAppName } from "./utils";
|
||||
|
||||
export const libsql = pgTable("libsql", {
|
||||
libsqlId: text("libsqlId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
appName: text("appName")
|
||||
.notNull()
|
||||
.$defaultFn(() => generateAppName("libsql"))
|
||||
.unique(),
|
||||
description: text("description"),
|
||||
databaseUser: text("databaseUser").notNull(),
|
||||
databasePassword: text("databasePassword").notNull(),
|
||||
sqldNode: sqldNode("sqldNode").notNull().default("primary"),
|
||||
sqldPrimaryUrl: text("sqldPrimaryUrl"),
|
||||
enableNamespaces: boolean("enableNamespaces").notNull().default(false),
|
||||
dockerImage: text("dockerImage").notNull(),
|
||||
command: text("command"),
|
||||
env: text("env"),
|
||||
// RESOURCES
|
||||
memoryReservation: text("memoryReservation"),
|
||||
memoryLimit: text("memoryLimit"),
|
||||
cpuReservation: text("cpuReservation"),
|
||||
cpuLimit: text("cpuLimit"),
|
||||
//
|
||||
externalPort: integer("externalPort"),
|
||||
externalGRPCPort: integer("externalGRPCPort"),
|
||||
externalAdminPort: integer("externalAdminPort"),
|
||||
applicationStatus: applicationStatus("applicationStatus")
|
||||
.notNull()
|
||||
.default("idle"),
|
||||
healthCheckSwarm: json("healthCheckSwarm").$type<HealthCheckSwarm>(),
|
||||
restartPolicySwarm: json("restartPolicySwarm").$type<RestartPolicySwarm>(),
|
||||
placementSwarm: json("placementSwarm").$type<PlacementSwarm>(),
|
||||
updateConfigSwarm: json("updateConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||
rollbackConfigSwarm: json("rollbackConfigSwarm").$type<UpdateConfigSwarm>(),
|
||||
modeSwarm: json("modeSwarm").$type<ServiceModeSwarm>(),
|
||||
labelsSwarm: json("labelsSwarm").$type<LabelsSwarm>(),
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
|
||||
environmentId: text("environmentId")
|
||||
.notNull()
|
||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
||||
serverId: text("serverId").references(() => server.serverId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
});
|
||||
|
||||
export const libsqlRelations = relations(libsql, ({ one, many }) => ({
|
||||
environment: one(environments, {
|
||||
fields: [libsql.environmentId],
|
||||
references: [environments.environmentId],
|
||||
}),
|
||||
backups: many(backups),
|
||||
mounts: many(mounts),
|
||||
server: one(server, {
|
||||
fields: [libsql.serverId],
|
||||
references: [server.serverId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(libsql, {
|
||||
libsqlId: z.string(),
|
||||
name: z.string().min(1),
|
||||
appName: z.string().min(1),
|
||||
createdAt: z.string(),
|
||||
databaseUser: z.string().min(1),
|
||||
databasePassword: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||
message:
|
||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
||||
}),
|
||||
sqldNode: z.enum(sqldNode.enumValues),
|
||||
sqldPrimaryUrl: z.string().nullable(),
|
||||
enableNamespaces: z.boolean().default(false),
|
||||
dockerImage: z
|
||||
.string()
|
||||
.default("ghcr.io/tursodatabase/libsql-server:v0.24.32"),
|
||||
command: z.string().optional(),
|
||||
env: z.string().optional(),
|
||||
memoryReservation: z.string().optional(),
|
||||
memoryLimit: z.string().optional(),
|
||||
cpuReservation: z.string().optional(),
|
||||
cpuLimit: z.string().optional(),
|
||||
environmentId: z.string(),
|
||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||
externalPort: z.number(),
|
||||
externalGRPCPort: z.number(),
|
||||
externalAdminPort: z.number(),
|
||||
description: z.string().optional(),
|
||||
serverId: z.string().optional(),
|
||||
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
|
||||
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
|
||||
placementSwarm: PlacementSwarmSchema.nullable(),
|
||||
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
|
||||
modeSwarm: ServiceModeSwarmSchema.nullable(),
|
||||
labelsSwarm: LabelsSwarmSchema.nullable(),
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreateLibsql = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
sqldNode: true,
|
||||
sqldPrimaryUrl: true,
|
||||
enableNamespaces: true,
|
||||
serverId: true,
|
||||
})
|
||||
.required()
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.sqldNode === "replica" && !data.sqldPrimaryUrl) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["sqldPrimaryUrl"],
|
||||
message: "sqldPrimaryUrl is required when sqldNode is 'replica'.",
|
||||
});
|
||||
}
|
||||
if (data.sqldNode !== "replica" && data.sqldPrimaryUrl) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ["sqldPrimaryUrl"],
|
||||
message:
|
||||
"sqldPrimaryUrl should not be provided when sqldNode is not 'replica'.",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const apiFindOneLibsql = z.object({
|
||||
libsqlId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiChangeLibsqlStatus = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
applicationStatus: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiSaveEnvironmentVariablesLibsql = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
env: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiSaveExternalPortsLibsql = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
externalPort: true,
|
||||
externalGRPCPort: true,
|
||||
externalAdminPort: true,
|
||||
})
|
||||
.required({ libsqlId: true })
|
||||
.superRefine((data, ctx) => {
|
||||
if (
|
||||
data.externalPort === null &&
|
||||
data.externalGRPCPort === null &&
|
||||
data.externalAdminPort === null
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message:
|
||||
"Either externalPort, externalGRPCPort or externalAdminPort must be provided.",
|
||||
path: ["externalPort", "externalGRPCPort", "externalAdminPort"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const apiDeployLibsql = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiResetLibsql = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
appName: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateLibsql = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
libsqlId: z.string().min(1),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
|
||||
export const apiRebuildLibsql = createSchema
|
||||
.pick({
|
||||
libsqlId: true,
|
||||
})
|
||||
.required();
|
||||
@@ -23,10 +23,12 @@ import {
|
||||
RestartPolicySwarmSchema,
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
type UlimitsSwarm,
|
||||
UlimitsSwarmSchema,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
|
||||
export const mariadb = pgTable("mariadb", {
|
||||
mariadbId: text("mariadbId")
|
||||
@@ -45,6 +47,7 @@ export const mariadb = pgTable("mariadb", {
|
||||
databaseRootPassword: text("rootPassword").notNull(),
|
||||
dockerImage: text("dockerImage").notNull(),
|
||||
command: text("command"),
|
||||
args: text("args").array(),
|
||||
env: text("env"),
|
||||
// RESOURCES
|
||||
memoryReservation: text("memoryReservation"),
|
||||
@@ -66,6 +69,7 @@ export const mariadb = pgTable("mariadb", {
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
ulimitsSwarm: json("ulimitsSwarm").$type<UlimitsSwarm>(),
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
@@ -95,7 +99,12 @@ export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
|
||||
const createSchema = createInsertSchema(mariadb, {
|
||||
mariadbId: z.string(),
|
||||
name: z.string().min(1),
|
||||
appName: z.string().min(1),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
createdAt: z.string(),
|
||||
databaseName: z.string().min(1),
|
||||
databaseUser: z.string().min(1),
|
||||
@@ -114,6 +123,7 @@ const createSchema = createInsertSchema(mariadb, {
|
||||
.optional(),
|
||||
dockerImage: z.string().default("mariadb:6"),
|
||||
command: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.string().optional(),
|
||||
memoryReservation: z.string().optional(),
|
||||
memoryLimit: z.string().optional(),
|
||||
@@ -134,28 +144,25 @@ const createSchema = createInsertSchema(mariadb, {
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreateMariaDB = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
databaseRootPassword: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseName: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
serverId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiCreateMariaDB = createSchema.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
databaseRootPassword: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseName: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
serverId: true,
|
||||
});
|
||||
|
||||
export const apiFindOneMariaDB = createSchema
|
||||
.pick({
|
||||
mariadbId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneMariaDB = z.object({
|
||||
mariadbId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiChangeMariaDBStatus = createSchema
|
||||
.pick({
|
||||
@@ -195,6 +202,7 @@ export const apiUpdateMariaDB = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
mariadbId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
|
||||
|
||||
@@ -30,10 +30,12 @@ import {
|
||||
RestartPolicySwarmSchema,
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
type UlimitsSwarm,
|
||||
UlimitsSwarmSchema,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
|
||||
export const mongo = pgTable("mongo", {
|
||||
mongoId: text("mongoId")
|
||||
@@ -50,6 +52,7 @@ export const mongo = pgTable("mongo", {
|
||||
databasePassword: text("databasePassword").notNull(),
|
||||
dockerImage: text("dockerImage").notNull(),
|
||||
command: text("command"),
|
||||
args: text("args").array(),
|
||||
env: text("env"),
|
||||
memoryReservation: text("memoryReservation"),
|
||||
memoryLimit: text("memoryLimit"),
|
||||
@@ -69,6 +72,7 @@ export const mongo = pgTable("mongo", {
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
ulimitsSwarm: json("ulimitsSwarm").$type<UlimitsSwarm>(),
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
@@ -97,7 +101,12 @@ export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(mongo, {
|
||||
appName: z.string().min(1),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
createdAt: z.string(),
|
||||
mongoId: z.string(),
|
||||
name: z.string().min(1),
|
||||
@@ -110,6 +119,7 @@ const createSchema = createInsertSchema(mongo, {
|
||||
databaseUser: z.string().min(1),
|
||||
dockerImage: z.string().default("mongo:15"),
|
||||
command: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.string().optional(),
|
||||
memoryReservation: z.string().optional(),
|
||||
memoryLimit: z.string().optional(),
|
||||
@@ -131,27 +141,24 @@ const createSchema = createInsertSchema(mongo, {
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreateMongo = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
serverId: true,
|
||||
replicaSets: true,
|
||||
})
|
||||
.required();
|
||||
export const apiCreateMongo = createSchema.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
serverId: true,
|
||||
replicaSets: true,
|
||||
});
|
||||
|
||||
export const apiFindOneMongo = createSchema
|
||||
.pick({
|
||||
mongoId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneMongo = z.object({
|
||||
mongoId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiChangeMongoStatus = createSchema
|
||||
.pick({
|
||||
@@ -184,6 +191,7 @@ export const apiUpdateMongo = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
mongoId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
import { libsql } from "./libsql";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { mysql } from "./mysql";
|
||||
@@ -19,8 +20,11 @@ export const serviceType = pgEnum("serviceType", [
|
||||
"mongo",
|
||||
"redis",
|
||||
"compose",
|
||||
"libsql",
|
||||
]);
|
||||
|
||||
export type ServiceType = (typeof serviceType.enumValues)[number];
|
||||
|
||||
export const mountType = pgEnum("mountType", ["bind", "volume", "file"]);
|
||||
|
||||
export const mounts = pgTable("mount", {
|
||||
@@ -39,7 +43,10 @@ export const mounts = pgTable("mount", {
|
||||
() => applications.applicationId,
|
||||
{ onDelete: "cascade" },
|
||||
),
|
||||
postgresId: text("postgresId").references(() => postgres.postgresId, {
|
||||
composeId: text("composeId").references(() => compose.composeId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
libsqlId: text("libsqlId").references(() => libsql.libsqlId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
mariadbId: text("mariadbId").references(() => mariadb.mariadbId, {
|
||||
@@ -51,10 +58,10 @@ export const mounts = pgTable("mount", {
|
||||
mysqlId: text("mysqlId").references(() => mysql.mysqlId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
redisId: text("redisId").references(() => redis.redisId, {
|
||||
postgresId: text("postgresId").references(() => postgres.postgresId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
composeId: text("composeId").references(() => compose.composeId, {
|
||||
redisId: text("redisId").references(() => redis.redisId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
});
|
||||
@@ -64,9 +71,13 @@ export const MountssRelations = relations(mounts, ({ one }) => ({
|
||||
fields: [mounts.applicationId],
|
||||
references: [applications.applicationId],
|
||||
}),
|
||||
postgres: one(postgres, {
|
||||
fields: [mounts.postgresId],
|
||||
references: [postgres.postgresId],
|
||||
compose: one(compose, {
|
||||
fields: [mounts.composeId],
|
||||
references: [compose.composeId],
|
||||
}),
|
||||
libsql: one(libsql, {
|
||||
fields: [mounts.libsqlId],
|
||||
references: [libsql.libsqlId],
|
||||
}),
|
||||
mariadb: one(mariadb, {
|
||||
fields: [mounts.mariadbId],
|
||||
@@ -80,14 +91,14 @@ export const MountssRelations = relations(mounts, ({ one }) => ({
|
||||
fields: [mounts.mysqlId],
|
||||
references: [mysql.mysqlId],
|
||||
}),
|
||||
postgres: one(postgres, {
|
||||
fields: [mounts.postgresId],
|
||||
references: [postgres.postgresId],
|
||||
}),
|
||||
redis: one(redis, {
|
||||
fields: [mounts.redisId],
|
||||
references: [redis.redisId],
|
||||
}),
|
||||
compose: one(compose, {
|
||||
fields: [mounts.composeId],
|
||||
references: [compose.composeId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(mounts, {
|
||||
@@ -99,23 +110,18 @@ const createSchema = createInsertSchema(mounts, {
|
||||
mountPath: z.string().min(1),
|
||||
mountId: z.string().optional(),
|
||||
filePath: z.string().optional(),
|
||||
serviceType: z
|
||||
.enum([
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis",
|
||||
"compose",
|
||||
])
|
||||
.default("application"),
|
||||
serviceType: z.enum([
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis",
|
||||
"compose",
|
||||
"libsql",
|
||||
]),
|
||||
});
|
||||
|
||||
export type ServiceType = NonNullable<
|
||||
z.infer<typeof createSchema>["serviceType"]
|
||||
>;
|
||||
|
||||
export const apiCreateMount = createSchema
|
||||
.pick({
|
||||
type: true,
|
||||
@@ -123,18 +129,16 @@ export const apiCreateMount = createSchema
|
||||
volumeName: true,
|
||||
content: true,
|
||||
mountPath: true,
|
||||
serviceType: true,
|
||||
filePath: true,
|
||||
serviceType: true,
|
||||
})
|
||||
.extend({
|
||||
serviceId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindOneMount = createSchema
|
||||
.pick({
|
||||
mountId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneMount = z.object({
|
||||
mountId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveMount = createSchema
|
||||
.pick({
|
||||
@@ -145,15 +149,13 @@ export const apiRemoveMount = createSchema
|
||||
// })
|
||||
.required();
|
||||
|
||||
export const apiFindMountByApplicationId = createSchema
|
||||
.extend({
|
||||
serviceId: z.string().min(1),
|
||||
})
|
||||
.pick({
|
||||
serviceId: true,
|
||||
serviceType: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindMountByApplicationId = z.object({
|
||||
serviceType: z
|
||||
.string()
|
||||
.min(1)
|
||||
.transform((val) => val as ServiceType),
|
||||
serviceId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateMount = createSchema.partial().extend({
|
||||
mountId: z.string().min(1),
|
||||
|
||||
@@ -23,10 +23,12 @@ import {
|
||||
RestartPolicySwarmSchema,
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
type UlimitsSwarm,
|
||||
UlimitsSwarmSchema,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
|
||||
export const mysql = pgTable("mysql", {
|
||||
mysqlId: text("mysqlId")
|
||||
@@ -45,6 +47,7 @@ export const mysql = pgTable("mysql", {
|
||||
databaseRootPassword: text("rootPassword").notNull(),
|
||||
dockerImage: text("dockerImage").notNull(),
|
||||
command: text("command"),
|
||||
args: text("args").array(),
|
||||
env: text("env"),
|
||||
memoryReservation: text("memoryReservation"),
|
||||
memoryLimit: text("memoryLimit"),
|
||||
@@ -64,6 +67,7 @@ export const mysql = pgTable("mysql", {
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
ulimitsSwarm: json("ulimitsSwarm").$type<UlimitsSwarm>(),
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
@@ -92,7 +96,12 @@ export const mysqlRelations = relations(mysql, ({ one, many }) => ({
|
||||
|
||||
const createSchema = createInsertSchema(mysql, {
|
||||
mysqlId: z.string(),
|
||||
appName: z.string().min(1),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
createdAt: z.string(),
|
||||
name: z.string().min(1),
|
||||
databaseName: z.string().min(1),
|
||||
@@ -112,6 +121,7 @@ const createSchema = createInsertSchema(mysql, {
|
||||
.optional(),
|
||||
dockerImage: z.string().default("mysql:8"),
|
||||
command: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.string().optional(),
|
||||
memoryReservation: z.string().optional(),
|
||||
memoryLimit: z.string().optional(),
|
||||
@@ -131,28 +141,25 @@ const createSchema = createInsertSchema(mysql, {
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreateMySql = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseName: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
databaseRootPassword: true,
|
||||
serverId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiCreateMySql = createSchema.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
databaseName: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
databaseRootPassword: true,
|
||||
serverId: true,
|
||||
});
|
||||
|
||||
export const apiFindOneMySql = createSchema
|
||||
.pick({
|
||||
mysqlId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneMySql = z.object({
|
||||
mysqlId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiChangeMySqlStatus = createSchema
|
||||
.pick({
|
||||
@@ -192,6 +199,7 @@ export const apiUpdateMySql = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
mysqlId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
text,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -10,9 +17,14 @@ export const notificationType = pgEnum("notificationType", [
|
||||
"telegram",
|
||||
"discord",
|
||||
"email",
|
||||
"resend",
|
||||
"gotify",
|
||||
"ntfy",
|
||||
"mattermost",
|
||||
"pushover",
|
||||
"custom",
|
||||
"lark",
|
||||
"teams",
|
||||
]);
|
||||
|
||||
export const notifications = pgTable("notification", {
|
||||
@@ -24,7 +36,9 @@ export const notifications = pgTable("notification", {
|
||||
appDeploy: boolean("appDeploy").notNull().default(false),
|
||||
appBuildError: boolean("appBuildError").notNull().default(false),
|
||||
databaseBackup: boolean("databaseBackup").notNull().default(false),
|
||||
volumeBackup: boolean("volumeBackup").notNull().default(false),
|
||||
dokployRestart: boolean("dokployRestart").notNull().default(false),
|
||||
dokployBackup: boolean("dokployBackup").notNull().default(false),
|
||||
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
|
||||
serverThreshold: boolean("serverThreshold").notNull().default(false),
|
||||
notificationType: notificationType("notificationType").notNull(),
|
||||
@@ -43,15 +57,30 @@ export const notifications = pgTable("notification", {
|
||||
emailId: text("emailId").references(() => email.emailId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
resendId: text("resendId").references(() => resend.resendId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
mattermostId: text("mattermostId").references(() => mattermost.mattermostId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
customId: text("customId").references(() => custom.customId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
larkId: text("larkId").references(() => lark.larkId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
pushoverId: text("pushoverId").references(() => pushover.pushoverId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
teamsId: text("teamsId").references(() => teams.teamsId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
organizationId: text("organizationId")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
@@ -98,6 +127,16 @@ export const email = pgTable("email", {
|
||||
toAddresses: text("toAddress").array().notNull(),
|
||||
});
|
||||
|
||||
export const resend = pgTable("resend", {
|
||||
resendId: text("resendId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
apiKey: text("apiKey").notNull(),
|
||||
fromAddress: text("fromAddress").notNull(),
|
||||
toAddresses: text("toAddress").array().notNull(),
|
||||
});
|
||||
|
||||
export const gotify = pgTable("gotify", {
|
||||
gotifyId: text("gotifyId")
|
||||
.notNull()
|
||||
@@ -116,10 +155,29 @@ export const ntfy = pgTable("ntfy", {
|
||||
.$defaultFn(() => nanoid()),
|
||||
serverUrl: text("serverUrl").notNull(),
|
||||
topic: text("topic").notNull(),
|
||||
accessToken: text("accessToken").notNull(),
|
||||
accessToken: text("accessToken"),
|
||||
priority: integer("priority").notNull().default(3),
|
||||
});
|
||||
|
||||
export const mattermost = pgTable("mattermost", {
|
||||
mattermostId: text("mattermostId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
webhookUrl: text("webhookUrl").notNull(),
|
||||
channel: text("channel"),
|
||||
username: text("username"),
|
||||
});
|
||||
|
||||
export const custom = pgTable("custom", {
|
||||
customId: text("customId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
endpoint: text("endpoint").notNull(),
|
||||
headers: jsonb("headers").$type<Record<string, string>>(),
|
||||
});
|
||||
|
||||
export const lark = pgTable("lark", {
|
||||
larkId: text("larkId")
|
||||
.notNull()
|
||||
@@ -128,6 +186,26 @@ export const lark = pgTable("lark", {
|
||||
webhookUrl: text("webhookUrl").notNull(),
|
||||
});
|
||||
|
||||
export const pushover = pgTable("pushover", {
|
||||
pushoverId: text("pushoverId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
userKey: text("userKey").notNull(),
|
||||
apiToken: text("apiToken").notNull(),
|
||||
priority: integer("priority").notNull().default(0),
|
||||
retry: integer("retry"),
|
||||
expire: integer("expire"),
|
||||
});
|
||||
|
||||
export const teams = pgTable("teams", {
|
||||
teamsId: text("teamsId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
webhookUrl: text("webhookUrl").notNull(),
|
||||
});
|
||||
|
||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
slack: one(slack, {
|
||||
fields: [notifications.slackId],
|
||||
@@ -145,6 +223,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
fields: [notifications.emailId],
|
||||
references: [email.emailId],
|
||||
}),
|
||||
resend: one(resend, {
|
||||
fields: [notifications.resendId],
|
||||
references: [resend.resendId],
|
||||
}),
|
||||
gotify: one(gotify, {
|
||||
fields: [notifications.gotifyId],
|
||||
references: [gotify.gotifyId],
|
||||
@@ -153,10 +235,26 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
fields: [notifications.ntfyId],
|
||||
references: [ntfy.ntfyId],
|
||||
}),
|
||||
mattermost: one(mattermost, {
|
||||
fields: [notifications.mattermostId],
|
||||
references: [mattermost.mattermostId],
|
||||
}),
|
||||
custom: one(custom, {
|
||||
fields: [notifications.customId],
|
||||
references: [custom.customId],
|
||||
}),
|
||||
lark: one(lark, {
|
||||
fields: [notifications.larkId],
|
||||
references: [lark.larkId],
|
||||
}),
|
||||
pushover: one(pushover, {
|
||||
fields: [notifications.pushoverId],
|
||||
references: [pushover.pushoverId],
|
||||
}),
|
||||
teams: one(teams, {
|
||||
fields: [notifications.teamsId],
|
||||
references: [teams.teamsId],
|
||||
}),
|
||||
organization: one(organization, {
|
||||
fields: [notifications.organizationId],
|
||||
references: [organization.id],
|
||||
@@ -169,6 +267,8 @@ export const apiCreateSlack = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -196,6 +296,8 @@ export const apiCreateTelegram = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -225,6 +327,8 @@ export const apiCreateDiscord = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -255,6 +359,8 @@ export const apiCreateEmail = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -286,10 +392,43 @@ export const apiTestEmailConnection = apiCreateEmail.pick({
|
||||
fromAddress: true,
|
||||
});
|
||||
|
||||
export const apiCreateResend = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
})
|
||||
.extend({
|
||||
apiKey: z.string().min(1),
|
||||
fromAddress: z.string().min(1),
|
||||
toAddresses: z.array(z.string()).min(1),
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateResend = apiCreateResend.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
resendId: z.string().min(1),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestResendConnection = apiCreateResend.pick({
|
||||
apiKey: true,
|
||||
fromAddress: true,
|
||||
toAddresses: true,
|
||||
});
|
||||
|
||||
export const apiCreateGotify = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -323,6 +462,8 @@ export const apiCreateNtfy = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -331,7 +472,7 @@ export const apiCreateNtfy = notificationsSchema
|
||||
.extend({
|
||||
serverUrl: z.string().min(1),
|
||||
topic: z.string().min(1),
|
||||
accessToken: z.string().min(1),
|
||||
accessToken: z.string().optional(),
|
||||
priority: z.number().min(1),
|
||||
})
|
||||
.required();
|
||||
@@ -349,16 +490,91 @@ export const apiTestNtfyConnection = apiCreateNtfy.pick({
|
||||
priority: true,
|
||||
});
|
||||
|
||||
export const apiFindOneNotification = notificationsSchema
|
||||
export const apiCreateMattermost = notificationsSchema
|
||||
.pick({
|
||||
notificationId: true,
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
})
|
||||
.required();
|
||||
.extend({
|
||||
webhookUrl: z.string().url(),
|
||||
channel: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
})
|
||||
.required({
|
||||
name: true,
|
||||
webhookUrl: true,
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
});
|
||||
|
||||
export const apiUpdateMattermost = apiCreateMattermost.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
mattermostId: z.string().min(1),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestMattermostConnection = apiCreateMattermost
|
||||
.pick({
|
||||
webhookUrl: true,
|
||||
channel: true,
|
||||
username: true,
|
||||
})
|
||||
.extend({
|
||||
channel: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiFindOneNotification = z.object({
|
||||
notificationId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiCreateCustom = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
})
|
||||
.extend({
|
||||
endpoint: z.string().min(1),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
});
|
||||
|
||||
export const apiUpdateCustom = apiCreateCustom.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
customId: z.string().min(1),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestCustomConnection = z.object({
|
||||
endpoint: z.string().min(1),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
});
|
||||
|
||||
export const apiCreateLark = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
@@ -380,6 +596,98 @@ export const apiTestLarkConnection = apiCreateLark.pick({
|
||||
webhookUrl: true,
|
||||
});
|
||||
|
||||
export const apiCreateTeams = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
})
|
||||
.extend({
|
||||
webhookUrl: z.string().min(1),
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateTeams = apiCreateTeams.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
teamsId: z.string().min(1),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestTeamsConnection = apiCreateTeams.pick({
|
||||
webhookUrl: true,
|
||||
});
|
||||
|
||||
export const apiCreatePushover = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployBackup: true,
|
||||
volumeBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
})
|
||||
.extend({
|
||||
userKey: z.string().min(1),
|
||||
apiToken: z.string().min(1),
|
||||
priority: z.number().min(-2).max(2).default(0),
|
||||
retry: z.number().min(30).nullish(),
|
||||
expire: z.number().min(1).max(10800).nullish(),
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
data.priority !== 2 || (data.retry != null && data.expire != null),
|
||||
{
|
||||
message: "Retry and expire are required for emergency priority (2)",
|
||||
path: ["retry"],
|
||||
},
|
||||
);
|
||||
|
||||
export const apiUpdatePushover = z.object({
|
||||
notificationId: z.string().min(1),
|
||||
pushoverId: z.string().min(1),
|
||||
organizationId: z.string().optional(),
|
||||
userKey: z.string().min(1).optional(),
|
||||
apiToken: z.string().min(1).optional(),
|
||||
priority: z.number().min(-2).max(2).optional(),
|
||||
retry: z.number().min(30).nullish(),
|
||||
expire: z.number().min(1).max(10800).nullish(),
|
||||
appBuildError: z.boolean().optional(),
|
||||
databaseBackup: z.boolean().optional(),
|
||||
dokployBackup: z.boolean().optional(),
|
||||
volumeBackup: z.boolean().optional(),
|
||||
dokployRestart: z.boolean().optional(),
|
||||
name: z.string().optional(),
|
||||
appDeploy: z.boolean().optional(),
|
||||
dockerCleanup: z.boolean().optional(),
|
||||
serverThreshold: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiTestPushoverConnection = z
|
||||
.object({
|
||||
userKey: z.string().min(1),
|
||||
apiToken: z.string().min(1),
|
||||
priority: z.number().min(-2).max(2),
|
||||
retry: z.number().min(30).nullish(),
|
||||
expire: z.number().min(1).max(10800).nullish(),
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
data.priority !== 2 || (data.retry != null && data.expire != null),
|
||||
{
|
||||
message: "Retry and expire are required for emergency priority (2)",
|
||||
path: ["retry"],
|
||||
},
|
||||
);
|
||||
|
||||
export const apiSendTest = notificationsSchema
|
||||
.extend({
|
||||
botToken: z.string(),
|
||||
@@ -392,10 +700,13 @@ export const apiSendTest = notificationsSchema
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
toAddresses: z.array(z.string()),
|
||||
apiKey: z.string(),
|
||||
serverUrl: z.string(),
|
||||
topic: z.string(),
|
||||
appToken: z.string(),
|
||||
accessToken: z.string(),
|
||||
accessToken: z.string().optional(),
|
||||
priority: z.number(),
|
||||
endpoint: z.string(),
|
||||
headers: z.string(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
100
packages/server/src/db/schema/patch.ts
Normal file
100
packages/server/src/db/schema/patch.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, pgEnum, pgTable, text, unique } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
|
||||
export const patchType = pgEnum("patchType", ["create", "update", "delete"]);
|
||||
|
||||
export const patch = pgTable(
|
||||
"patch",
|
||||
{
|
||||
patchId: text("patchId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
type: patchType("type").notNull().default("update"),
|
||||
filePath: text("filePath").notNull(),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
content: text("content").notNull(),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
updatedAt: text("updatedAt").$defaultFn(() => new Date().toISOString()),
|
||||
// Relations - one of these must be set
|
||||
applicationId: text("applicationId").references(
|
||||
() => applications.applicationId,
|
||||
{ onDelete: "cascade" },
|
||||
),
|
||||
composeId: text("composeId").references(() => compose.composeId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
},
|
||||
(table) => [
|
||||
// Unique constraint: one patch per file per application/compose
|
||||
unique("patch_filepath_application_unique").on(
|
||||
table.filePath,
|
||||
table.applicationId,
|
||||
),
|
||||
unique("patch_filepath_compose_unique").on(table.filePath, table.composeId),
|
||||
],
|
||||
);
|
||||
|
||||
export const patchRelations = relations(patch, ({ one }) => ({
|
||||
application: one(applications, {
|
||||
fields: [patch.applicationId],
|
||||
references: [applications.applicationId],
|
||||
}),
|
||||
compose: one(compose, {
|
||||
fields: [patch.composeId],
|
||||
references: [compose.composeId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(patch, {
|
||||
filePath: z.string().min(1),
|
||||
content: z.string(),
|
||||
type: z.enum(["create", "update", "delete"]).optional(),
|
||||
enabled: z.boolean().optional(),
|
||||
applicationId: z.string().optional(),
|
||||
composeId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiCreatePatch = createSchema.pick({
|
||||
filePath: true,
|
||||
content: true,
|
||||
type: true,
|
||||
enabled: true,
|
||||
applicationId: true,
|
||||
composeId: true,
|
||||
});
|
||||
|
||||
export const apiFindPatch = z.object({
|
||||
patchId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindPatchesByApplicationId = z.object({
|
||||
applicationId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindPatchesByComposeId = z.object({
|
||||
composeId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdatePatch = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
patchId: z.string().min(1),
|
||||
})
|
||||
.omit({ applicationId: true, composeId: true });
|
||||
|
||||
export const apiDeletePatch = z.object({
|
||||
patchId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiTogglePatchEnabled = z.object({
|
||||
patchId: z.string().min(1),
|
||||
enabled: z.boolean(),
|
||||
});
|
||||
@@ -49,11 +49,9 @@ export const apiCreatePort = createSchema
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiFindOnePort = createSchema
|
||||
.pick({
|
||||
portId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOnePort = z.object({
|
||||
portId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdatePort = createSchema
|
||||
.pick({
|
||||
|
||||
@@ -23,10 +23,12 @@ import {
|
||||
RestartPolicySwarmSchema,
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
type UlimitsSwarm,
|
||||
UlimitsSwarmSchema,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
|
||||
export const postgres = pgTable("postgres", {
|
||||
postgresId: text("postgresId")
|
||||
@@ -44,6 +46,7 @@ export const postgres = pgTable("postgres", {
|
||||
description: text("description"),
|
||||
dockerImage: text("dockerImage").notNull(),
|
||||
command: text("command"),
|
||||
args: text("args").array(),
|
||||
env: text("env"),
|
||||
memoryReservation: text("memoryReservation"),
|
||||
externalPort: integer("externalPort"),
|
||||
@@ -64,6 +67,7 @@ export const postgres = pgTable("postgres", {
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
ulimitsSwarm: json("ulimitsSwarm").$type<UlimitsSwarm>(),
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
@@ -93,6 +97,12 @@ export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
||||
const createSchema = createInsertSchema(postgres, {
|
||||
postgresId: z.string(),
|
||||
name: z.string().min(1),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
databasePassword: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
||||
@@ -101,8 +111,9 @@ const createSchema = createInsertSchema(postgres, {
|
||||
}),
|
||||
databaseName: z.string().min(1),
|
||||
databaseUser: z.string().min(1),
|
||||
dockerImage: z.string().default("postgres:15"),
|
||||
dockerImage: z.string().default("postgres:18"),
|
||||
command: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.string().optional(),
|
||||
memoryReservation: z.string().optional(),
|
||||
memoryLimit: z.string().optional(),
|
||||
@@ -124,27 +135,24 @@ const createSchema = createInsertSchema(postgres, {
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreatePostgres = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
databaseName: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiCreatePostgres = createSchema.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
databaseName: true,
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
});
|
||||
|
||||
export const apiFindOnePostgres = createSchema
|
||||
.pick({
|
||||
postgresId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOnePostgres = z.object({
|
||||
postgresId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiChangePostgresStatus = createSchema
|
||||
.pick({
|
||||
@@ -184,6 +192,7 @@ export const apiUpdatePostgres = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
postgresId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
|
||||
|
||||
@@ -58,17 +58,12 @@ export const createSchema = createInsertSchema(previewDeployments, {
|
||||
applicationId: z.string(),
|
||||
});
|
||||
|
||||
export const apiCreatePreviewDeployment = createSchema
|
||||
.pick({
|
||||
applicationId: true,
|
||||
domainId: true,
|
||||
branch: true,
|
||||
pullRequestId: true,
|
||||
pullRequestNumber: true,
|
||||
pullRequestURL: true,
|
||||
pullRequestTitle: true,
|
||||
})
|
||||
.extend({
|
||||
applicationId: z.string().min(1),
|
||||
// deploymentId: z.string().min(1),
|
||||
});
|
||||
export const apiCreatePreviewDeployment = z.object({
|
||||
applicationId: z.string().min(1),
|
||||
domainId: z.string().optional(),
|
||||
branch: z.string().min(1),
|
||||
pullRequestId: z.string().min(1),
|
||||
pullRequestNumber: z.string().min(1),
|
||||
pullRequestURL: z.string().min(1),
|
||||
pullRequestTitle: z.string().min(1),
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { organization } from "./account";
|
||||
import { environments } from "./environment";
|
||||
import { projectTags } from "./tag";
|
||||
|
||||
export const projects = pgTable("project", {
|
||||
projectId: text("projectId")
|
||||
@@ -25,6 +26,7 @@ export const projects = pgTable("project", {
|
||||
|
||||
export const projectRelations = relations(projects, ({ many, one }) => ({
|
||||
environments: many(environments),
|
||||
projectTags: many(projectTags),
|
||||
organization: one(organization, {
|
||||
fields: [projects.organizationId],
|
||||
references: [organization.id],
|
||||
@@ -43,12 +45,9 @@ export const apiCreateProject = createSchema.pick({
|
||||
env: true,
|
||||
});
|
||||
|
||||
export const apiFindOneProject = createSchema
|
||||
.pick({
|
||||
projectId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiFindOneProject = z.object({
|
||||
projectId: z.string().min(1),
|
||||
});
|
||||
export const apiRemoveProject = createSchema
|
||||
.pick({
|
||||
projectId: true,
|
||||
|
||||
@@ -35,11 +35,9 @@ const createSchema = createInsertSchema(redirects, {
|
||||
permanent: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiFindOneRedirect = createSchema
|
||||
.pick({
|
||||
redirectId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneRedirect = z.object({
|
||||
redirectId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiCreateRedirect = createSchema
|
||||
.pick({
|
||||
|
||||
@@ -22,10 +22,12 @@ import {
|
||||
RestartPolicySwarmSchema,
|
||||
type ServiceModeSwarm,
|
||||
ServiceModeSwarmSchema,
|
||||
type UlimitsSwarm,
|
||||
UlimitsSwarmSchema,
|
||||
type UpdateConfigSwarm,
|
||||
UpdateConfigSwarmSchema,
|
||||
} from "./shared";
|
||||
import { generateAppName } from "./utils";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX, generateAppName } from "./utils";
|
||||
|
||||
export const redis = pgTable("redis", {
|
||||
redisId: text("redisId")
|
||||
@@ -41,6 +43,7 @@ export const redis = pgTable("redis", {
|
||||
databasePassword: text("password").notNull(),
|
||||
dockerImage: text("dockerImage").notNull(),
|
||||
command: text("command"),
|
||||
args: text("args").array(),
|
||||
env: text("env"),
|
||||
memoryReservation: text("memoryReservation"),
|
||||
memoryLimit: text("memoryLimit"),
|
||||
@@ -63,6 +66,7 @@ export const redis = pgTable("redis", {
|
||||
networkSwarm: json("networkSwarm").$type<NetworkSwarm[]>(),
|
||||
stopGracePeriodSwarm: bigint("stopGracePeriodSwarm", { mode: "bigint" }),
|
||||
endpointSpecSwarm: json("endpointSpecSwarm").$type<EndpointSpecSwarm>(),
|
||||
ulimitsSwarm: json("ulimitsSwarm").$type<UlimitsSwarm>(),
|
||||
replicas: integer("replicas").default(1).notNull(),
|
||||
|
||||
environmentId: text("environmentId")
|
||||
@@ -87,12 +91,18 @@ export const redisRelations = relations(redis, ({ one, many }) => ({
|
||||
|
||||
const createSchema = createInsertSchema(redis, {
|
||||
redisId: z.string(),
|
||||
appName: z.string().min(1),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(63)
|
||||
.regex(APP_NAME_REGEX, APP_NAME_MESSAGE)
|
||||
.optional(),
|
||||
createdAt: z.string(),
|
||||
name: z.string().min(1),
|
||||
databasePassword: z.string(),
|
||||
dockerImage: z.string().default("redis:8"),
|
||||
command: z.string().optional(),
|
||||
args: z.array(z.string()).optional(),
|
||||
env: z.string().optional(),
|
||||
memoryReservation: z.string().optional(),
|
||||
memoryLimit: z.string().optional(),
|
||||
@@ -113,25 +123,22 @@ const createSchema = createInsertSchema(redis, {
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreateRedis = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
databasePassword: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiCreateRedis = createSchema.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
databasePassword: true,
|
||||
dockerImage: true,
|
||||
environmentId: true,
|
||||
description: true,
|
||||
serverId: true,
|
||||
});
|
||||
|
||||
export const apiFindOneRedis = createSchema
|
||||
.pick({
|
||||
redisId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneRedis = z.object({
|
||||
redisId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiChangeRedisStatus = createSchema
|
||||
.pick({
|
||||
@@ -171,6 +178,7 @@ export const apiUpdateRedis = createSchema
|
||||
.partial()
|
||||
.extend({
|
||||
redisId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
|
||||
|
||||
@@ -33,7 +33,15 @@ export const registry = pgTable("registry", {
|
||||
});
|
||||
|
||||
export const registryRelations = relations(registry, ({ many }) => ({
|
||||
applications: many(applications),
|
||||
applications: many(applications, {
|
||||
relationName: "applicationRegistry",
|
||||
}),
|
||||
buildApplications: many(applications, {
|
||||
relationName: "applicationBuildRegistry",
|
||||
}),
|
||||
rollbackApplications: many(applications, {
|
||||
relationName: "applicationRollbackRegistry",
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(registry, {
|
||||
@@ -72,17 +80,23 @@ export const apiTestRegistry = createSchema.pick({}).extend({
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestRegistryById = createSchema
|
||||
.pick({
|
||||
registryId: true,
|
||||
})
|
||||
.extend({
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiRemoveRegistry = createSchema
|
||||
.pick({
|
||||
registryId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiFindOneRegistry = createSchema
|
||||
.pick({
|
||||
registryId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneRegistry = z.object({
|
||||
registryId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateRegistry = createSchema.partial().extend({
|
||||
registryId: z.string().min(1),
|
||||
|
||||
@@ -49,6 +49,7 @@ export const schedules = pgTable("schedule", {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
timezone: text("timezone"),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
|
||||
@@ -5,17 +5,24 @@ enum applicationStatus {
|
||||
error
|
||||
}
|
||||
|
||||
enum backupType {
|
||||
database
|
||||
compose
|
||||
}
|
||||
|
||||
enum buildType {
|
||||
dockerfile
|
||||
heroku_buildpacks
|
||||
paketo_buildpacks
|
||||
nixpacks
|
||||
static
|
||||
railpack
|
||||
}
|
||||
|
||||
enum certificateType {
|
||||
letsencrypt
|
||||
none
|
||||
custom
|
||||
}
|
||||
|
||||
enum composeType {
|
||||
@@ -28,6 +35,7 @@ enum databaseType {
|
||||
mariadb
|
||||
mysql
|
||||
mongo
|
||||
"web-server"
|
||||
}
|
||||
|
||||
enum deploymentStatus {
|
||||
@@ -61,6 +69,8 @@ enum notificationType {
|
||||
discord
|
||||
email
|
||||
gotify
|
||||
ntfy
|
||||
custom
|
||||
}
|
||||
|
||||
enum protocolType {
|
||||
@@ -68,14 +78,21 @@ enum protocolType {
|
||||
udp
|
||||
}
|
||||
|
||||
enum publishModeType {
|
||||
ingress
|
||||
host
|
||||
}
|
||||
|
||||
enum RegistryType {
|
||||
selfHosted
|
||||
cloud
|
||||
}
|
||||
|
||||
enum Roles {
|
||||
admin
|
||||
user
|
||||
enum scheduleType {
|
||||
application
|
||||
compose
|
||||
server
|
||||
"dokploy-server"
|
||||
}
|
||||
|
||||
enum serverStatus {
|
||||
@@ -93,6 +110,11 @@ enum serviceType {
|
||||
compose
|
||||
}
|
||||
|
||||
enum shellType {
|
||||
bash
|
||||
sh
|
||||
}
|
||||
|
||||
enum sourceType {
|
||||
docker
|
||||
git
|
||||
@@ -112,6 +134,11 @@ enum sourceTypeCompose {
|
||||
raw
|
||||
}
|
||||
|
||||
enum triggerType {
|
||||
push
|
||||
tag
|
||||
}
|
||||
|
||||
table account {
|
||||
id text [pk, not null]
|
||||
account_id text [not null]
|
||||
@@ -133,7 +160,39 @@ table account {
|
||||
confirmationExpiresAt text
|
||||
}
|
||||
|
||||
table admin {
|
||||
table ai {
|
||||
aiId text [pk, not null]
|
||||
name text [not null]
|
||||
apiUrl text [not null]
|
||||
apiKey text [not null]
|
||||
model text [not null]
|
||||
isEnabled boolean [not null, default: true]
|
||||
organizationId text [not null]
|
||||
createdAt text [not null]
|
||||
}
|
||||
|
||||
table apikey {
|
||||
id text [pk, not null]
|
||||
name text
|
||||
start text
|
||||
prefix text
|
||||
key text [not null]
|
||||
user_id text [not null]
|
||||
refill_interval integer
|
||||
refill_amount integer
|
||||
last_refill_at timestamp
|
||||
enabled boolean
|
||||
rate_limit_enabled boolean
|
||||
rate_limit_time_window integer
|
||||
rate_limit_max integer
|
||||
request_count integer
|
||||
remaining integer
|
||||
last_request timestamp
|
||||
expires_at timestamp
|
||||
created_at timestamp [not null]
|
||||
updated_at timestamp [not null]
|
||||
permissions text
|
||||
metadata text
|
||||
}
|
||||
|
||||
table application {
|
||||
@@ -143,14 +202,19 @@ table application {
|
||||
description text
|
||||
env text
|
||||
previewEnv text
|
||||
watchPaths text[]
|
||||
previewBuildArgs text
|
||||
previewLabels text[]
|
||||
previewWildcard text
|
||||
previewPort integer [default: 3000]
|
||||
previewHttps boolean [not null, default: false]
|
||||
previewPath text [default: '/']
|
||||
certificateType certificateType [not null, default: 'none']
|
||||
previewCustomCertResolver text
|
||||
previewLimit integer [default: 3]
|
||||
isPreviewDeploymentsActive boolean [default: false]
|
||||
previewRequireCollaboratorPermissions boolean [default: true]
|
||||
rollbackActive boolean [default: false]
|
||||
buildArgs text
|
||||
memoryReservation text
|
||||
memoryLimit text
|
||||
@@ -167,6 +231,7 @@ table application {
|
||||
owner text
|
||||
branch text
|
||||
buildPath text [default: '/']
|
||||
triggerType triggerType [default: 'push']
|
||||
autoDeploy boolean
|
||||
gitlabProjectId integer
|
||||
gitlabRepository text
|
||||
@@ -174,6 +239,10 @@ table application {
|
||||
gitlabBranch text
|
||||
gitlabBuildPath text [default: '/']
|
||||
gitlabPathNamespace text
|
||||
giteaRepository text
|
||||
giteaOwner text
|
||||
giteaBranch text
|
||||
giteaBuildPath text [default: '/']
|
||||
bitbucketRepository text
|
||||
bitbucketOwner text
|
||||
bitbucketBranch text
|
||||
@@ -186,6 +255,7 @@ table application {
|
||||
customGitBranch text
|
||||
customGitBuildPath text
|
||||
customGitSSHKeyId text
|
||||
enableSubmodules boolean [not null, default: false]
|
||||
dockerfile text
|
||||
dockerContextPath text
|
||||
dockerBuildStage text
|
||||
@@ -201,52 +271,47 @@ table application {
|
||||
replicas integer [not null, default: 1]
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
buildType buildType [not null, default: 'nixpacks']
|
||||
railpackVersion text [default: '0.2.2']
|
||||
herokuVersion text [default: '24']
|
||||
publishDirectory text
|
||||
isStaticSpa boolean
|
||||
createdAt text [not null]
|
||||
registryId text
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
githubId text
|
||||
gitlabId text
|
||||
bitbucketId text
|
||||
giteaId 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]
|
||||
appName text [not null, unique]
|
||||
schedule text [not null]
|
||||
enabled boolean
|
||||
database text [not null]
|
||||
prefix text [not null]
|
||||
serviceName text
|
||||
destinationId text [not null]
|
||||
keepLatestCount integer
|
||||
backupType backupType [not null, default: 'database']
|
||||
databaseType databaseType [not null]
|
||||
composeId text
|
||||
postgresId text
|
||||
mariadbId text
|
||||
mysqlId text
|
||||
mongoId text
|
||||
userId text
|
||||
metadata jsonb
|
||||
}
|
||||
|
||||
table bitbucket {
|
||||
bitbucketId text [pk, not null]
|
||||
bitbucketUsername text
|
||||
bitbucketEmail text
|
||||
appPassword text
|
||||
apiToken text
|
||||
bitbucketWorkspaceName text
|
||||
gitProviderId text [not null]
|
||||
}
|
||||
@@ -258,7 +323,7 @@ table certificate {
|
||||
privateKey text [not null]
|
||||
certificatePath text [not null, unique]
|
||||
autoRenew boolean
|
||||
userId text
|
||||
organizationId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -291,13 +356,17 @@ table compose {
|
||||
customGitBranch text
|
||||
customGitSSHKeyId text
|
||||
command text [not null, default: '']
|
||||
enableSubmodules boolean [not null, default: false]
|
||||
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]
|
||||
isolatedDeploymentsVolume boolean [not null, default: false]
|
||||
triggerType triggerType [default: 'push']
|
||||
composeStatus applicationStatus [not null, default: 'idle']
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
createdAt text [not null]
|
||||
watchPaths text[]
|
||||
githubId text
|
||||
gitlabId text
|
||||
bitbucketId text
|
||||
@@ -305,19 +374,32 @@ table compose {
|
||||
serverId text
|
||||
}
|
||||
|
||||
table custom {
|
||||
customId text [pk, not null]
|
||||
endpoint text [not null]
|
||||
headers text
|
||||
}
|
||||
|
||||
table deployment {
|
||||
deploymentId text [pk, not null]
|
||||
title text [not null]
|
||||
description text
|
||||
status deploymentStatus [default: 'running']
|
||||
logPath text [not null]
|
||||
pid text
|
||||
applicationId text
|
||||
composeId text
|
||||
serverId text
|
||||
isPreviewDeployment boolean [default: false]
|
||||
previewDeploymentId text
|
||||
createdAt text [not null]
|
||||
startedAt text
|
||||
finishedAt text
|
||||
errorMessage text
|
||||
scheduleId text
|
||||
backupId text
|
||||
rollbackId text
|
||||
volumeBackupId text
|
||||
}
|
||||
|
||||
table destination {
|
||||
@@ -329,7 +411,8 @@ table destination {
|
||||
bucket text [not null]
|
||||
region text [not null]
|
||||
endpoint text [not null]
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
createdAt timestamp [not null, default: `now()`]
|
||||
}
|
||||
|
||||
table discord {
|
||||
@@ -349,9 +432,12 @@ table domain {
|
||||
uniqueConfigKey serial [not null, increment]
|
||||
createdAt text [not null]
|
||||
composeId text
|
||||
customCertResolver text
|
||||
applicationId text
|
||||
previewDeploymentId text
|
||||
certificateType certificateType [not null, default: 'none']
|
||||
internalPath text [default: '/']
|
||||
stripPath boolean [not null, default: false]
|
||||
}
|
||||
|
||||
table email {
|
||||
@@ -364,12 +450,37 @@ table email {
|
||||
toAddress text[] [not null]
|
||||
}
|
||||
|
||||
table environment {
|
||||
environmentId text [pk, not null]
|
||||
name text [not null]
|
||||
description text
|
||||
createdAt text [not null]
|
||||
env text [not null, default: '']
|
||||
projectId 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
|
||||
organizationId text [not null]
|
||||
userId text [not null]
|
||||
}
|
||||
|
||||
table gitea {
|
||||
giteaId text [pk, not null]
|
||||
giteaUrl text [not null, default: 'https://gitea.com']
|
||||
giteaInternalUrl text
|
||||
redirect_uri text
|
||||
client_id text
|
||||
client_secret text
|
||||
gitProviderId text [not null]
|
||||
access_token text
|
||||
refresh_token text
|
||||
expires_at integer
|
||||
scopes text [default: 'repo,repo:status,read:user,read:org']
|
||||
last_authenticated_at integer
|
||||
}
|
||||
|
||||
table github {
|
||||
@@ -387,6 +498,7 @@ table github {
|
||||
table gitlab {
|
||||
gitlabId text [pk, not null]
|
||||
gitlabUrl text [not null, default: 'https://gitlab.com']
|
||||
gitlabInternalUrl text
|
||||
application_id text
|
||||
redirect_uri text
|
||||
secret text
|
||||
@@ -397,20 +509,6 @@ table gitlab {
|
||||
gitProviderId text [not null]
|
||||
}
|
||||
|
||||
table gitea {
|
||||
giteaId text [pk, not null]
|
||||
giteaUrl text [not null, default: 'https://gitea.com']
|
||||
redirect_uri text
|
||||
client_id text [not null]
|
||||
client_secret text [not null]
|
||||
access_token text
|
||||
refresh_token text
|
||||
expires_at integer
|
||||
gitProviderId text [not null]
|
||||
scopes text [default: 'repo,repo:status,read:user,read:org']
|
||||
last_authenticated_at integer
|
||||
}
|
||||
|
||||
table gotify {
|
||||
gotifyId text [pk, not null]
|
||||
serverUrl text [not null]
|
||||
@@ -427,6 +525,7 @@ table invitation {
|
||||
status text [not null]
|
||||
expires_at timestamp [not null]
|
||||
inviter_id text [not null]
|
||||
team_id text
|
||||
}
|
||||
|
||||
table mariadb {
|
||||
@@ -447,8 +546,17 @@ table mariadb {
|
||||
cpuLimit text
|
||||
externalPort integer
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -458,6 +566,19 @@ table member {
|
||||
user_id text [not null]
|
||||
role text [not null]
|
||||
created_at timestamp [not null]
|
||||
team_id text
|
||||
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[]`]
|
||||
accessedEnvironments text[] [not null, default: `ARRAY[]::text[]`]
|
||||
accesedServices text[] [not null, default: `ARRAY[]::text[]`]
|
||||
}
|
||||
|
||||
table mongo {
|
||||
@@ -476,8 +597,17 @@ table mongo {
|
||||
cpuLimit text
|
||||
externalPort integer
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
replicaSets boolean [default: false]
|
||||
}
|
||||
@@ -518,8 +648,17 @@ table mysql {
|
||||
cpuLimit text
|
||||
externalPort integer
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -539,7 +678,17 @@ table notification {
|
||||
discordId text
|
||||
emailId text
|
||||
gotifyId text
|
||||
userId text
|
||||
ntfyId text
|
||||
customId text
|
||||
organizationId text [not null]
|
||||
}
|
||||
|
||||
table ntfy {
|
||||
ntfyId text [pk, not null]
|
||||
serverUrl text [not null]
|
||||
topic text [not null]
|
||||
accessToken text [not null]
|
||||
priority integer [not null, default: 3]
|
||||
}
|
||||
|
||||
table organization {
|
||||
@@ -555,6 +704,7 @@ table organization {
|
||||
table port {
|
||||
portId text [pk, not null]
|
||||
publishedPort integer [not null]
|
||||
publishMode publishModeType [not null, default: 'host']
|
||||
targetPort integer [not null]
|
||||
protocol protocolType [not null]
|
||||
applicationId text [not null]
|
||||
@@ -577,8 +727,17 @@ table postgres {
|
||||
cpuReservation text
|
||||
cpuLimit text
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -603,7 +762,7 @@ table project {
|
||||
name text [not null]
|
||||
description text
|
||||
createdAt text [not null]
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
env text [not null, default: '']
|
||||
}
|
||||
|
||||
@@ -633,7 +792,16 @@ table redis {
|
||||
externalPort integer
|
||||
createdAt text [not null]
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
projectId text [not null]
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -646,7 +814,34 @@ table registry {
|
||||
registryUrl text [not null, default: '']
|
||||
createdAt text [not null]
|
||||
selfHosted RegistryType [not null, default: 'cloud']
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
}
|
||||
|
||||
table rollback {
|
||||
rollbackId text [pk, not null]
|
||||
deploymentId text [not null]
|
||||
version serial [not null, increment]
|
||||
image text
|
||||
createdAt text [not null]
|
||||
fullContext jsonb
|
||||
}
|
||||
|
||||
table schedule {
|
||||
scheduleId text [pk, not null]
|
||||
name text [not null]
|
||||
cronExpression text [not null]
|
||||
appName text [not null]
|
||||
serviceName text
|
||||
shellType shellType [not null, default: 'bash']
|
||||
scheduleType scheduleType [not null, default: 'application']
|
||||
command text [not null]
|
||||
script text
|
||||
applicationId text
|
||||
composeId text
|
||||
serverId text
|
||||
userId text
|
||||
enabled boolean [not null, default: true]
|
||||
createdAt text [not null]
|
||||
}
|
||||
|
||||
table security {
|
||||
@@ -671,14 +866,14 @@ table server {
|
||||
appName text [not null]
|
||||
enableDockerCleanup boolean [not null, default: false]
|
||||
createdAt text [not null]
|
||||
userId text [not null]
|
||||
organizationId 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 {
|
||||
table session_temp {
|
||||
id text [pk, not null]
|
||||
expires_at timestamp [not null]
|
||||
token text [not null, unique]
|
||||
@@ -705,49 +900,49 @@ table "ssh-key" {
|
||||
description text
|
||||
createdAt text [not null]
|
||||
lastUsedAt text
|
||||
userId text
|
||||
organizationId text [not null]
|
||||
}
|
||||
|
||||
table telegram {
|
||||
telegramId text [pk, not null]
|
||||
botToken text [not null]
|
||||
chatId text [not null]
|
||||
messageThreadId text
|
||||
}
|
||||
|
||||
table user {
|
||||
table two_factor {
|
||||
id text [pk, not null]
|
||||
secret text [not null]
|
||||
backup_codes text [not null]
|
||||
user_id text [not null]
|
||||
}
|
||||
|
||||
table user_temp {
|
||||
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[]`]
|
||||
created_at timestamp [default: `now()`]
|
||||
two_factor_enabled boolean
|
||||
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']
|
||||
https boolean [not null, default: false]
|
||||
host text
|
||||
letsEncryptEmail text
|
||||
sshPrivateKey text
|
||||
enableDockerCleanup boolean [not null, default: false]
|
||||
enableLogRotation boolean [not null, default: false]
|
||||
logCleanupCron text [default: '0 0 * * *']
|
||||
role text [not null, default: 'user']
|
||||
enablePaidFeatures boolean [not null, default: false]
|
||||
allowImpersonation 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]
|
||||
@@ -766,6 +961,29 @@ table verification {
|
||||
updated_at timestamp
|
||||
}
|
||||
|
||||
table volume_backup {
|
||||
volumeBackupId text [pk, not null]
|
||||
name text [not null]
|
||||
volumeName text [not null]
|
||||
prefix text [not null]
|
||||
serviceType serviceType [not null, default: 'application']
|
||||
appName text [not null]
|
||||
serviceName text
|
||||
turnOff boolean [not null, default: false]
|
||||
cronExpression text [not null]
|
||||
keepLatestCount integer
|
||||
enabled boolean
|
||||
applicationId text
|
||||
postgresId text
|
||||
mariadbId text
|
||||
mongoId text
|
||||
mysqlId text
|
||||
redisId text
|
||||
composeId text
|
||||
createdAt text [not null]
|
||||
destinationId text [not null]
|
||||
}
|
||||
|
||||
ref: mount.applicationId > application.applicationId
|
||||
|
||||
ref: mount.postgresId > postgres.postgresId
|
||||
@@ -780,7 +998,13 @@ ref: mount.redisId > redis.redisId
|
||||
|
||||
ref: mount.composeId > compose.composeId
|
||||
|
||||
ref: application.projectId > project.projectId
|
||||
ref: user_temp.id - account.user_id
|
||||
|
||||
ref: ai.organizationId - organization.id
|
||||
|
||||
ref: apikey.user_id > user_temp.id
|
||||
|
||||
ref: application.environmentId > environment.environmentId
|
||||
|
||||
ref: application.customGitSSHKeyId > "ssh-key".sshKeyId
|
||||
|
||||
@@ -790,6 +1014,8 @@ ref: application.githubId - github.githubId
|
||||
|
||||
ref: application.gitlabId - gitlab.gitlabId
|
||||
|
||||
ref: application.giteaId - gitea.giteaId
|
||||
|
||||
ref: application.bitbucketId - bitbucket.bitbucketId
|
||||
|
||||
ref: application.serverId > server.serverId
|
||||
@@ -804,13 +1030,17 @@ ref: backup.mysqlId > mysql.mysqlId
|
||||
|
||||
ref: backup.mongoId > mongo.mongoId
|
||||
|
||||
ref: backup.userId > user_temp.id
|
||||
|
||||
ref: backup.composeId > compose.composeId
|
||||
|
||||
ref: git_provider.gitProviderId - bitbucket.gitProviderId
|
||||
|
||||
ref: certificate.serverId > server.serverId
|
||||
|
||||
ref: certificate.userId - user.id
|
||||
ref: certificate.organizationId - organization.id
|
||||
|
||||
ref: compose.projectId > project.projectId
|
||||
ref: compose.environmentId > environment.environmentId
|
||||
|
||||
ref: compose.customGitSSHKeyId > "ssh-key".sshKeyId
|
||||
|
||||
@@ -820,6 +1050,8 @@ ref: compose.gitlabId - gitlab.gitlabId
|
||||
|
||||
ref: compose.bitbucketId - bitbucket.bitbucketId
|
||||
|
||||
ref: compose.giteaId - gitea.giteaId
|
||||
|
||||
ref: compose.serverId > server.serverId
|
||||
|
||||
ref: deployment.applicationId > application.applicationId
|
||||
@@ -830,7 +1062,15 @@ ref: deployment.serverId > server.serverId
|
||||
|
||||
ref: deployment.previewDeploymentId > preview_deployments.previewDeploymentId
|
||||
|
||||
ref: destination.userId - user.id
|
||||
ref: deployment.scheduleId > schedule.scheduleId
|
||||
|
||||
ref: deployment.backupId > backup.backupId
|
||||
|
||||
ref: rollback.deploymentId - deployment.deploymentId
|
||||
|
||||
ref: deployment.volumeBackupId > volume_backup.volumeBackupId
|
||||
|
||||
ref: destination.organizationId - organization.id
|
||||
|
||||
ref: domain.applicationId > application.applicationId
|
||||
|
||||
@@ -838,23 +1078,33 @@ ref: domain.composeId > compose.composeId
|
||||
|
||||
ref: preview_deployments.domainId - domain.domainId
|
||||
|
||||
ref: environment.projectId > project.projectId
|
||||
|
||||
ref: github.gitProviderId - git_provider.gitProviderId
|
||||
|
||||
ref: gitlab.gitProviderId - git_provider.gitProviderId
|
||||
|
||||
ref: gitea.gitProviderId - git_provider.gitProviderId
|
||||
|
||||
ref: git_provider.userId - user.id
|
||||
ref: git_provider.organizationId - organization.id
|
||||
|
||||
ref: mariadb.projectId > project.projectId
|
||||
ref: git_provider.userId - user_temp.id
|
||||
|
||||
ref: invitation.organization_id - organization.id
|
||||
|
||||
ref: mariadb.environmentId > environment.environmentId
|
||||
|
||||
ref: mariadb.serverId > server.serverId
|
||||
|
||||
ref: mongo.projectId > project.projectId
|
||||
ref: member.organization_id > organization.id
|
||||
|
||||
ref: member.user_id - user_temp.id
|
||||
|
||||
ref: mongo.environmentId > environment.environmentId
|
||||
|
||||
ref: mongo.serverId > server.serverId
|
||||
|
||||
ref: mysql.projectId > project.projectId
|
||||
ref: mysql.environmentId > environment.environmentId
|
||||
|
||||
ref: mysql.serverId > server.serverId
|
||||
|
||||
@@ -868,30 +1118,58 @@ ref: notification.emailId - email.emailId
|
||||
|
||||
ref: notification.gotifyId - gotify.gotifyId
|
||||
|
||||
ref: notification.userId - user.id
|
||||
ref: notification.ntfyId - ntfy.ntfyId
|
||||
|
||||
ref: notification.customId - custom.customId
|
||||
|
||||
ref: notification.organizationId - organization.id
|
||||
|
||||
ref: organization.owner_id > user_temp.id
|
||||
|
||||
ref: port.applicationId > application.applicationId
|
||||
|
||||
ref: postgres.projectId > project.projectId
|
||||
ref: postgres.environmentId > environment.environmentId
|
||||
|
||||
ref: postgres.serverId > server.serverId
|
||||
|
||||
ref: preview_deployments.applicationId > application.applicationId
|
||||
|
||||
ref: project.userId - user.id
|
||||
ref: project.organizationId > organization.id
|
||||
|
||||
ref: redirect.applicationId > application.applicationId
|
||||
|
||||
ref: redis.projectId > project.projectId
|
||||
ref: redis.environmentId > environment.environmentId
|
||||
|
||||
ref: redis.serverId > server.serverId
|
||||
|
||||
ref: registry.userId - user.id
|
||||
ref: schedule.applicationId - application.applicationId
|
||||
|
||||
ref: schedule.composeId > compose.composeId
|
||||
|
||||
ref: schedule.serverId > server.serverId
|
||||
|
||||
ref: schedule.userId > user_temp.id
|
||||
|
||||
ref: security.applicationId > application.applicationId
|
||||
|
||||
ref: server.userId - user.id
|
||||
|
||||
ref: server.sshKeyId > "ssh-key".sshKeyId
|
||||
|
||||
ref: "ssh-key".userId - user.id
|
||||
ref: server.organizationId > organization.id
|
||||
|
||||
ref: "ssh-key".organizationId - organization.id
|
||||
|
||||
ref: volume_backup.applicationId - application.applicationId
|
||||
|
||||
ref: volume_backup.postgresId - postgres.postgresId
|
||||
|
||||
ref: volume_backup.mariadbId - mariadb.mariadbId
|
||||
|
||||
ref: volume_backup.mongoId - mongo.mongoId
|
||||
|
||||
ref: volume_backup.mysqlId - mysql.mysqlId
|
||||
|
||||
ref: volume_backup.redisId - redis.redisId
|
||||
|
||||
ref: volume_backup.composeId - compose.composeId
|
||||
|
||||
ref: volume_backup.destinationId - destination.destinationId
|
||||
@@ -38,11 +38,9 @@ const createSchema = createInsertSchema(security, {
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiFindOneSecurity = createSchema
|
||||
.pick({
|
||||
securityId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneSecurity = z.object({
|
||||
securityId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiCreateSecurity = createSchema
|
||||
.pick({
|
||||
|
||||
@@ -15,6 +15,7 @@ import { applications } from "./application";
|
||||
import { certificates } from "./certificate";
|
||||
import { compose } from "./compose";
|
||||
import { deployments } from "./deployment";
|
||||
import { libsql } from "./libsql";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { mysql } from "./mysql";
|
||||
@@ -24,6 +25,7 @@ import { schedules } from "./schedule";
|
||||
import { sshKeys } from "./ssh-key";
|
||||
import { generateAppName } from "./utils";
|
||||
export const serverStatus = pgEnum("serverStatus", ["active", "inactive"]);
|
||||
export const serverType = pgEnum("serverType", ["deploy", "build"]);
|
||||
|
||||
export const server = pgTable("server", {
|
||||
serverId: text("serverId")
|
||||
@@ -44,6 +46,7 @@ export const server = pgTable("server", {
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
serverStatus: serverStatus("serverStatus").notNull().default("active"),
|
||||
serverType: serverType("serverType").notNull().default("deploy"),
|
||||
command: text("command").notNull().default(""),
|
||||
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
|
||||
onDelete: "set null",
|
||||
@@ -97,13 +100,24 @@ export const server = pgTable("server", {
|
||||
});
|
||||
|
||||
export const serverRelations = relations(server, ({ one, many }) => ({
|
||||
deployments: many(deployments),
|
||||
deployments: many(deployments, {
|
||||
relationName: "deploymentServer",
|
||||
}),
|
||||
buildDeployments: many(deployments, {
|
||||
relationName: "deploymentBuildServer",
|
||||
}),
|
||||
sshKey: one(sshKeys, {
|
||||
fields: [server.sshKeyId],
|
||||
references: [sshKeys.sshKeyId],
|
||||
}),
|
||||
applications: many(applications),
|
||||
applications: many(applications, {
|
||||
relationName: "applicationServer",
|
||||
}),
|
||||
buildApplications: many(applications, {
|
||||
relationName: "applicationBuildServer",
|
||||
}),
|
||||
compose: many(compose),
|
||||
libsql: many(libsql),
|
||||
redis: many(redis),
|
||||
mariadb: many(mariadb),
|
||||
mongo: many(mongo),
|
||||
@@ -121,6 +135,7 @@ const createSchema = createInsertSchema(server, {
|
||||
serverId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
serverType: z.enum(["deploy", "build"]).optional(),
|
||||
});
|
||||
|
||||
export const apiCreateServer = createSchema
|
||||
@@ -131,14 +146,13 @@ export const apiCreateServer = createSchema
|
||||
port: true,
|
||||
username: true,
|
||||
sshKeyId: true,
|
||||
serverType: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiFindOneServer = createSchema
|
||||
.pick({
|
||||
serverId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneServer = z.object({
|
||||
serverId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveServer = createSchema
|
||||
.pick({
|
||||
@@ -155,6 +169,7 @@ export const apiUpdateServer = createSchema
|
||||
port: true,
|
||||
username: true,
|
||||
sshKeyId: true,
|
||||
serverType: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { user } from "./user";
|
||||
|
||||
// OLD TABLE
|
||||
export const session = pgTable("session_temp", {
|
||||
export const session = pgTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
expiresAt: timestamp("expires_at").notNull(),
|
||||
token: text("token").notNull().unique(),
|
||||
|
||||
@@ -16,6 +16,8 @@ export const certificateType = pgEnum("certificateType", [
|
||||
|
||||
export const triggerType = pgEnum("triggerType", ["push", "tag"]);
|
||||
|
||||
export const sqldNode = pgEnum("sqldNode", ["primary", "replica"]);
|
||||
|
||||
export interface HealthCheckSwarm {
|
||||
Test?: string[] | undefined;
|
||||
Interval?: number | undefined;
|
||||
@@ -86,6 +88,14 @@ export interface EndpointSpecSwarm {
|
||||
Ports?: EndpointPortConfigSwarm[] | undefined;
|
||||
}
|
||||
|
||||
export interface UlimitSwarm {
|
||||
Name: string;
|
||||
Soft: number;
|
||||
Hard: number;
|
||||
}
|
||||
|
||||
export type UlimitsSwarm = UlimitSwarm[];
|
||||
|
||||
export const HealthCheckSwarmSchema = z
|
||||
.object({
|
||||
Test: z.array(z.string()).optional(),
|
||||
@@ -167,12 +177,12 @@ export const NetworkSwarmSchema = z.array(
|
||||
.object({
|
||||
Target: z.string().optional(),
|
||||
Aliases: z.array(z.string()).optional(),
|
||||
DriverOpts: z.object({}).optional(),
|
||||
DriverOpts: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
.strict(),
|
||||
);
|
||||
|
||||
export const LabelsSwarmSchema = z.record(z.string());
|
||||
export const LabelsSwarmSchema = z.record(z.string(), z.string());
|
||||
|
||||
export const EndpointPortConfigSwarmSchema = z
|
||||
.object({
|
||||
@@ -189,3 +199,13 @@ export const EndpointSpecSwarmSchema = z
|
||||
Ports: z.array(EndpointPortConfigSwarmSchema).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const UlimitSwarmSchema = z
|
||||
.object({
|
||||
Name: z.string().min(1),
|
||||
Soft: z.number().int().min(-1),
|
||||
Hard: z.number().int().min(-1),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const UlimitsSwarmSchema = z.array(UlimitSwarmSchema);
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 { sshKeyCreate, sshKeyType } from "../validations";
|
||||
import { organization } from "./account";
|
||||
import { applications } from "./application";
|
||||
@@ -52,11 +53,9 @@ export const apiCreateSshKey = createSchema
|
||||
})
|
||||
.merge(sshKeyCreate.pick({ privateKey: true }));
|
||||
|
||||
export const apiFindOneSshKey = createSchema
|
||||
.pick({
|
||||
sshKeyId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiFindOneSshKey = z.object({
|
||||
sshKeyId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiGenerateSSHKey = sshKeyType;
|
||||
|
||||
|
||||
133
packages/server/src/db/schema/sso.ts
Normal file
133
packages/server/src/db/schema/sso.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { z } from "zod";
|
||||
import { organization } from "./account";
|
||||
import { user } from "./user";
|
||||
|
||||
export const ssoProvider = pgTable("sso_provider", {
|
||||
id: text("id").primaryKey(),
|
||||
issuer: text("issuer").notNull(),
|
||||
oidcConfig: text("oidc_config"),
|
||||
samlConfig: text("saml_config"),
|
||||
providerId: text("provider_id").notNull().unique(),
|
||||
userId: text("user_id").references(() => user.id, { onDelete: "cascade" }),
|
||||
organizationId: text("organization_id").references(() => organization.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
domain: text("domain").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const ssoProviderRelations = relations(ssoProvider, ({ one }) => ({
|
||||
organization: one(organization, {
|
||||
fields: [ssoProvider.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
user: one(user, {
|
||||
fields: [ssoProvider.userId],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
|
||||
export const ssoProviderBodySchema = z.object({
|
||||
providerId: z.string({}),
|
||||
issuer: z.string({}),
|
||||
domains: z
|
||||
.string()
|
||||
.array()
|
||||
.transform((val) =>
|
||||
Array.from(
|
||||
new Set(val.map((d) => d.trim().toLowerCase()).filter(Boolean)),
|
||||
),
|
||||
)
|
||||
.refine((val) => val.every((d) => domainRegex.test(d)), {
|
||||
message: "Invalid domain",
|
||||
path: ["domains"],
|
||||
}),
|
||||
oidcConfig: z
|
||||
.object({
|
||||
clientId: z.string({}),
|
||||
clientSecret: z.string({}),
|
||||
authorizationEndpoint: z.string({}).optional(),
|
||||
tokenEndpoint: z.string({}).optional(),
|
||||
userInfoEndpoint: z.string({}).optional(),
|
||||
tokenEndpointAuthentication: z
|
||||
.enum(["client_secret_post", "client_secret_basic"])
|
||||
.optional(),
|
||||
jwksEndpoint: z.string({}).optional(),
|
||||
discoveryEndpoint: z.string().optional(),
|
||||
skipDiscovery: z.boolean().optional(),
|
||||
scopes: z.array(z.string()).optional(),
|
||||
pkce: z.boolean().default(true).optional(),
|
||||
mapping: z
|
||||
.object({
|
||||
id: z.string({}),
|
||||
email: z.string({}),
|
||||
emailVerified: z.string({}).optional(),
|
||||
name: z.string({}),
|
||||
image: z.string({}).optional(),
|
||||
extraFields: z.record(z.string(), z.any()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
samlConfig: z
|
||||
.object({
|
||||
entryPoint: z.string({}),
|
||||
cert: z.string({}),
|
||||
callbackUrl: z.string({}),
|
||||
audience: z.string().optional(),
|
||||
idpMetadata: z
|
||||
.object({
|
||||
metadata: z.string().optional(),
|
||||
entityID: z.string().optional(),
|
||||
cert: z.string().optional(),
|
||||
privateKey: z.string().optional(),
|
||||
privateKeyPass: z.string().optional(),
|
||||
isAssertionEncrypted: z.boolean().optional(),
|
||||
encPrivateKey: z.string().optional(),
|
||||
encPrivateKeyPass: z.string().optional(),
|
||||
singleSignOnService: z
|
||||
.array(
|
||||
z.object({
|
||||
Binding: z.string(),
|
||||
Location: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
spMetadata: z.object({
|
||||
metadata: z.string().optional(),
|
||||
entityID: z.string().optional(),
|
||||
binding: z.string().optional(),
|
||||
privateKey: z.string().optional(),
|
||||
privateKeyPass: z.string().optional(),
|
||||
isAssertionEncrypted: z.boolean().optional(),
|
||||
encPrivateKey: z.string().optional(),
|
||||
encPrivateKeyPass: z.string().optional(),
|
||||
}),
|
||||
wantAssertionsSigned: z.boolean().optional(),
|
||||
authnRequestsSigned: z.boolean().optional(),
|
||||
signatureAlgorithm: z.string().optional(),
|
||||
digestAlgorithm: z.string().optional(),
|
||||
identifierFormat: z.string().optional(),
|
||||
privateKey: z.string().optional(),
|
||||
decryptionPvk: z.string().optional(),
|
||||
additionalParams: z.record(z.string(), z.any()).optional(),
|
||||
mapping: z
|
||||
.object({
|
||||
id: z.string({}),
|
||||
email: z.string({}),
|
||||
emailVerified: z.string({}).optional(),
|
||||
name: z.string({}),
|
||||
firstName: z.string({}).optional(),
|
||||
lastName: z.string({}).optional(),
|
||||
extraFields: z.record(z.string(), z.any()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
organizationId: z.string({}).optional(),
|
||||
overrideUserInfo: z.boolean({}).default(false).optional(),
|
||||
});
|
||||
99
packages/server/src/db/schema/tag.ts
Normal file
99
packages/server/src/db/schema/tag.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { pgTable, text, unique } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { organization } from "./account";
|
||||
import { projects } from "./project";
|
||||
|
||||
export const tags = pgTable(
|
||||
"tag",
|
||||
{
|
||||
tagId: text("tagId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
color: text("color"),
|
||||
createdAt: text("createdAt")
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString()),
|
||||
|
||||
organizationId: text("organizationId")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => ({
|
||||
// Unique index on (organizationId, name) to prevent duplicate tag names per organization
|
||||
uniqueOrgName: unique("unique_org_tag_name").on(
|
||||
table.organizationId,
|
||||
table.name,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const projectTags = pgTable(
|
||||
"project_tag",
|
||||
{
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
projectId: text("projectId")
|
||||
.notNull()
|
||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||
tagId: text("tagId")
|
||||
.notNull()
|
||||
.references(() => tags.tagId, { onDelete: "cascade" }),
|
||||
},
|
||||
(table) => ({
|
||||
// Unique constraint to prevent duplicate project-tag associations
|
||||
uniqueProjectTag: unique("unique_project_tag").on(
|
||||
table.projectId,
|
||||
table.tagId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const tagRelations = relations(tags, ({ one, many }) => ({
|
||||
organization: one(organization, {
|
||||
fields: [tags.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
projectTags: many(projectTags),
|
||||
}));
|
||||
|
||||
export const projectTagRelations = relations(projectTags, ({ one }) => ({
|
||||
project: one(projects, {
|
||||
fields: [projectTags.projectId],
|
||||
references: [projects.projectId],
|
||||
}),
|
||||
tag: one(tags, {
|
||||
fields: [projectTags.tagId],
|
||||
references: [tags.tagId],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(tags, {
|
||||
tagId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
color: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiCreateTag = createSchema.pick({
|
||||
name: true,
|
||||
color: true,
|
||||
});
|
||||
|
||||
export const apiFindOneTag = z.object({
|
||||
tagId: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiRemoveTag = createSchema
|
||||
.pick({
|
||||
tagId: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateTag = createSchema.partial().extend({
|
||||
tagId: z.string().min(1),
|
||||
});
|
||||
@@ -3,7 +3,6 @@ import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
@@ -16,7 +15,7 @@ import { account, apikey, organization } from "./account";
|
||||
import { backups } from "./backups";
|
||||
import { projects } from "./project";
|
||||
import { schedules } from "./schedule";
|
||||
import { certificateType } from "./shared";
|
||||
import { ssoProvider } from "./sso";
|
||||
/**
|
||||
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
||||
* database instance for multiple projects.
|
||||
@@ -32,7 +31,8 @@ export const user = pgTable("user", {
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull().default(""),
|
||||
firstName: text("firstName").notNull().default(""),
|
||||
lastName: text("lastName").notNull().default(""),
|
||||
isRegistered: boolean("isRegistered").notNull().default(false),
|
||||
expirationDate: text("expirationDate")
|
||||
.notNull()
|
||||
@@ -51,76 +51,22 @@ export const user = pgTable("user", {
|
||||
banExpires: timestamp("ban_expires"),
|
||||
updatedAt: timestamp("updated_at").notNull(),
|
||||
// Admin
|
||||
serverIp: text("serverIp"),
|
||||
certificateType: certificateType("certificateType").notNull().default("none"),
|
||||
https: boolean("https").notNull().default(false),
|
||||
host: text("host"),
|
||||
letsEncryptEmail: text("letsEncryptEmail"),
|
||||
sshPrivateKey: text("sshPrivateKey"),
|
||||
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
||||
logCleanupCron: text("logCleanupCron").default("0 0 * * *"),
|
||||
role: text("role").notNull().default("user"),
|
||||
// Metrics
|
||||
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
|
||||
allowImpersonation: boolean("allowImpersonation").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")
|
||||
// Enterprise / proprietary features
|
||||
enableEnterpriseFeatures: boolean("enableEnterpriseFeatures")
|
||||
.notNull()
|
||||
.default(false),
|
||||
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
|
||||
.notNull()
|
||||
.default(false),
|
||||
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
|
||||
licenseKey: text("licenseKey"),
|
||||
isValidEnterpriseLicense: boolean("isValidEnterpriseLicense")
|
||||
.notNull()
|
||||
.default(false),
|
||||
stripeCustomerId: text("stripeCustomerId"),
|
||||
stripeSubscriptionId: text("stripeSubscriptionId"),
|
||||
serversQuantity: integer("serversQuantity").notNull().default(0),
|
||||
trustedOrigins: text("trustedOrigins").array(),
|
||||
});
|
||||
|
||||
export const userTemplateBookmarks = pgTable(
|
||||
@@ -149,6 +95,7 @@ export const usersRelations = relations(user, ({ one, many }) => ({
|
||||
organizations: many(organization),
|
||||
projects: many(projects),
|
||||
apiKeys: many(apikey),
|
||||
ssoProviders: many(ssoProvider),
|
||||
backups: many(backups),
|
||||
schedules: many(schedules),
|
||||
templateBookmarks: many(userTemplateBookmarks),
|
||||
@@ -169,6 +116,8 @@ const createSchema = createInsertSchema(user, {
|
||||
isRegistered: z.boolean().optional(),
|
||||
}).omit({
|
||||
role: true,
|
||||
trustedOrigins: true,
|
||||
isValidEnterpriseLicense: true,
|
||||
});
|
||||
|
||||
export const apiCreateUserInvitation = createSchema.pick({}).extend({
|
||||
@@ -207,6 +156,7 @@ export const apiAssignPermissions = createSchema
|
||||
accessedProjects: z.array(z.string()).optional(),
|
||||
accessedEnvironments: z.array(z.string()).optional(),
|
||||
accessedServices: z.array(z.string()).optional(),
|
||||
accessedGitProviders: z.array(z.string()).optional(),
|
||||
canCreateProjects: z.boolean().optional(),
|
||||
canCreateServices: z.boolean().optional(),
|
||||
canDeleteProjects: z.boolean().optional(),
|
||||
@@ -232,33 +182,6 @@ export const apiFindOneUserByAuth = createSchema
|
||||
// authId: true,
|
||||
})
|
||||
.required();
|
||||
export const apiSaveSSHKey = createSchema
|
||||
.pick({
|
||||
sshPrivateKey: true,
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiAssignDomain = createSchema
|
||||
.pick({
|
||||
host: true,
|
||||
certificateType: true,
|
||||
letsEncryptEmail: true,
|
||||
https: true,
|
||||
})
|
||||
.required()
|
||||
.partial({
|
||||
letsEncryptEmail: true,
|
||||
https: true,
|
||||
});
|
||||
|
||||
export const apiUpdateDockerCleanup = createSchema
|
||||
.pick({
|
||||
enableDockerCleanup: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTraefikConfig = z.object({
|
||||
traefikConfig: z.string().min(1),
|
||||
@@ -327,32 +250,6 @@ export const apiReadStatsLogs = z.object({
|
||||
.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({
|
||||
email: z
|
||||
.string()
|
||||
@@ -361,30 +258,6 @@ export const apiUpdateUser = createSchema.partial().extend({
|
||||
.optional(),
|
||||
password: z.string().optional(),
|
||||
currentPassword: z.string().optional(),
|
||||
name: 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(),
|
||||
logCleanupCron: z.string().optional().nullable(),
|
||||
firstName: z.string().optional(),
|
||||
lastName: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,12 @@ const alphabet = "abcdefghijklmnopqrstuvwxyz123456789";
|
||||
|
||||
const customNanoid = customAlphabet(alphabet, 6);
|
||||
|
||||
/** App name: letters, numbers, dots, underscores, hyphens only (no spaces). Safe for shell/Docker. */
|
||||
export const APP_NAME_REGEX = /^[a-zA-Z0-9._-]+$/;
|
||||
|
||||
export const APP_NAME_MESSAGE =
|
||||
"App name can only contain letters, numbers, dots, underscores and hyphens";
|
||||
|
||||
export const generateAppName = (type: string) => {
|
||||
const verb = faker.hacker.verb().replace(/ /g, "-");
|
||||
const adjective = faker.hacker.adjective().replace(/ /g, "-");
|
||||
|
||||
@@ -7,6 +7,7 @@ import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
import { deployments } from "./deployment";
|
||||
import { destinations } from "./destination";
|
||||
import { libsql } from "./libsql";
|
||||
import { mariadb } from "./mariadb";
|
||||
import { mongo } from "./mongo";
|
||||
import { serviceType } from "./mount";
|
||||
@@ -53,6 +54,9 @@ export const volumeBackups = pgTable("volume_backup", {
|
||||
redisId: text("redisId").references(() => redis.redisId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
libsqlId: text("libsqlId").references(() => libsql.libsqlId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
composeId: text("composeId").references(() => compose.composeId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
@@ -93,6 +97,10 @@ export const volumeBackupsRelations = relations(
|
||||
fields: [volumeBackups.redisId],
|
||||
references: [redis.redisId],
|
||||
}),
|
||||
libsql: one(libsql, {
|
||||
fields: [volumeBackups.libsqlId],
|
||||
references: [libsql.libsqlId],
|
||||
}),
|
||||
compose: one(compose, {
|
||||
fields: [volumeBackups.composeId],
|
||||
references: [compose.composeId],
|
||||
|
||||
238
packages/server/src/db/schema/web-server-settings.ts
Normal file
238
packages/server/src/db/schema/web-server-settings.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { certificateType } from "./shared";
|
||||
|
||||
export const webServerSettings = pgTable("webServerSettings", {
|
||||
id: text("id")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
// Web Server Configuration
|
||||
serverIp: text("serverIp"),
|
||||
certificateType: certificateType("certificateType").notNull().default("none"),
|
||||
https: boolean("https").notNull().default(false),
|
||||
host: text("host"),
|
||||
letsEncryptEmail: text("letsEncryptEmail"),
|
||||
sshPrivateKey: text("sshPrivateKey"),
|
||||
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(true),
|
||||
logCleanupCron: text("logCleanupCron").default("0 0 * * *"),
|
||||
// Metrics Configuration
|
||||
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: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Whitelabeling Configuration (Enterprise / Proprietary)
|
||||
whitelabelingConfig: jsonb("whitelabelingConfig")
|
||||
.$type<{
|
||||
appName: string | null;
|
||||
appDescription: string | null;
|
||||
logoUrl: string | null;
|
||||
faviconUrl: string | null;
|
||||
customCss: string | null;
|
||||
loginLogoUrl: string | null;
|
||||
supportUrl: string | null;
|
||||
docsUrl: string | null;
|
||||
errorPageTitle: string | null;
|
||||
errorPageDescription: string | null;
|
||||
metaTitle: string | null;
|
||||
footerText: string | null;
|
||||
}>()
|
||||
.default({
|
||||
appName: null,
|
||||
appDescription: null,
|
||||
logoUrl: null,
|
||||
faviconUrl: null,
|
||||
customCss: null,
|
||||
loginLogoUrl: null,
|
||||
supportUrl: null,
|
||||
docsUrl: null,
|
||||
errorPageTitle: null,
|
||||
errorPageDescription: null,
|
||||
metaTitle: null,
|
||||
footerText: null,
|
||||
}),
|
||||
// Cache Cleanup Configuration
|
||||
cleanupCacheApplications: boolean("cleanupCacheApplications")
|
||||
.notNull()
|
||||
.default(false),
|
||||
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
|
||||
.notNull()
|
||||
.default(false),
|
||||
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
|
||||
.notNull()
|
||||
.default(false),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const webServerSettingsRelations = relations(
|
||||
webServerSettings,
|
||||
() => ({}),
|
||||
);
|
||||
|
||||
const createSchema = createInsertSchema(webServerSettings, {
|
||||
id: z.string().min(1),
|
||||
});
|
||||
|
||||
export const apiUpdateWebServerSettings = createSchema.partial().extend({
|
||||
serverIp: z.string().optional(),
|
||||
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||
https: z.boolean().optional(),
|
||||
host: z.string().optional(),
|
||||
letsEncryptEmail: z.string().email().optional().nullable(),
|
||||
sshPrivateKey: z.string().optional(),
|
||||
enableDockerCleanup: z.boolean().optional(),
|
||||
logCleanupCron: z.string().optional().nullable(),
|
||||
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(),
|
||||
cleanupCacheApplications: z.boolean().optional(),
|
||||
cleanupCacheOnPreviews: z.boolean().optional(),
|
||||
cleanupCacheOnCompose: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiAssignDomain = z
|
||||
.object({
|
||||
host: z.string(),
|
||||
certificateType: z.enum(["letsencrypt", "none", "custom"]),
|
||||
letsEncryptEmail: z
|
||||
.union([z.string().email(), z.literal("")])
|
||||
.optional()
|
||||
.nullable(),
|
||||
https: z.boolean().optional(),
|
||||
})
|
||||
.required()
|
||||
.partial({
|
||||
letsEncryptEmail: true,
|
||||
https: true,
|
||||
});
|
||||
|
||||
export const apiSaveSSHKey = z
|
||||
.object({
|
||||
sshPrivateKey: z.string(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiUpdateDockerCleanup = z.object({
|
||||
enableDockerCleanup: z.boolean(),
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
// Whitelabeling validation schemas
|
||||
const safeUrl = z
|
||||
.string()
|
||||
.refine((url) => /^https?:\/\//i.test(url), {
|
||||
message: "Only http:// and https:// URLs are allowed",
|
||||
})
|
||||
.nullable();
|
||||
|
||||
export const whitelabelingConfigSchema = z.object({
|
||||
appName: z.string().nullable(),
|
||||
appDescription: z.string().nullable(),
|
||||
logoUrl: safeUrl,
|
||||
faviconUrl: safeUrl,
|
||||
customCss: z.string().nullable(),
|
||||
loginLogoUrl: safeUrl,
|
||||
supportUrl: safeUrl,
|
||||
docsUrl: safeUrl,
|
||||
errorPageTitle: z.string().nullable(),
|
||||
errorPageDescription: z.string().nullable(),
|
||||
metaTitle: z.string().nullable(),
|
||||
footerText: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const apiUpdateWhitelabeling = z.object({
|
||||
whitelabelingConfig: whitelabelingConfigSchema,
|
||||
});
|
||||
|
||||
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(),
|
||||
});
|
||||
3
packages/server/src/db/validations/destination.ts
Normal file
3
packages/server/src/db/validations/destination.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const ADDITIONAL_FLAG_REGEX = /^--[a-zA-Z0-9-]+(=[a-zA-Z0-9._:/@-]+)?$/;
|
||||
export const ADDITIONAL_FLAG_ERROR =
|
||||
"Invalid flag format. Must start with -- (e.g. --s3-sign-accept-encoding=false)";
|
||||
@@ -2,7 +2,13 @@ import { z } from "zod";
|
||||
|
||||
export const domain = z
|
||||
.object({
|
||||
host: z.string().min(1, { message: "Add a hostname" }),
|
||||
host: z
|
||||
.string()
|
||||
.min(1, { message: "Add a hostname" })
|
||||
.refine((val) => val === val.trim(), {
|
||||
message: "Domain name cannot have leading or trailing spaces",
|
||||
})
|
||||
.transform((val) => val.trim()),
|
||||
path: z.string().min(1).optional(),
|
||||
internalPath: z.string().optional(),
|
||||
stripPath: z.boolean().optional(),
|
||||
@@ -58,7 +64,13 @@ export const domain = z
|
||||
|
||||
export const domainCompose = z
|
||||
.object({
|
||||
host: z.string().min(1, { message: "Host is required" }),
|
||||
host: z
|
||||
.string()
|
||||
.min(1, { message: "Add a hostname" })
|
||||
.refine((val) => val === val.trim(), {
|
||||
message: "Domain name cannot have leading or trailing spaces",
|
||||
})
|
||||
.transform((val) => val.trim()),
|
||||
path: z.string().min(1).optional(),
|
||||
internalPath: z.string().optional(),
|
||||
stripPath: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user