Merge branch 'canary' into feat/add-mattermost-notification-provider

Resolves merge conflicts between mattermost notification provider (this PR)
and new canary features (resend, teams, SSO, patches, etc).

All notification providers are now included:
- slack, telegram, discord, email, gotify, ntfy
- mattermost (this PR)
- resend, pushover, custom, lark, teams (from canary)
This commit is contained in:
Hootan
2026-02-28 00:49:31 +01:00
463 changed files with 99829 additions and 8878 deletions

View File

@@ -26,7 +26,8 @@ if (DATABASE_URL) {
password,
)}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}`;
} else {
console.warn(`
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.
@@ -34,6 +35,13 @@ if (DATABASE_URL) {
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
`);
dbUrl =
"postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
}
if (process.env.NODE_ENV === "production") {
dbUrl =
"postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
} else {
dbUrl =
"postgres://dokploy:amukds4wi9001583845717ad2@localhost:5432/dokploy";
}
}

View File

@@ -1,24 +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(dbUrl), {
// En producción no usamos global cache
dbConnection = drizzle(postgres(dbUrl), {
schema,
});
} else {
if (!global.db)
global.db = drizzle(postgres(dbUrl), {
// 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 };

View File

@@ -9,6 +9,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", {
@@ -78,6 +79,7 @@ export const organizationRelations = relations(
servers: many(server),
projects: many(projects),
members: many(member),
ssoProviders: many(ssoProvider),
}),
);
@@ -153,6 +155,7 @@ export const invitation = pgTable("invitation", {
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
teamId: text("team_id"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export const invitationRelations = relations(invitation, ({ one }) => ({

View File

@@ -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",
@@ -156,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
@@ -172,6 +175,7 @@ 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")
@@ -283,11 +287,17 @@ export const applicationsRelations = relations(
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(),
@@ -321,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",
@@ -359,6 +370,7 @@ const createSchema = createInsertSchema(applications, {
cleanCache: z.boolean().optional(),
stopGracePeriodSwarm: z.bigint().nullable(),
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
});
export const apiCreateApplication = createSchema.pick({
@@ -369,11 +381,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({
@@ -509,11 +519,9 @@ export const apiSaveEnvironmentVariables = createSchema
})
.required();
export const apiFindMonitoringStats = createSchema
.pick({
appName: true,
})
.required();
export const apiFindMonitoringStats = z.object({
appName: z.string().min(1),
});
export const apiUpdateApplication = createSchema
.partial()

View File

@@ -165,11 +165,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({

View File

@@ -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",
@@ -143,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(),
@@ -156,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({

View File

@@ -126,7 +126,6 @@ const schema = createInsertSchema(deployments, {
previewDeploymentId: z.string(),
buildServerId: z.string(),
});
export const apiCreateDeployment = schema
.pick({
title: true,
@@ -210,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",
]),
});

View File

@@ -61,11 +61,9 @@ export const apiCreateDestination = createSchema
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({

View File

@@ -70,7 +70,11 @@ 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,
@@ -88,11 +92,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,

View File

@@ -1,6 +1,5 @@
import { relations } from "drizzle-orm";
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { applications } from "./application";
@@ -46,46 +45,30 @@ export const environmentRelations = relations(
}),
);
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),
})
.omit({
isDefault: true,
});
export const apiDuplicateEnvironment = createSchema
.pick({
environmentId: true,
name: true,
description: true,
})
.required({
environmentId: true,
name: true,
});

View File

@@ -1,6 +1,5 @@
import { relations } from "drizzle-orm";
import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
@@ -62,10 +61,6 @@ export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
}),
}));
const createSchema = createInsertSchema(gitProvider);
export const apiRemoveGitProvider = createSchema
.extend({
gitProviderId: z.string().min(1),
})
.pick({ gitProviderId: true });
export const apiRemoveGitProvider = z.object({
gitProviderId: z.string().min(1),
});

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ 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 +33,7 @@ export * from "./server";
export * from "./session";
export * from "./shared";
export * from "./ssh-key";
export * from "./sso";
export * from "./user";
export * from "./utils";
export * from "./volume-backups";

View File

@@ -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")
@@ -67,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()
@@ -96,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),
@@ -136,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({

View File

@@ -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")
@@ -70,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()
@@ -98,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),
@@ -133,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({

View File

@@ -130,11 +130,9 @@ export const apiCreateMount = createSchema
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({
@@ -148,12 +146,20 @@ export const apiRemoveMount = createSchema
export const apiFindMountByApplicationId = createSchema
.extend({
serviceId: z.string().min(1),
serviceType: z.enum([
"application",
"postgres",
"mysql",
"mariadb",
"mongo",
"redis",
"compose",
]),
})
.pick({
serviceId: true,
serviceType: true,
})
.required();
});
export const apiUpdateMount = createSchema.partial().extend({
mountId: z.string().min(1),

View File

@@ -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")
@@ -65,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()
@@ -93,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),
@@ -133,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({

View File

@@ -17,12 +17,14 @@ export const notificationType = pgEnum("notificationType", [
"telegram",
"discord",
"email",
"resend",
"gotify",
"ntfy",
"mattermost",
"pushover",
"custom",
"lark",
"teams",
]);
export const notifications = pgTable("notification", {
@@ -54,6 +56,9 @@ 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",
}),
@@ -72,6 +77,9 @@ export const notifications = pgTable("notification", {
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" }),
@@ -118,6 +126,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()
@@ -179,6 +197,14 @@ export const pushover = pgTable("pushover", {
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],
@@ -196,6 +222,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],
@@ -220,6 +250,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
fields: [notifications.pushoverId],
references: [pushover.pushoverId],
}),
teams: one(teams, {
fields: [notifications.teamsId],
references: [teams.teamsId],
}),
organization: one(organization, {
fields: [notifications.organizationId],
references: [organization.id],
@@ -353,6 +387,36 @@ export const apiTestEmailConnection = apiCreateEmail.pick({
fromAddress: true,
});
export const apiCreateResend = notificationsSchema
.pick({
appBuildError: true,
databaseBackup: 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,
@@ -422,6 +486,7 @@ export const apiCreateMattermost = notificationsSchema
.pick({
appBuildError: true,
databaseBackup: true,
volumeBackup: true,
dokployRestart: true,
name: true,
appDeploy: true,
@@ -438,6 +503,7 @@ export const apiCreateMattermost = notificationsSchema
webhookUrl: true,
appBuildError: true,
databaseBackup: true,
volumeBackup: true,
dokployRestart: true,
appDeploy: true,
dockerCleanup: true,
@@ -461,11 +527,9 @@ export const apiTestMattermostConnection = apiCreateMattermost
username: z.string().optional(),
});
export const apiFindOneNotification = notificationsSchema
.pick({
notificationId: true,
})
.required();
export const apiFindOneNotification = z.object({
notificationId: z.string().min(1),
});
export const apiCreateCustom = notificationsSchema
.pick({
@@ -480,7 +544,7 @@ export const apiCreateCustom = notificationsSchema
})
.extend({
endpoint: z.string().min(1),
headers: z.record(z.string()).optional(),
headers: z.record(z.string(), z.string()).optional(),
});
export const apiUpdateCustom = apiCreateCustom.partial().extend({
@@ -491,7 +555,7 @@ export const apiUpdateCustom = apiCreateCustom.partial().extend({
export const apiTestCustomConnection = z.object({
endpoint: z.string().min(1),
headers: z.record(z.string()).optional(),
headers: z.record(z.string(), z.string()).optional(),
});
export const apiCreateLark = notificationsSchema
@@ -520,6 +584,32 @@ export const apiTestLarkConnection = apiCreateLark.pick({
webhookUrl: true,
});
export const apiCreateTeams = notificationsSchema
.pick({
appBuildError: true,
databaseBackup: 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,
@@ -595,6 +685,7 @@ 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(),

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

View File

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

View File

@@ -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")
@@ -65,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()
@@ -94,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@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
@@ -126,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({

View File

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

View File

@@ -43,12 +43,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,

View File

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

View File

@@ -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")
@@ -64,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")
@@ -88,7 +91,12 @@ 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(),
@@ -115,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({

View File

@@ -94,11 +94,9 @@ export const apiRemoveRegistry = createSchema
})
.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),

View File

@@ -471,6 +471,7 @@ table git_provider {
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
@@ -497,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

View File

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

View File

@@ -133,6 +133,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
@@ -147,11 +148,9 @@ export const apiCreateServer = createSchema
})
.required();
export const apiFindOneServer = createSchema
.pick({
serverId: true,
})
.required();
export const apiFindOneServer = z.object({
serverId: z.string().min(1),
});
export const apiRemoveServer = createSchema
.pick({

View File

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

View File

@@ -86,6 +86,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 +175,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 +197,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);

View File

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

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

View File

@@ -14,6 +14,7 @@ import { account, apikey, organization } from "./account";
import { backups } from "./backups";
import { projects } from "./project";
import { schedules } from "./schedule";
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.
@@ -53,9 +54,18 @@ export const user = pgTable("user", {
// Metrics
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
allowImpersonation: boolean("allowImpersonation").notNull().default(false),
// Enterprise / proprietary features
enableEnterpriseFeatures: boolean("enableEnterpriseFeatures")
.notNull()
.default(false),
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 usersRelations = relations(user, ({ one, many }) => ({
@@ -66,6 +76,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),
}));
@@ -75,6 +86,8 @@ const createSchema = createInsertSchema(user, {
isRegistered: z.boolean().optional(),
}).omit({
role: true,
trustedOrigins: true,
isValidEnterpriseLicense: true,
});
export const apiCreateUserInvitation = createSchema.pick({}).extend({

View File

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