dokploy/packages/server/src/db/schema/application.ts
2025-03-16 13:13:41 -06:00

559 lines
15 KiB
TypeScript

import { relations } from "drizzle-orm";
import {
boolean,
integer,
json,
pgEnum,
pgTable,
text,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { bitbucket } from "./bitbucket";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { github } from "./github";
import { gitlab } from "./gitlab";
import { gitea } from "./gitea";
import { mounts } from "./mount";
import { ports } from "./port";
import { previewDeployments } from "./preview-deployments";
import { projects } from "./project";
import { redirects } from "./redirects";
import { registry } from "./registry";
import { security } from "./security";
import { server } from "./server";
import { applicationStatus, certificateType } from "./shared";
import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils";
export const sourceType = pgEnum("sourceType", [
"docker",
"git",
"github",
"gitlab",
"bitbucket",
"gitea",
"drop",
]);
export const buildType = pgEnum("buildType", [
"dockerfile",
"heroku_buildpacks",
"paketo_buildpacks",
"nixpacks",
"static",
"railpack",
]);
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
StartPeriod?: number | undefined;
Retries?: number | undefined;
}
export interface RestartPolicySwarm {
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}
export interface PlacementSwarm {
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
Platforms?:
| Array<{
Architecture: string;
OS: string;
}>
| undefined;
}
export interface UpdateConfigSwarm {
Parallelism: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
Monitor?: number | undefined;
MaxFailureRatio?: number | undefined;
Order: string;
}
export interface ServiceModeSwarm {
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
| {
MaxConcurrent?: number | undefined;
TotalCompletions?: number | undefined;
}
| undefined;
GlobalJob?: {} | undefined;
}
export interface NetworkSwarm {
Target?: string | undefined;
Aliases?: string[] | undefined;
DriverOpts?: { [key: string]: string } | undefined;
}
export interface LabelsSwarm {
[name: string]: string;
}
export const applications = pgTable("application", {
applicationId: text("applicationId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull(),
appName: text("appName")
.notNull()
.$defaultFn(() => generateAppName("app"))
.unique(),
description: text("description"),
env: text("env"),
previewEnv: text("previewEnv"),
watchPaths: text("watchPaths").array(),
previewBuildArgs: text("previewBuildArgs"),
previewWildcard: text("previewWildcard"),
previewPort: integer("previewPort").default(3000),
previewHttps: boolean("previewHttps").notNull().default(false),
previewPath: text("previewPath").default("/"),
previewCertificateType: certificateType("certificateType")
.notNull()
.default("none"),
previewCustomCertResolver: text("previewCustomCertResolver"),
previewLimit: integer("previewLimit").default(3),
isPreviewDeploymentsActive: boolean("isPreviewDeploymentsActive").default(
false,
),
buildArgs: text("buildArgs"),
memoryReservation: text("memoryReservation"),
memoryLimit: text("memoryLimit"),
cpuReservation: text("cpuReservation"),
cpuLimit: text("cpuLimit"),
title: text("title"),
enabled: boolean("enabled"),
subtitle: text("subtitle"),
command: text("command"),
refreshToken: text("refreshToken").$defaultFn(() => nanoid()),
sourceType: sourceType("sourceType").notNull().default("github"),
cleanCache: boolean("cleanCache").default(false),
// Github
repository: text("repository"),
owner: text("owner"),
branch: text("branch"),
buildPath: text("buildPath").default("/"),
autoDeploy: boolean("autoDeploy").$defaultFn(() => true),
// Gitlab
gitlabProjectId: integer("gitlabProjectId"),
gitlabRepository: text("gitlabRepository"),
gitlabOwner: text("gitlabOwner"),
gitlabBranch: text("gitlabBranch"),
gitlabBuildPath: text("gitlabBuildPath").default("/"),
gitlabPathNamespace: text("gitlabPathNamespace"),
// Gitea
giteaProjectId: integer("giteaProjectId"),
giteaRepository: text("giteaRepository"),
giteaOwner: text("giteaOwner"),
giteaBranch: text("giteaBranch"),
giteaBuildPath: text("giteaBuildPath").default("/"),
giteaPathNamespace: text("giteaPathNamespace"),
// Bitbucket
bitbucketRepository: text("bitbucketRepository"),
bitbucketOwner: text("bitbucketOwner"),
bitbucketBranch: text("bitbucketBranch"),
bitbucketBuildPath: text("bitbucketBuildPath").default("/"),
// Docker
username: text("username"),
password: text("password"),
dockerImage: text("dockerImage"),
registryUrl: text("registryUrl"),
// Git
customGitUrl: text("customGitUrl"),
customGitBranch: text("customGitBranch"),
customGitBuildPath: text("customGitBuildPath"),
customGitSSHKeyId: text("customGitSSHKeyId").references(
() => sshKeys.sshKeyId,
{
onDelete: "set null",
},
),
dockerfile: text("dockerfile"),
dockerContextPath: text("dockerContextPath"),
dockerBuildStage: text("dockerBuildStage"),
// Drop
dropBuildPath: text("dropBuildPath"),
// Docker swarm json
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[]>(),
//
replicas: integer("replicas").default(1).notNull(),
applicationStatus: applicationStatus("applicationStatus")
.notNull()
.default("idle"),
buildType: buildType("buildType").notNull().default("nixpacks"),
herokuVersion: text("herokuVersion").default("24"),
publishDirectory: text("publishDirectory"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryId: text("registryId").references(() => registry.registryId, {
onDelete: "set null",
}),
projectId: text("projectId")
.notNull()
.references(() => projects.projectId, { onDelete: "cascade" }),
githubId: text("githubId").references(() => github.githubId, {
onDelete: "set null",
}),
gitlabId: text("gitlabId").references(() => gitlab.gitlabId, {
onDelete: "set null",
}),
giteaId: text("giteaId").references(() => gitea.giteaId, {
onDelete: "set null",
}),
bitbucketId: text("bitbucketId").references(() => bitbucket.bitbucketId, {
onDelete: "set null",
}),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
export const applicationsRelations = relations(
applications,
({ one, many }) => ({
project: one(projects, {
fields: [applications.projectId],
references: [projects.projectId],
}),
deployments: many(deployments),
customGitSSHKey: one(sshKeys, {
fields: [applications.customGitSSHKeyId],
references: [sshKeys.sshKeyId],
}),
domains: many(domains),
mounts: many(mounts),
redirects: many(redirects),
security: many(security),
ports: many(ports),
registry: one(registry, {
fields: [applications.registryId],
references: [registry.registryId],
}),
github: one(github, {
fields: [applications.githubId],
references: [github.githubId],
}),
gitlab: one(gitlab, {
fields: [applications.gitlabId],
references: [gitlab.gitlabId],
}),
gitea: one(gitea, {
fields: [applications.giteaId],
references: [gitea.giteaId],
}),
bitbucket: one(bitbucket, {
fields: [applications.bitbucketId],
references: [bitbucket.bitbucketId],
}),
server: one(server, {
fields: [applications.serverId],
references: [server.serverId],
}),
previewDeployments: many(previewDeployments),
}),
);
const HealthCheckSwarmSchema = z
.object({
Test: z.array(z.string()).optional(),
Interval: z.number().optional(),
Timeout: z.number().optional(),
StartPeriod: z.number().optional(),
Retries: z.number().optional(),
})
.strict();
const RestartPolicySwarmSchema = z
.object({
Condition: z.string().optional(),
Delay: z.number().optional(),
MaxAttempts: z.number().optional(),
Window: z.number().optional(),
})
.strict();
const PreferenceSchema = z
.object({
Spread: z.object({
SpreadDescriptor: z.string(),
}),
})
.strict();
const PlatformSchema = z
.object({
Architecture: z.string(),
OS: z.string(),
})
.strict();
const PlacementSwarmSchema = z
.object({
Constraints: z.array(z.string()).optional(),
Preferences: z.array(PreferenceSchema).optional(),
MaxReplicas: z.number().optional(),
Platforms: z.array(PlatformSchema).optional(),
})
.strict();
const UpdateConfigSwarmSchema = z
.object({
Parallelism: z.number(),
Delay: z.number().optional(),
FailureAction: z.string().optional(),
Monitor: z.number().optional(),
MaxFailureRatio: z.number().optional(),
Order: z.string(),
})
.strict();
const ReplicatedSchema = z
.object({
Replicas: z.number().optional(),
})
.strict();
const ReplicatedJobSchema = z
.object({
MaxConcurrent: z.number().optional(),
TotalCompletions: z.number().optional(),
})
.strict();
const ServiceModeSwarmSchema = z
.object({
Replicated: ReplicatedSchema.optional(),
Global: z.object({}).optional(),
ReplicatedJob: ReplicatedJobSchema.optional(),
GlobalJob: z.object({}).optional(),
})
.strict();
const NetworkSwarmSchema = z.array(
z
.object({
Target: z.string().optional(),
Aliases: z.array(z.string()).optional(),
DriverOpts: z.object({}).optional(),
})
.strict(),
);
const LabelsSwarmSchema = z.record(z.string());
const createSchema = createInsertSchema(applications, {
appName: z.string(),
createdAt: z.string(),
applicationId: z.string(),
autoDeploy: z.boolean(),
env: z.string().optional(),
buildArgs: z.string().optional(),
name: z.string().min(1),
description: z.string().optional(),
memoryReservation: z.string().optional(),
memoryLimit: z.string().optional(),
cpuReservation: z.string().optional(),
cpuLimit: z.string().optional(),
title: z.string().optional(),
enabled: z.boolean().optional(),
subtitle: z.string().optional(),
dockerImage: z.string().optional(),
username: z.string().optional(),
isPreviewDeploymentsActive: z.boolean().optional(),
password: z.string().optional(),
registryUrl: z.string().optional(),
customGitSSHKeyId: z.string().optional(),
repository: z.string().optional(),
dockerfile: z.string().optional(),
branch: z.string().optional(),
customGitBranch: z.string().optional(),
customGitBuildPath: z.string().optional(),
customGitUrl: z.string().optional(),
buildPath: z.string().optional(),
projectId: z.string(),
sourceType: z.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"]).optional(),
applicationStatus: z.enum(["idle", "running", "done", "error"]),
buildType: z.enum([
"dockerfile",
"heroku_buildpacks",
"paketo_buildpacks",
"nixpacks",
"static",
"railpack",
]),
herokuVersion: z.string().optional(),
publishDirectory: z.string().optional(),
owner: z.string(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
placementSwarm: PlacementSwarmSchema.nullable(),
updateConfigSwarm: UpdateConfigSwarmSchema.nullable(),
rollbackConfigSwarm: UpdateConfigSwarmSchema.nullable(),
modeSwarm: ServiceModeSwarmSchema.nullable(),
labelsSwarm: LabelsSwarmSchema.nullable(),
networkSwarm: NetworkSwarmSchema.nullable(),
previewPort: z.number().optional(),
previewEnv: z.string().optional(),
previewBuildArgs: z.string().optional(),
previewWildcard: z.string().optional(),
previewLimit: z.number().optional(),
previewHttps: z.boolean().optional(),
previewPath: z.string().optional(),
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
watchPaths: z.array(z.string()).optional(),
cleanCache: z.boolean().optional(),
});
export const apiCreateApplication = createSchema.pick({
name: true,
appName: true,
description: true,
projectId: true,
serverId: true,
});
export const apiFindOneApplication = createSchema
.pick({
applicationId: true,
})
.required();
export const apiReloadApplication = createSchema
.pick({
appName: true,
applicationId: true,
})
.required();
export const apiSaveBuildType = createSchema
.pick({
applicationId: true,
buildType: true,
dockerfile: true,
dockerContextPath: true,
dockerBuildStage: true,
herokuVersion: true,
})
.required()
.merge(createSchema.pick({ publishDirectory: true }));
export const apiSaveGithubProvider = createSchema
.pick({
applicationId: true,
repository: true,
branch: true,
owner: true,
buildPath: true,
githubId: true,
watchPaths: true,
})
.required();
export const apiSaveGitlabProvider = createSchema
.pick({
applicationId: true,
gitlabBranch: true,
gitlabBuildPath: true,
gitlabOwner: true,
gitlabRepository: true,
gitlabId: true,
gitlabProjectId: true,
gitlabPathNamespace: true,
watchPaths: true,
})
.required();
export const apiSaveBitbucketProvider = createSchema
.pick({
bitbucketBranch: true,
bitbucketBuildPath: true,
bitbucketOwner: true,
bitbucketRepository: true,
bitbucketId: true,
applicationId: true,
watchPaths: true,
})
.required();
export const apiSaveGiteaProvider = createSchema
.pick({
applicationId: true,
giteaBranch: true,
giteaBuildPath: true,
giteaOwner: true,
giteaRepository: true,
giteaId: true,
giteaProjectId: true,
giteaPathNamespace: true,
})
.required();
export const apiSaveDockerProvider = createSchema
.pick({
dockerImage: true,
applicationId: true,
username: true,
password: true,
registryUrl: true,
})
.required();
export const apiSaveGitProvider = createSchema
.pick({
customGitBranch: true,
applicationId: true,
customGitBuildPath: true,
customGitUrl: true,
watchPaths: true,
})
.required()
.merge(
createSchema.pick({
customGitSSHKeyId: true,
}),
);
export const apiSaveEnvironmentVariables = createSchema
.pick({
applicationId: true,
env: true,
buildArgs: true,
})
.required();
export const apiFindMonitoringStats = createSchema
.pick({
appName: true,
})
.required();
export const apiUpdateApplication = createSchema
.partial()
.extend({
applicationId: z.string().min(1),
})
.omit({ serverId: true });