diff --git a/apps/dokploy/components/icons/notification-icons.tsx b/apps/dokploy/components/icons/notification-icons.tsx
index 87bb6c0ae..0f3a37729 100644
--- a/apps/dokploy/components/icons/notification-icons.tsx
+++ b/apps/dokploy/components/icons/notification-icons.tsx
@@ -1,237 +1,237 @@
-import { cn } from "@/lib/utils";
-
-interface Props {
- className?: string;
-}
-export const SlackIcon = ({ className }: Props) => {
- return (
-
- );
-};
-
-export const TelegramIcon = ({ className }: Props) => {
- return (
-
- );
-};
-
-export const DiscordIcon = ({ className }: Props) => {
- return (
-
- );
-};
-export const LarkIcon = ({ className }: Props) => {
- return (
-
- );
-};
-export const GotifyIcon = ({ className }: Props) => {
- return (
-
- );
-};
-
-export const NtfyIcon = ({ className }: Props) => {
- return (
-
- );
-};
-
+import { cn } from "@/lib/utils";
+
+interface Props {
+ className?: string;
+}
+export const SlackIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+
+export const TelegramIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+
+export const DiscordIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+export const LarkIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+export const GotifyIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+
+export const NtfyIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+
export const PushoverIcon = ({ className }: Props) => {
return (
);
};
+
+export const ResendIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
diff --git a/apps/dokploy/drizzle/0137_worried_shriek.sql b/apps/dokploy/drizzle/0137_worried_shriek.sql
new file mode 100644
index 000000000..ae3629c6e
--- /dev/null
+++ b/apps/dokploy/drizzle/0137_worried_shriek.sql
@@ -0,0 +1,10 @@
+ALTER TYPE "public"."notificationType" ADD VALUE 'resend' BEFORE 'gotify';--> statement-breakpoint
+CREATE TABLE "resend" (
+ "resendId" text PRIMARY KEY NOT NULL,
+ "apiKey" text NOT NULL,
+ "fromAddress" text NOT NULL,
+ "toAddress" text[] NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "notification" ADD COLUMN "resendId" text;--> statement-breakpoint
+ALTER TABLE "notification" ADD CONSTRAINT "notification_resendId_resend_resendId_fk" FOREIGN KEY ("resendId") REFERENCES "public"."resend"("resendId") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/0137_snapshot.json b/apps/dokploy/drizzle/meta/0137_snapshot.json
new file mode 100644
index 000000000..5698c3339
--- /dev/null
+++ b/apps/dokploy/drizzle/meta/0137_snapshot.json
@@ -0,0 +1,7107 @@
+{
+ "id": "3f56c8d0-1d0d-4791-9c4a-06be622dc244",
+ "prevId": "5958b029-1fb9-4a44-be24-c96b4e899b84",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is2FAEnabled": {
+ "name": "is2FAEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resetPasswordToken": {
+ "name": "resetPasswordToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resetPasswordExpiresAt": {
+ "name": "resetPasswordExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationToken": {
+ "name": "confirmationToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmationExpiresAt": {
+ "name": "confirmationExpiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.apikey": {
+ "name": "apikey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start": {
+ "name": "start",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refill_interval": {
+ "name": "refill_interval",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refill_amount": {
+ "name": "refill_amount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refill_at": {
+ "name": "last_refill_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_enabled": {
+ "name": "rate_limit_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_time_window": {
+ "name": "rate_limit_time_window",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rate_limit_max": {
+ "name": "rate_limit_max",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "request_count": {
+ "name": "request_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "remaining": {
+ "name": "remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "apikey_user_id_user_id_fk": {
+ "name": "apikey_user_id_user_id_fk",
+ "tableFrom": "apikey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": [
+ "inviter_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateProjects": {
+ "name": "canCreateProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToSSHKeys": {
+ "name": "canAccessToSSHKeys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateServices": {
+ "name": "canCreateServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteProjects": {
+ "name": "canDeleteProjects",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteServices": {
+ "name": "canDeleteServices",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToDocker": {
+ "name": "canAccessToDocker",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToAPI": {
+ "name": "canAccessToAPI",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToGitProviders": {
+ "name": "canAccessToGitProviders",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canAccessToTraefikFiles": {
+ "name": "canAccessToTraefikFiles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canDeleteEnvironments": {
+ "name": "canDeleteEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "canCreateEnvironments": {
+ "name": "canCreateEnvironments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accesedProjects": {
+ "name": "accesedProjects",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accessedEnvironments": {
+ "name": "accessedEnvironments",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ },
+ "accesedServices": {
+ "name": "accesedServices",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "ARRAY[]::text[]"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organization_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "organization_owner_id_user_id_fk": {
+ "name": "organization_owner_id_user_id_fk",
+ "tableFrom": "organization",
+ "tableTo": "user",
+ "columnsFrom": [
+ "owner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "organization_slug_unique": {
+ "name": "organization_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.two_factor": {
+ "name": "two_factor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backup_codes": {
+ "name": "backup_codes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "two_factor_user_id_user_id_fk": {
+ "name": "two_factor_user_id_user_id_fk",
+ "tableFrom": "two_factor",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ai": {
+ "name": "ai",
+ "schema": "",
+ "columns": {
+ "aiId": {
+ "name": "aiId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiUrl": {
+ "name": "apiUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isEnabled": {
+ "name": "isEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ai_organizationId_organization_id_fk": {
+ "name": "ai_organizationId_organization_id_fk",
+ "tableFrom": "ai",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.application": {
+ "name": "application",
+ "schema": "",
+ "columns": {
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewEnv": {
+ "name": "previewEnv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildArgs": {
+ "name": "previewBuildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewBuildSecrets": {
+ "name": "previewBuildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLabels": {
+ "name": "previewLabels",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewWildcard": {
+ "name": "previewWildcard",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewPort": {
+ "name": "previewPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "previewHttps": {
+ "name": "previewHttps",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "previewPath": {
+ "name": "previewPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "previewCustomCertResolver": {
+ "name": "previewCustomCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewLimit": {
+ "name": "previewLimit",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "isPreviewDeploymentsActive": {
+ "name": "isPreviewDeploymentsActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewRequireCollaboratorPermissions": {
+ "name": "previewRequireCollaboratorPermissions",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": true
+ },
+ "rollbackActive": {
+ "name": "rollbackActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "buildArgs": {
+ "name": "buildArgs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildSecrets": {
+ "name": "buildSecrets",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subtitle": {
+ "name": "subtitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "cleanCache": {
+ "name": "cleanCache",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildPath": {
+ "name": "buildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBuildPath": {
+ "name": "gitlabBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBuildPath": {
+ "name": "giteaBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBuildPath": {
+ "name": "bitbucketBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBuildPath": {
+ "name": "customGitBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerfile": {
+ "name": "dockerfile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerContextPath": {
+ "name": "dockerContextPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerBuildStage": {
+ "name": "dockerBuildStage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dropBuildPath": {
+ "name": "dropBuildPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "buildType": {
+ "name": "buildType",
+ "type": "buildType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'nixpacks'"
+ },
+ "railpackVersion": {
+ "name": "railpackVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0.15.4'"
+ },
+ "herokuVersion": {
+ "name": "herokuVersion",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'24'"
+ },
+ "publishDirectory": {
+ "name": "publishDirectory",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isStaticSpa": {
+ "name": "isStaticSpa",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createEnvFile": {
+ "name": "createEnvFile",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackRegistryId": {
+ "name": "rollbackRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildRegistryId": {
+ "name": "buildRegistryId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "application_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "application_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "application",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_registryId_registry_registryId_fk": {
+ "name": "application_registryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "registryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_rollbackRegistryId_registry_registryId_fk": {
+ "name": "application_rollbackRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "rollbackRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_environmentId_environment_environmentId_fk": {
+ "name": "application_environmentId_environment_environmentId_fk",
+ "tableFrom": "application",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_githubId_github_githubId_fk": {
+ "name": "application_githubId_github_githubId_fk",
+ "tableFrom": "application",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_gitlabId_gitlab_gitlabId_fk": {
+ "name": "application_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_giteaId_gitea_giteaId_fk": {
+ "name": "application_giteaId_gitea_giteaId_fk",
+ "tableFrom": "application",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "application_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "application",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_serverId_server_serverId_fk": {
+ "name": "application_serverId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "application_buildServerId_server_serverId_fk": {
+ "name": "application_buildServerId_server_serverId_fk",
+ "tableFrom": "application",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "application_buildRegistryId_registry_registryId_fk": {
+ "name": "application_buildRegistryId_registry_registryId_fk",
+ "tableFrom": "application",
+ "tableTo": "registry",
+ "columnsFrom": [
+ "buildRegistryId"
+ ],
+ "columnsTo": [
+ "registryId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "application_appName_unique": {
+ "name": "application_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.backup": {
+ "name": "backup",
+ "schema": "",
+ "columns": {
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schedule": {
+ "name": "schedule",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "database": {
+ "name": "database",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupType": {
+ "name": "backupType",
+ "type": "backupType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'database'"
+ },
+ "databaseType": {
+ "name": "databaseType",
+ "type": "databaseType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "backup_destinationId_destination_destinationId_fk": {
+ "name": "backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_composeId_compose_composeId_fk": {
+ "name": "backup_composeId_compose_composeId_fk",
+ "tableFrom": "backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_postgresId_postgres_postgresId_fk": {
+ "name": "backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_mongoId_mongo_mongoId_fk": {
+ "name": "backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "backup_userId_user_id_fk": {
+ "name": "backup_userId_user_id_fk",
+ "tableFrom": "backup",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "backup_appName_unique": {
+ "name": "backup_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bitbucket": {
+ "name": "bitbucket",
+ "schema": "",
+ "columns": {
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "bitbucketUsername": {
+ "name": "bitbucketUsername",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketEmail": {
+ "name": "bitbucketEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appPassword": {
+ "name": "appPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketWorkspaceName": {
+ "name": "bitbucketWorkspaceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bitbucket_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "bitbucket_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "bitbucket",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.certificate": {
+ "name": "certificate",
+ "schema": "",
+ "columns": {
+ "certificateId": {
+ "name": "certificateId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificateData": {
+ "name": "certificateData",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "certificatePath": {
+ "name": "certificatePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "autoRenew": {
+ "name": "autoRenew",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "certificate_organizationId_organization_id_fk": {
+ "name": "certificate_organizationId_organization_id_fk",
+ "tableFrom": "certificate",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "certificate_serverId_server_serverId_fk": {
+ "name": "certificate_serverId_server_serverId_fk",
+ "tableFrom": "certificate",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "certificate_certificatePath_unique": {
+ "name": "certificate_certificatePath_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "certificatePath"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.compose": {
+ "name": "compose",
+ "schema": "",
+ "columns": {
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeFile": {
+ "name": "composeFile",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "refreshToken": {
+ "name": "refreshToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sourceType": {
+ "name": "sourceType",
+ "type": "sourceTypeCompose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "composeType": {
+ "name": "composeType",
+ "type": "composeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'docker-compose'"
+ },
+ "repository": {
+ "name": "repository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner": {
+ "name": "owner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "autoDeploy": {
+ "name": "autoDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabProjectId": {
+ "name": "gitlabProjectId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabRepository": {
+ "name": "gitlabRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabOwner": {
+ "name": "gitlabOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabBranch": {
+ "name": "gitlabBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabPathNamespace": {
+ "name": "gitlabPathNamespace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepository": {
+ "name": "bitbucketRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketRepositorySlug": {
+ "name": "bitbucketRepositorySlug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketOwner": {
+ "name": "bitbucketOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketBranch": {
+ "name": "bitbucketBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaRepository": {
+ "name": "giteaRepository",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaOwner": {
+ "name": "giteaOwner",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaBranch": {
+ "name": "giteaBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitUrl": {
+ "name": "customGitUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitBranch": {
+ "name": "customGitBranch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customGitSSHKeyId": {
+ "name": "customGitSSHKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "enableSubmodules": {
+ "name": "enableSubmodules",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "composePath": {
+ "name": "composePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'./docker-compose.yml'"
+ },
+ "suffix": {
+ "name": "suffix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "randomize": {
+ "name": "randomize",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeployment": {
+ "name": "isolatedDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "isolatedDeploymentsVolume": {
+ "name": "isolatedDeploymentsVolume",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "triggerType": {
+ "name": "triggerType",
+ "type": "triggerType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'push'"
+ },
+ "composeStatus": {
+ "name": "composeStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "watchPaths": {
+ "name": "watchPaths",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bitbucketId": {
+ "name": "bitbucketId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk": {
+ "name": "compose_customGitSSHKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "compose",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "customGitSSHKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_environmentId_environment_environmentId_fk": {
+ "name": "compose_environmentId_environment_environmentId_fk",
+ "tableFrom": "compose",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "compose_githubId_github_githubId_fk": {
+ "name": "compose_githubId_github_githubId_fk",
+ "tableFrom": "compose",
+ "tableTo": "github",
+ "columnsFrom": [
+ "githubId"
+ ],
+ "columnsTo": [
+ "githubId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_gitlabId_gitlab_gitlabId_fk": {
+ "name": "compose_gitlabId_gitlab_gitlabId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitlab",
+ "columnsFrom": [
+ "gitlabId"
+ ],
+ "columnsTo": [
+ "gitlabId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_bitbucketId_bitbucket_bitbucketId_fk": {
+ "name": "compose_bitbucketId_bitbucket_bitbucketId_fk",
+ "tableFrom": "compose",
+ "tableTo": "bitbucket",
+ "columnsFrom": [
+ "bitbucketId"
+ ],
+ "columnsTo": [
+ "bitbucketId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_giteaId_gitea_giteaId_fk": {
+ "name": "compose_giteaId_gitea_giteaId_fk",
+ "tableFrom": "compose",
+ "tableTo": "gitea",
+ "columnsFrom": [
+ "giteaId"
+ ],
+ "columnsTo": [
+ "giteaId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "compose_serverId_server_serverId_fk": {
+ "name": "compose_serverId_server_serverId_fk",
+ "tableFrom": "compose",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deployment": {
+ "name": "deployment",
+ "schema": "",
+ "columns": {
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "deploymentStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'running'"
+ },
+ "logPath": {
+ "name": "logPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pid": {
+ "name": "pid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "isPreviewDeployment": {
+ "name": "isPreviewDeployment",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "startedAt": {
+ "name": "startedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "finishedAt": {
+ "name": "finishedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backupId": {
+ "name": "backupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "buildServerId": {
+ "name": "buildServerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployment_applicationId_application_applicationId_fk": {
+ "name": "deployment_applicationId_application_applicationId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_composeId_compose_composeId_fk": {
+ "name": "deployment_composeId_compose_composeId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_serverId_server_serverId_fk": {
+ "name": "deployment_serverId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "deployment_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_scheduleId_schedule_scheduleId_fk": {
+ "name": "deployment_scheduleId_schedule_scheduleId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "schedule",
+ "columnsFrom": [
+ "scheduleId"
+ ],
+ "columnsTo": [
+ "scheduleId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_backupId_backup_backupId_fk": {
+ "name": "deployment_backupId_backup_backupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "backup",
+ "columnsFrom": [
+ "backupId"
+ ],
+ "columnsTo": [
+ "backupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_rollbackId_rollback_rollbackId_fk": {
+ "name": "deployment_rollbackId_rollback_rollbackId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "rollback",
+ "columnsFrom": [
+ "rollbackId"
+ ],
+ "columnsTo": [
+ "rollbackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_volumeBackupId_volume_backup_volumeBackupId_fk": {
+ "name": "deployment_volumeBackupId_volume_backup_volumeBackupId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "volume_backup",
+ "columnsFrom": [
+ "volumeBackupId"
+ ],
+ "columnsTo": [
+ "volumeBackupId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployment_buildServerId_server_serverId_fk": {
+ "name": "deployment_buildServerId_server_serverId_fk",
+ "tableFrom": "deployment",
+ "tableTo": "server",
+ "columnsFrom": [
+ "buildServerId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.destination": {
+ "name": "destination",
+ "schema": "",
+ "columns": {
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accessKey": {
+ "name": "accessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secretAccessKey": {
+ "name": "secretAccessKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "bucket": {
+ "name": "bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "destination_organizationId_organization_id_fk": {
+ "name": "destination_organizationId_organization_id_fk",
+ "tableFrom": "destination",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.domain": {
+ "name": "domain",
+ "schema": "",
+ "columns": {
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3000
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domainType": {
+ "name": "domainType",
+ "type": "domainType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'application'"
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customCertResolver": {
+ "name": "customCertResolver",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "internalPath": {
+ "name": "internalPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'/'"
+ },
+ "stripPath": {
+ "name": "stripPath",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "domain_composeId_compose_composeId_fk": {
+ "name": "domain_composeId_compose_composeId_fk",
+ "tableFrom": "domain",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_applicationId_application_applicationId_fk": {
+ "name": "domain_applicationId_application_applicationId_fk",
+ "tableFrom": "domain",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk": {
+ "name": "domain_previewDeploymentId_preview_deployments_previewDeploymentId_fk",
+ "tableFrom": "domain",
+ "tableTo": "preview_deployments",
+ "columnsFrom": [
+ "previewDeploymentId"
+ ],
+ "columnsTo": [
+ "previewDeploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "isDefault": {
+ "name": "isDefault",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_projectId_project_projectId_fk": {
+ "name": "environment_projectId_project_projectId_fk",
+ "tableFrom": "environment",
+ "tableTo": "project",
+ "columnsFrom": [
+ "projectId"
+ ],
+ "columnsTo": [
+ "projectId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.git_provider": {
+ "name": "git_provider",
+ "schema": "",
+ "columns": {
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerType": {
+ "name": "providerType",
+ "type": "gitProviderType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'github'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "git_provider_organizationId_organization_id_fk": {
+ "name": "git_provider_organizationId_organization_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "git_provider_userId_user_id_fk": {
+ "name": "git_provider_userId_user_id_fk",
+ "tableFrom": "git_provider",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitea": {
+ "name": "gitea",
+ "schema": "",
+ "columns": {
+ "giteaId": {
+ "name": "giteaId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "giteaUrl": {
+ "name": "giteaUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitea.com'"
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'repo,repo:status,read:user,read:org'"
+ },
+ "last_authenticated_at": {
+ "name": "last_authenticated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitea_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitea_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitea",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github": {
+ "name": "github",
+ "schema": "",
+ "columns": {
+ "githubId": {
+ "name": "githubId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "githubAppName": {
+ "name": "githubAppName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubAppId": {
+ "name": "githubAppId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientId": {
+ "name": "githubClientId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubClientSecret": {
+ "name": "githubClientSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubInstallationId": {
+ "name": "githubInstallationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubPrivateKey": {
+ "name": "githubPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "githubWebhookSecret": {
+ "name": "githubWebhookSecret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "github_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "github_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "github",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gitlab": {
+ "name": "gitlab",
+ "schema": "",
+ "columns": {
+ "gitlabId": {
+ "name": "gitlabId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "gitlabUrl": {
+ "name": "gitlabUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'https://gitlab.com'"
+ },
+ "application_id": {
+ "name": "application_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gitProviderId": {
+ "name": "gitProviderId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "gitlab_gitProviderId_git_provider_gitProviderId_fk": {
+ "name": "gitlab_gitProviderId_git_provider_gitProviderId_fk",
+ "tableFrom": "gitlab",
+ "tableTo": "git_provider",
+ "columnsFrom": [
+ "gitProviderId"
+ ],
+ "columnsTo": [
+ "gitProviderId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mariadb": {
+ "name": "mariadb",
+ "schema": "",
+ "columns": {
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mariadb_environmentId_environment_environmentId_fk": {
+ "name": "mariadb_environmentId_environment_environmentId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mariadb_serverId_server_serverId_fk": {
+ "name": "mariadb_serverId_server_serverId_fk",
+ "tableFrom": "mariadb",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mariadb_appName_unique": {
+ "name": "mariadb_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mongo": {
+ "name": "mongo",
+ "schema": "",
+ "columns": {
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicaSets": {
+ "name": "replicaSets",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mongo_environmentId_environment_environmentId_fk": {
+ "name": "mongo_environmentId_environment_environmentId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mongo_serverId_server_serverId_fk": {
+ "name": "mongo_serverId_server_serverId_fk",
+ "tableFrom": "mongo",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mongo_appName_unique": {
+ "name": "mongo_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mount": {
+ "name": "mount",
+ "schema": "",
+ "columns": {
+ "mountId": {
+ "name": "mountId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "mountType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hostPath": {
+ "name": "hostPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filePath": {
+ "name": "filePath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "mountPath": {
+ "name": "mountPath",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mount_applicationId_application_applicationId_fk": {
+ "name": "mount_applicationId_application_applicationId_fk",
+ "tableFrom": "mount",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_postgresId_postgres_postgresId_fk": {
+ "name": "mount_postgresId_postgres_postgresId_fk",
+ "tableFrom": "mount",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mariadbId_mariadb_mariadbId_fk": {
+ "name": "mount_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mongoId_mongo_mongoId_fk": {
+ "name": "mount_mongoId_mongo_mongoId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_mysqlId_mysql_mysqlId_fk": {
+ "name": "mount_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "mount",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_redisId_redis_redisId_fk": {
+ "name": "mount_redisId_redis_redisId_fk",
+ "tableFrom": "mount",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mount_composeId_compose_composeId_fk": {
+ "name": "mount_composeId_compose_composeId_fk",
+ "tableFrom": "mount",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mysql": {
+ "name": "mysql",
+ "schema": "",
+ "columns": {
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rootPassword": {
+ "name": "rootPassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mysql_environmentId_environment_environmentId_fk": {
+ "name": "mysql_environmentId_environment_environmentId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mysql_serverId_server_serverId_fk": {
+ "name": "mysql_serverId_server_serverId_fk",
+ "tableFrom": "mysql",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mysql_appName_unique": {
+ "name": "mysql_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom": {
+ "name": "custom",
+ "schema": "",
+ "columns": {
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "headers": {
+ "name": "headers",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.discord": {
+ "name": "discord",
+ "schema": "",
+ "columns": {
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.email": {
+ "name": "email",
+ "schema": "",
+ "columns": {
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "smtpServer": {
+ "name": "smtpServer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "smtpPort": {
+ "name": "smtpPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.gotify": {
+ "name": "gotify",
+ "schema": "",
+ "columns": {
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appToken": {
+ "name": "appToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 5
+ },
+ "decoration": {
+ "name": "decoration",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lark": {
+ "name": "lark",
+ "schema": "",
+ "columns": {
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notification": {
+ "name": "notification",
+ "schema": "",
+ "columns": {
+ "notificationId": {
+ "name": "notificationId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appDeploy": {
+ "name": "appDeploy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "appBuildError": {
+ "name": "appBuildError",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "databaseBackup": {
+ "name": "databaseBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "volumeBackup": {
+ "name": "volumeBackup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dokployRestart": {
+ "name": "dokployRestart",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "dockerCleanup": {
+ "name": "dockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "serverThreshold": {
+ "name": "serverThreshold",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "notificationType": {
+ "name": "notificationType",
+ "type": "notificationType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discordId": {
+ "name": "discordId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "emailId": {
+ "name": "emailId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gotifyId": {
+ "name": "gotifyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "customId": {
+ "name": "customId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "larkId": {
+ "name": "larkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_slackId_slack_slackId_fk": {
+ "name": "notification_slackId_slack_slackId_fk",
+ "tableFrom": "notification",
+ "tableTo": "slack",
+ "columnsFrom": [
+ "slackId"
+ ],
+ "columnsTo": [
+ "slackId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_telegramId_telegram_telegramId_fk": {
+ "name": "notification_telegramId_telegram_telegramId_fk",
+ "tableFrom": "notification",
+ "tableTo": "telegram",
+ "columnsFrom": [
+ "telegramId"
+ ],
+ "columnsTo": [
+ "telegramId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_discordId_discord_discordId_fk": {
+ "name": "notification_discordId_discord_discordId_fk",
+ "tableFrom": "notification",
+ "tableTo": "discord",
+ "columnsFrom": [
+ "discordId"
+ ],
+ "columnsTo": [
+ "discordId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_emailId_email_emailId_fk": {
+ "name": "notification_emailId_email_emailId_fk",
+ "tableFrom": "notification",
+ "tableTo": "email",
+ "columnsFrom": [
+ "emailId"
+ ],
+ "columnsTo": [
+ "emailId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_resendId_resend_resendId_fk": {
+ "name": "notification_resendId_resend_resendId_fk",
+ "tableFrom": "notification",
+ "tableTo": "resend",
+ "columnsFrom": [
+ "resendId"
+ ],
+ "columnsTo": [
+ "resendId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_gotifyId_gotify_gotifyId_fk": {
+ "name": "notification_gotifyId_gotify_gotifyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "gotify",
+ "columnsFrom": [
+ "gotifyId"
+ ],
+ "columnsTo": [
+ "gotifyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_ntfyId_ntfy_ntfyId_fk": {
+ "name": "notification_ntfyId_ntfy_ntfyId_fk",
+ "tableFrom": "notification",
+ "tableTo": "ntfy",
+ "columnsFrom": [
+ "ntfyId"
+ ],
+ "columnsTo": [
+ "ntfyId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_customId_custom_customId_fk": {
+ "name": "notification_customId_custom_customId_fk",
+ "tableFrom": "notification",
+ "tableTo": "custom",
+ "columnsFrom": [
+ "customId"
+ ],
+ "columnsTo": [
+ "customId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_larkId_lark_larkId_fk": {
+ "name": "notification_larkId_lark_larkId_fk",
+ "tableFrom": "notification",
+ "tableTo": "lark",
+ "columnsFrom": [
+ "larkId"
+ ],
+ "columnsTo": [
+ "larkId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_pushoverId_pushover_pushoverId_fk": {
+ "name": "notification_pushoverId_pushover_pushoverId_fk",
+ "tableFrom": "notification",
+ "tableTo": "pushover",
+ "columnsFrom": [
+ "pushoverId"
+ ],
+ "columnsTo": [
+ "pushoverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notification_organizationId_organization_id_fk": {
+ "name": "notification_organizationId_organization_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ntfy": {
+ "name": "ntfy",
+ "schema": "",
+ "columns": {
+ "ntfyId": {
+ "name": "ntfyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverUrl": {
+ "name": "serverUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topic": {
+ "name": "topic",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "accessToken": {
+ "name": "accessToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 3
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pushover": {
+ "name": "pushover",
+ "schema": "",
+ "columns": {
+ "pushoverId": {
+ "name": "pushoverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userKey": {
+ "name": "userKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apiToken": {
+ "name": "apiToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "retry": {
+ "name": "retry",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expire": {
+ "name": "expire",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resend": {
+ "name": "resend",
+ "schema": "",
+ "columns": {
+ "resendId": {
+ "name": "resendId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fromAddress": {
+ "name": "fromAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "toAddress": {
+ "name": "toAddress",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.slack": {
+ "name": "slack",
+ "schema": "",
+ "columns": {
+ "slackId": {
+ "name": "slackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "webhookUrl": {
+ "name": "webhookUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.telegram": {
+ "name": "telegram",
+ "schema": "",
+ "columns": {
+ "telegramId": {
+ "name": "telegramId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "botToken": {
+ "name": "botToken",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chatId": {
+ "name": "chatId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "messageThreadId": {
+ "name": "messageThreadId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.port": {
+ "name": "port",
+ "schema": "",
+ "columns": {
+ "portId": {
+ "name": "portId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "publishedPort": {
+ "name": "publishedPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "publishMode": {
+ "name": "publishMode",
+ "type": "publishModeType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'host'"
+ },
+ "targetPort": {
+ "name": "targetPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol": {
+ "name": "protocol",
+ "type": "protocolType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "port_applicationId_application_applicationId_fk": {
+ "name": "port_applicationId_application_applicationId_fk",
+ "tableFrom": "port",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.postgres": {
+ "name": "postgres",
+ "schema": "",
+ "columns": {
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseName": {
+ "name": "databaseName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databaseUser": {
+ "name": "databaseUser",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "databasePassword": {
+ "name": "databasePassword",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "postgres_environmentId_environment_environmentId_fk": {
+ "name": "postgres_environmentId_environment_environmentId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "postgres_serverId_server_serverId_fk": {
+ "name": "postgres_serverId_server_serverId_fk",
+ "tableFrom": "postgres",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postgres_appName_unique": {
+ "name": "postgres_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.preview_deployments": {
+ "name": "preview_deployments",
+ "schema": "",
+ "columns": {
+ "previewDeploymentId": {
+ "name": "previewDeploymentId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "branch": {
+ "name": "branch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestId": {
+ "name": "pullRequestId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestNumber": {
+ "name": "pullRequestNumber",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestURL": {
+ "name": "pullRequestURL",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestTitle": {
+ "name": "pullRequestTitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pullRequestCommentId": {
+ "name": "pullRequestCommentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "previewStatus": {
+ "name": "previewStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domainId": {
+ "name": "domainId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expiresAt": {
+ "name": "expiresAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "preview_deployments_applicationId_application_applicationId_fk": {
+ "name": "preview_deployments_applicationId_application_applicationId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "preview_deployments_domainId_domain_domainId_fk": {
+ "name": "preview_deployments_domainId_domain_domainId_fk",
+ "tableFrom": "preview_deployments",
+ "tableTo": "domain",
+ "columnsFrom": [
+ "domainId"
+ ],
+ "columnsTo": [
+ "domainId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "preview_deployments_appName_unique": {
+ "name": "preview_deployments_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.project": {
+ "name": "project",
+ "schema": "",
+ "columns": {
+ "projectId": {
+ "name": "projectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "project_organizationId_organization_id_fk": {
+ "name": "project_organizationId_organization_id_fk",
+ "tableFrom": "project",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redirect": {
+ "name": "redirect",
+ "schema": "",
+ "columns": {
+ "redirectId": {
+ "name": "redirectId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "regex": {
+ "name": "regex",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "replacement": {
+ "name": "replacement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent": {
+ "name": "permanent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uniqueConfigKey": {
+ "name": "uniqueConfigKey",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redirect_applicationId_application_applicationId_fk": {
+ "name": "redirect_applicationId_application_applicationId_fk",
+ "tableFrom": "redirect",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.redis": {
+ "name": "redis",
+ "schema": "",
+ "columns": {
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dockerImage": {
+ "name": "dockerImage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "args": {
+ "name": "args",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env": {
+ "name": "env",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryReservation": {
+ "name": "memoryReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "memoryLimit": {
+ "name": "memoryLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuReservation": {
+ "name": "cpuReservation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cpuLimit": {
+ "name": "cpuLimit",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "externalPort": {
+ "name": "externalPort",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationStatus": {
+ "name": "applicationStatus",
+ "type": "applicationStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "healthCheckSwarm": {
+ "name": "healthCheckSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "restartPolicySwarm": {
+ "name": "restartPolicySwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "placementSwarm": {
+ "name": "placementSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updateConfigSwarm": {
+ "name": "updateConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rollbackConfigSwarm": {
+ "name": "rollbackConfigSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "modeSwarm": {
+ "name": "modeSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labelsSwarm": {
+ "name": "labelsSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "networkSwarm": {
+ "name": "networkSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stopGracePeriodSwarm": {
+ "name": "stopGracePeriodSwarm",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpointSpecSwarm": {
+ "name": "endpointSpecSwarm",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "replicas": {
+ "name": "replicas",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "environmentId": {
+ "name": "environmentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "redis_environmentId_environment_environmentId_fk": {
+ "name": "redis_environmentId_environment_environmentId_fk",
+ "tableFrom": "redis",
+ "tableTo": "environment",
+ "columnsFrom": [
+ "environmentId"
+ ],
+ "columnsTo": [
+ "environmentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "redis_serverId_server_serverId_fk": {
+ "name": "redis_serverId_server_serverId_fk",
+ "tableFrom": "redis",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "redis_appName_unique": {
+ "name": "redis_appName_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "appName"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.registry": {
+ "name": "registry",
+ "schema": "",
+ "columns": {
+ "registryId": {
+ "name": "registryId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "registryName": {
+ "name": "registryName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "imagePrefix": {
+ "name": "imagePrefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "registryUrl": {
+ "name": "registryUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "selfHosted": {
+ "name": "selfHosted",
+ "type": "RegistryType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'cloud'"
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "registry_organizationId_organization_id_fk": {
+ "name": "registry_organizationId_organization_id_fk",
+ "tableFrom": "registry",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rollback": {
+ "name": "rollback",
+ "schema": "",
+ "columns": {
+ "rollbackId": {
+ "name": "rollbackId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "deploymentId": {
+ "name": "deploymentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fullContext": {
+ "name": "fullContext",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "rollback_deploymentId_deployment_deploymentId_fk": {
+ "name": "rollback_deploymentId_deployment_deploymentId_fk",
+ "tableFrom": "rollback",
+ "tableTo": "deployment",
+ "columnsFrom": [
+ "deploymentId"
+ ],
+ "columnsTo": [
+ "deploymentId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.schedule": {
+ "name": "schedule",
+ "schema": "",
+ "columns": {
+ "scheduleId": {
+ "name": "scheduleId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "shellType": {
+ "name": "shellType",
+ "type": "shellType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'bash'"
+ },
+ "scheduleType": {
+ "name": "scheduleType",
+ "type": "scheduleType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "script": {
+ "name": "script",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "schedule_applicationId_application_applicationId_fk": {
+ "name": "schedule_applicationId_application_applicationId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_composeId_compose_composeId_fk": {
+ "name": "schedule_composeId_compose_composeId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_serverId_server_serverId_fk": {
+ "name": "schedule_serverId_server_serverId_fk",
+ "tableFrom": "schedule",
+ "tableTo": "server",
+ "columnsFrom": [
+ "serverId"
+ ],
+ "columnsTo": [
+ "serverId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "schedule_userId_user_id_fk": {
+ "name": "schedule_userId_user_id_fk",
+ "tableFrom": "schedule",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.security": {
+ "name": "security",
+ "schema": "",
+ "columns": {
+ "securityId": {
+ "name": "securityId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "security_applicationId_application_applicationId_fk": {
+ "name": "security_applicationId_application_applicationId_fk",
+ "tableFrom": "security",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "security_username_applicationId_unique": {
+ "name": "security_username_applicationId_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username",
+ "applicationId"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.server": {
+ "name": "server",
+ "schema": "",
+ "columns": {
+ "serverId": {
+ "name": "serverId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ipAddress": {
+ "name": "ipAddress",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "port": {
+ "name": "port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'root'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serverStatus": {
+ "name": "serverStatus",
+ "type": "serverStatus",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "serverType": {
+ "name": "serverType",
+ "type": "serverType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'deploy'"
+ },
+ "command": {
+ "name": "command",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Remote\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"urlCallback\":\"\",\"cronJob\":\"\",\"retentionDays\":2,\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "server_organizationId_organization_id_fk": {
+ "name": "server_organizationId_organization_id_fk",
+ "tableFrom": "server",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "server_sshKeyId_ssh-key_sshKeyId_fk": {
+ "name": "server_sshKeyId_ssh-key_sshKeyId_fk",
+ "tableFrom": "server",
+ "tableTo": "ssh-key",
+ "columnsFrom": [
+ "sshKeyId"
+ ],
+ "columnsTo": [
+ "sshKeyId"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session_temp": {
+ "name": "session_temp",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_temp_user_id_user_id_fk": {
+ "name": "session_temp_user_id_user_id_fk",
+ "tableFrom": "session_temp",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_temp_token_unique": {
+ "name": "session_temp_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ssh-key": {
+ "name": "ssh-key",
+ "schema": "",
+ "columns": {
+ "sshKeyId": {
+ "name": "sshKeyId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "privateKey": {
+ "name": "privateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "publicKey": {
+ "name": "publicKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lastUsedAt": {
+ "name": "lastUsedAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organizationId": {
+ "name": "organizationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ssh-key_organizationId_organization_id_fk": {
+ "name": "ssh-key_organizationId_organization_id_fk",
+ "tableFrom": "ssh-key",
+ "tableTo": "organization",
+ "columnsFrom": [
+ "organizationId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "lastName": {
+ "name": "lastName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "isRegistered": {
+ "name": "isRegistered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "expirationDate": {
+ "name": "expirationDate",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "two_factor_enabled": {
+ "name": "two_factor_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'user'"
+ },
+ "enablePaidFeatures": {
+ "name": "enablePaidFeatures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "allowImpersonation": {
+ "name": "allowImpersonation",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "stripeCustomerId": {
+ "name": "stripeCustomerId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripeSubscriptionId": {
+ "name": "stripeSubscriptionId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serversQuantity": {
+ "name": "serversQuantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.volume_backup": {
+ "name": "volume_backup",
+ "schema": "",
+ "columns": {
+ "volumeBackupId": {
+ "name": "volumeBackupId",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "volumeName": {
+ "name": "volumeName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prefix": {
+ "name": "prefix",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceType": {
+ "name": "serviceType",
+ "type": "serviceType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'application'"
+ },
+ "appName": {
+ "name": "appName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "serviceName": {
+ "name": "serviceName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "turnOff": {
+ "name": "turnOff",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cronExpression": {
+ "name": "cronExpression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "keepLatestCount": {
+ "name": "keepLatestCount",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "applicationId": {
+ "name": "applicationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postgresId": {
+ "name": "postgresId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mariadbId": {
+ "name": "mariadbId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mongoId": {
+ "name": "mongoId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mysqlId": {
+ "name": "mysqlId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redisId": {
+ "name": "redisId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "composeId": {
+ "name": "composeId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "destinationId": {
+ "name": "destinationId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "volume_backup_applicationId_application_applicationId_fk": {
+ "name": "volume_backup_applicationId_application_applicationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "application",
+ "columnsFrom": [
+ "applicationId"
+ ],
+ "columnsTo": [
+ "applicationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_postgresId_postgres_postgresId_fk": {
+ "name": "volume_backup_postgresId_postgres_postgresId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "postgres",
+ "columnsFrom": [
+ "postgresId"
+ ],
+ "columnsTo": [
+ "postgresId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mariadbId_mariadb_mariadbId_fk": {
+ "name": "volume_backup_mariadbId_mariadb_mariadbId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mariadb",
+ "columnsFrom": [
+ "mariadbId"
+ ],
+ "columnsTo": [
+ "mariadbId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mongoId_mongo_mongoId_fk": {
+ "name": "volume_backup_mongoId_mongo_mongoId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mongo",
+ "columnsFrom": [
+ "mongoId"
+ ],
+ "columnsTo": [
+ "mongoId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_mysqlId_mysql_mysqlId_fk": {
+ "name": "volume_backup_mysqlId_mysql_mysqlId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "mysql",
+ "columnsFrom": [
+ "mysqlId"
+ ],
+ "columnsTo": [
+ "mysqlId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_redisId_redis_redisId_fk": {
+ "name": "volume_backup_redisId_redis_redisId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "redis",
+ "columnsFrom": [
+ "redisId"
+ ],
+ "columnsTo": [
+ "redisId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_composeId_compose_composeId_fk": {
+ "name": "volume_backup_composeId_compose_composeId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "compose",
+ "columnsFrom": [
+ "composeId"
+ ],
+ "columnsTo": [
+ "composeId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volume_backup_destinationId_destination_destinationId_fk": {
+ "name": "volume_backup_destinationId_destination_destinationId_fk",
+ "tableFrom": "volume_backup",
+ "tableTo": "destination",
+ "columnsFrom": [
+ "destinationId"
+ ],
+ "columnsTo": [
+ "destinationId"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webServerSettings": {
+ "name": "webServerSettings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "serverIp": {
+ "name": "serverIp",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "certificateType": {
+ "name": "certificateType",
+ "type": "certificateType",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "https": {
+ "name": "https",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "host": {
+ "name": "host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "letsEncryptEmail": {
+ "name": "letsEncryptEmail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sshPrivateKey": {
+ "name": "sshPrivateKey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enableDockerCleanup": {
+ "name": "enableDockerCleanup",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "logCleanupCron": {
+ "name": "logCleanupCron",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0 0 * * *'"
+ },
+ "metricsConfig": {
+ "name": "metricsConfig",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"server\":{\"type\":\"Dokploy\",\"refreshRate\":60,\"port\":4500,\"token\":\"\",\"retentionDays\":2,\"cronJob\":\"\",\"urlCallback\":\"\",\"thresholds\":{\"cpu\":0,\"memory\":0}},\"containers\":{\"refreshRate\":60,\"services\":{\"include\":[],\"exclude\":[]}}}'::jsonb"
+ },
+ "cleanupCacheApplications": {
+ "name": "cleanupCacheApplications",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnPreviews": {
+ "name": "cleanupCacheOnPreviews",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cleanupCacheOnCompose": {
+ "name": "cleanupCacheOnCompose",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.buildType": {
+ "name": "buildType",
+ "schema": "public",
+ "values": [
+ "dockerfile",
+ "heroku_buildpacks",
+ "paketo_buildpacks",
+ "nixpacks",
+ "static",
+ "railpack"
+ ]
+ },
+ "public.sourceType": {
+ "name": "sourceType",
+ "schema": "public",
+ "values": [
+ "docker",
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "drop"
+ ]
+ },
+ "public.backupType": {
+ "name": "backupType",
+ "schema": "public",
+ "values": [
+ "database",
+ "compose"
+ ]
+ },
+ "public.databaseType": {
+ "name": "databaseType",
+ "schema": "public",
+ "values": [
+ "postgres",
+ "mariadb",
+ "mysql",
+ "mongo",
+ "web-server"
+ ]
+ },
+ "public.composeType": {
+ "name": "composeType",
+ "schema": "public",
+ "values": [
+ "docker-compose",
+ "stack"
+ ]
+ },
+ "public.sourceTypeCompose": {
+ "name": "sourceTypeCompose",
+ "schema": "public",
+ "values": [
+ "git",
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea",
+ "raw"
+ ]
+ },
+ "public.deploymentStatus": {
+ "name": "deploymentStatus",
+ "schema": "public",
+ "values": [
+ "running",
+ "done",
+ "error",
+ "cancelled"
+ ]
+ },
+ "public.domainType": {
+ "name": "domainType",
+ "schema": "public",
+ "values": [
+ "compose",
+ "application",
+ "preview"
+ ]
+ },
+ "public.gitProviderType": {
+ "name": "gitProviderType",
+ "schema": "public",
+ "values": [
+ "github",
+ "gitlab",
+ "bitbucket",
+ "gitea"
+ ]
+ },
+ "public.mountType": {
+ "name": "mountType",
+ "schema": "public",
+ "values": [
+ "bind",
+ "volume",
+ "file"
+ ]
+ },
+ "public.serviceType": {
+ "name": "serviceType",
+ "schema": "public",
+ "values": [
+ "application",
+ "postgres",
+ "mysql",
+ "mariadb",
+ "mongo",
+ "redis",
+ "compose"
+ ]
+ },
+ "public.notificationType": {
+ "name": "notificationType",
+ "schema": "public",
+ "values": [
+ "slack",
+ "telegram",
+ "discord",
+ "email",
+ "resend",
+ "gotify",
+ "ntfy",
+ "pushover",
+ "custom",
+ "lark"
+ ]
+ },
+ "public.protocolType": {
+ "name": "protocolType",
+ "schema": "public",
+ "values": [
+ "tcp",
+ "udp"
+ ]
+ },
+ "public.publishModeType": {
+ "name": "publishModeType",
+ "schema": "public",
+ "values": [
+ "ingress",
+ "host"
+ ]
+ },
+ "public.RegistryType": {
+ "name": "RegistryType",
+ "schema": "public",
+ "values": [
+ "selfHosted",
+ "cloud"
+ ]
+ },
+ "public.scheduleType": {
+ "name": "scheduleType",
+ "schema": "public",
+ "values": [
+ "application",
+ "compose",
+ "server",
+ "dokploy-server"
+ ]
+ },
+ "public.shellType": {
+ "name": "shellType",
+ "schema": "public",
+ "values": [
+ "bash",
+ "sh"
+ ]
+ },
+ "public.serverStatus": {
+ "name": "serverStatus",
+ "schema": "public",
+ "values": [
+ "active",
+ "inactive"
+ ]
+ },
+ "public.serverType": {
+ "name": "serverType",
+ "schema": "public",
+ "values": [
+ "deploy",
+ "build"
+ ]
+ },
+ "public.applicationStatus": {
+ "name": "applicationStatus",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "done",
+ "error"
+ ]
+ },
+ "public.certificateType": {
+ "name": "certificateType",
+ "schema": "public",
+ "values": [
+ "letsencrypt",
+ "none",
+ "custom"
+ ]
+ },
+ "public.triggerType": {
+ "name": "triggerType",
+ "schema": "public",
+ "values": [
+ "push",
+ "tag"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/apps/dokploy/drizzle/meta/_journal.json b/apps/dokploy/drizzle/meta/_journal.json
index d442758b3..a066d7bf7 100644
--- a/apps/dokploy/drizzle/meta/_journal.json
+++ b/apps/dokploy/drizzle/meta/_journal.json
@@ -960,6 +960,13 @@
"when": 1769580434296,
"tag": "0136_tidy_puff_adder",
"breakpoints": true
+ },
+ {
+ "idx": 137,
+ "version": "7",
+ "when": 1769595469101,
+ "tag": "0137_worried_shriek",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts
index c22ce7aa5..a5097288b 100644
--- a/apps/dokploy/server/api/routers/notification.ts
+++ b/apps/dokploy/server/api/routers/notification.ts
@@ -1,4 +1,4 @@
-import {
+import {
createCustomNotification,
createDiscordNotification,
createEmailNotification,
@@ -6,12 +6,13 @@ import {
createLarkNotification,
createNtfyNotification,
createPushoverNotification,
+ createResendNotification,
createSlackNotification,
createTelegramNotification,
findNotificationById,
getWebServerSettings,
IS_CLOUD,
- removeNotificationById,
+ removeNotificationById,
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
@@ -19,6 +20,7 @@ import {
sendLarkNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendServerThresholdNotifications,
sendSlackNotification,
sendTelegramNotification,
@@ -29,27 +31,29 @@ import {
updateLarkNotification,
updateNtfyNotification,
updatePushoverNotification,
+ updateResendNotification,
updateSlackNotification,
updateTelegramNotification,
} from "@dokploy/server";
-import { TRPCError } from "@trpc/server";
-import { desc, eq, sql } from "drizzle-orm";
-import { z } from "zod";
-import {
- adminProcedure,
- createTRPCRouter,
- protectedProcedure,
- publicProcedure,
-} from "@/server/api/trpc";
-import { db } from "@/server/db";
-import {
- apiCreateCustom,
+import { TRPCError } from "@trpc/server";
+import { desc, eq, sql } from "drizzle-orm";
+import { z } from "zod";
+import {
+ adminProcedure,
+ createTRPCRouter,
+ protectedProcedure,
+ publicProcedure,
+} from "@/server/api/trpc";
+import { db } from "@/server/db";
+import {
+ apiCreateCustom,
apiCreateDiscord,
apiCreateEmail,
apiCreateGotify,
apiCreateLark,
apiCreateNtfy,
apiCreatePushover,
+ apiCreateResend,
apiCreateSlack,
apiCreateTelegram,
apiFindOneNotification,
@@ -60,6 +64,7 @@ import {
apiTestLarkConnection,
apiTestNtfyConnection,
apiTestPushoverConnection,
+ apiTestResendConnection,
apiTestSlackConnection,
apiTestTelegramConnection,
apiUpdateCustom,
@@ -69,640 +74,700 @@ import {
apiUpdateLark,
apiUpdateNtfy,
apiUpdatePushover,
+ apiUpdateResend,
apiUpdateSlack,
apiUpdateTelegram,
notifications,
server,
-} from "@/server/db/schema";
-
-export const notificationRouter = createTRPCRouter({
- createSlack: adminProcedure
- .input(apiCreateSlack)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createSlackNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updateSlack: adminProcedure
- .input(apiUpdateSlack)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateSlackNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw error;
- }
- }),
- testSlackConnection: adminProcedure
- .input(apiTestSlackConnection)
- .mutation(async ({ input }) => {
- try {
- await sendSlackNotification(input, {
- channel: input.channel,
- text: "Hi, From Dokploy ๐",
- });
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `${error instanceof Error ? error.message : "Unknown error"}`,
- cause: error,
- });
- }
- }),
- createTelegram: adminProcedure
- .input(apiCreateTelegram)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createTelegramNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
-
- updateTelegram: adminProcedure
- .input(apiUpdateTelegram)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateTelegramNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error updating the notification",
- cause: error,
- });
- }
- }),
- testTelegramConnection: adminProcedure
- .input(apiTestTelegramConnection)
- .mutation(async ({ input }) => {
- try {
- await sendTelegramNotification(input, "Hi, From Dokploy ๐");
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error testing the notification",
- cause: error,
- });
- }
- }),
- createDiscord: adminProcedure
- .input(apiCreateDiscord)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createDiscordNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
-
- updateDiscord: adminProcedure
- .input(apiUpdateDiscord)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateDiscordNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error updating the notification",
- cause: error,
- });
- }
- }),
-
- testDiscordConnection: adminProcedure
- .input(apiTestDiscordConnection)
- .mutation(async ({ input }) => {
- try {
- const decorate = (decoration: string, text: string) =>
- `${input.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(input, {
- title: decorate(">", "`๐ค` - Test Notification"),
- description: decorate(">", "Hi, From Dokploy ๐"),
- color: 0xf3f7f4,
- });
-
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `${error instanceof Error ? error.message : "Unknown error"}`,
- cause: error,
- });
- }
- }),
- createEmail: adminProcedure
- .input(apiCreateEmail)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createEmailNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updateEmail: adminProcedure
- .input(apiUpdateEmail)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateEmailNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error updating the notification",
- cause: error,
- });
- }
- }),
- testEmailConnection: adminProcedure
- .input(apiTestEmailConnection)
- .mutation(async ({ input }) => {
- try {
- await sendEmailNotification(
- input,
- "Test Email",
- "
Hi, From Dokploy ๐
",
- );
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `${error instanceof Error ? error.message : "Unknown error"}`,
- cause: error,
- });
- }
- }),
- remove: adminProcedure
- .input(apiFindOneNotification)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to delete this notification",
- });
- }
- return await removeNotificationById(input.notificationId);
- } catch (error) {
- const message =
- error instanceof Error
- ? error.message
- : "Error deleting this notification";
- throw new TRPCError({
- code: "BAD_REQUEST",
- message,
- });
- }
- }),
- one: protectedProcedure
- .input(apiFindOneNotification)
- .query(async ({ input, ctx }) => {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to access this notification",
- });
- }
- return notification;
- }),
- all: adminProcedure.query(async ({ ctx }) => {
- return await db.query.notifications.findMany({
- with: {
- slack: true,
- telegram: true,
- discord: true,
- email: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- orderBy: desc(notifications.createdAt),
- where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
- });
- }),
- receiveNotification: publicProcedure
- .input(
- z.object({
- ServerType: z.enum(["Dokploy", "Remote"]).default("Dokploy"),
- Type: z.enum(["Memory", "CPU"]),
- Value: z.number(),
- Threshold: z.number(),
- Message: z.string(),
- Timestamp: z.string(),
- Token: z.string(),
- }),
- )
- .mutation(async ({ input }) => {
- try {
- let organizationId = "";
- let ServerName = "";
- if (input.ServerType === "Dokploy") {
- const settings = await getWebServerSettings();
- if (
- !settings?.metricsConfig?.server?.token ||
- settings.metricsConfig.server.token !== input.Token
- ) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Token not found",
- });
- }
-
- // For Dokploy server type, we don't have a specific organizationId
- // This might need to be adjusted based on your business logic
- organizationId = "";
- ServerName = "Dokploy";
- } else {
- const result = await db
- .select()
- .from(server)
- .where(
- sql`${server.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
- );
-
- if (!result?.[0]?.organizationId) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Token not found",
- });
- }
-
- organizationId = result?.[0]?.organizationId;
- ServerName = "Remote";
- }
-
- await sendServerThresholdNotifications(organizationId, {
- ...input,
- ServerName,
- });
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error sending the notification",
- cause: error,
- });
- }
- }),
- createGotify: adminProcedure
- .input(apiCreateGotify)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createGotifyNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updateGotify: adminProcedure
- .input(apiUpdateGotify)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (
- IS_CLOUD &&
- notification.organizationId !== ctx.session.activeOrganizationId
- ) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateGotifyNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw error;
- }
- }),
- testGotifyConnection: adminProcedure
- .input(apiTestGotifyConnection)
- .mutation(async ({ input }) => {
- try {
- await sendGotifyNotification(
- input,
- "Test Notification",
- "Hi, From Dokploy ๐",
- );
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error testing the notification",
- cause: error,
- });
- }
- }),
- createNtfy: adminProcedure
- .input(apiCreateNtfy)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createNtfyNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updateNtfy: adminProcedure
- .input(apiUpdateNtfy)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (
- IS_CLOUD &&
- notification.organizationId !== ctx.session.activeOrganizationId
- ) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateNtfyNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw error;
- }
- }),
- testNtfyConnection: adminProcedure
- .input(apiTestNtfyConnection)
- .mutation(async ({ input }) => {
- try {
- await sendNtfyNotification(
- input,
- "Test Notification",
- "",
- "view, visit Dokploy on Github, https://github.com/dokploy/dokploy, clear=true;",
- "Hi, From Dokploy ๐",
- );
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error testing the notification",
- cause: error,
- });
- }
- }),
- createCustom: adminProcedure
- .input(apiCreateCustom)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createCustomNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updateCustom: adminProcedure
- .input(apiUpdateCustom)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (notification.organizationId !== ctx.session.activeOrganizationId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateCustomNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw error;
- }
- }),
- testCustomConnection: adminProcedure
- .input(apiTestCustomConnection)
- .mutation(async ({ input }) => {
- try {
- await sendCustomNotification(input, {
- title: "Test Notification",
- message: "Hi, From Dokploy ๐",
- timestamp: new Date().toISOString(),
- });
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `${error instanceof Error ? error.message : "Unknown error"}`,
- cause: error,
- });
- }
- }),
- createLark: adminProcedure
- .input(apiCreateLark)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createLarkNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updateLark: adminProcedure
- .input(apiUpdateLark)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (
- IS_CLOUD &&
- notification.organizationId !== ctx.session.activeOrganizationId
- ) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updateLarkNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw error;
- }
- }),
- testLarkConnection: adminProcedure
- .input(apiTestLarkConnection)
- .mutation(async ({ input }) => {
- try {
- await sendLarkNotification(input, {
- msg_type: "text",
- content: {
- text: "Hi, From Dokploy ๐",
- },
- });
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error testing the notification",
- cause: error,
- });
- }
- }),
- createPushover: adminProcedure
- .input(apiCreatePushover)
- .mutation(async ({ input, ctx }) => {
- try {
- return await createPushoverNotification(
- input,
- ctx.session.activeOrganizationId,
- );
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the notification",
- cause: error,
- });
- }
- }),
- updatePushover: adminProcedure
- .input(apiUpdatePushover)
- .mutation(async ({ input, ctx }) => {
- try {
- const notification = await findNotificationById(input.notificationId);
- if (
- IS_CLOUD &&
- notification.organizationId !== ctx.session.activeOrganizationId
- ) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to update this notification",
- });
- }
- return await updatePushoverNotification({
- ...input,
- organizationId: ctx.session.activeOrganizationId,
- });
- } catch (error) {
- throw error;
- }
- }),
- testPushoverConnection: adminProcedure
- .input(apiTestPushoverConnection)
- .mutation(async ({ input }) => {
- try {
- await sendPushoverNotification(
- input,
- "Test Notification",
- "Hi, From Dokploy ๐",
- );
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error testing the notification",
- cause: error,
- });
- }
- }),
- getEmailProviders: adminProcedure.query(async ({ ctx }) => {
- return await db.query.notifications.findMany({
- where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
- with: {
- email: true,
- },
- });
- }),
-});
+} from "@/server/db/schema";
+
+export const notificationRouter = createTRPCRouter({
+ createSlack: adminProcedure
+ .input(apiCreateSlack)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createSlackNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateSlack: adminProcedure
+ .input(apiUpdateSlack)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateSlackNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testSlackConnection: adminProcedure
+ .input(apiTestSlackConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendSlackNotification(input, {
+ channel: input.channel,
+ text: "Hi, From Dokploy ๐",
+ });
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
+ cause: error,
+ });
+ }
+ }),
+ createTelegram: adminProcedure
+ .input(apiCreateTelegram)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createTelegramNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+
+ updateTelegram: adminProcedure
+ .input(apiUpdateTelegram)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateTelegramNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error updating the notification",
+ cause: error,
+ });
+ }
+ }),
+ testTelegramConnection: adminProcedure
+ .input(apiTestTelegramConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendTelegramNotification(input, "Hi, From Dokploy ๐");
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error testing the notification",
+ cause: error,
+ });
+ }
+ }),
+ createDiscord: adminProcedure
+ .input(apiCreateDiscord)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createDiscordNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+
+ updateDiscord: adminProcedure
+ .input(apiUpdateDiscord)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateDiscordNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error updating the notification",
+ cause: error,
+ });
+ }
+ }),
+
+ testDiscordConnection: adminProcedure
+ .input(apiTestDiscordConnection)
+ .mutation(async ({ input }) => {
+ try {
+ const decorate = (decoration: string, text: string) =>
+ `${input.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(input, {
+ title: decorate(">", "`๐ค` - Test Notification"),
+ description: decorate(">", "Hi, From Dokploy ๐"),
+ color: 0xf3f7f4,
+ });
+
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
+ cause: error,
+ });
+ }
+ }),
+ createEmail: adminProcedure
+ .input(apiCreateEmail)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createEmailNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateEmail: adminProcedure
+ .input(apiUpdateEmail)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateEmailNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error updating the notification",
+ cause: error,
+ });
+ }
+ }),
+ testEmailConnection: adminProcedure
+ .input(apiTestEmailConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendEmailNotification(
+ input,
+ "Test Email",
+ "
Hi, From Dokploy ๐
",
+ );
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
+ cause: error,
+ });
+ }
+ }),
+ createResend: adminProcedure
+ .input(apiCreateResend)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createResendNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateResend: adminProcedure
+ .input(apiUpdateResend)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateResendNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error updating the notification",
+ cause: error,
+ });
+ }
+ }),
+ testResendConnection: adminProcedure
+ .input(apiTestResendConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendResendNotification(
+ input,
+ "Test Email",
+ "
Hi, From Dokploy ๐
",
+ );
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
+ cause: error,
+ });
+ }
+ }),
+ remove: adminProcedure
+ .input(apiFindOneNotification)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to delete this notification",
+ });
+ }
+ return await removeNotificationById(input.notificationId);
+ } catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this notification";
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message,
+ });
+ }
+ }),
+ one: protectedProcedure
+ .input(apiFindOneNotification)
+ .query(async ({ input, ctx }) => {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to access this notification",
+ });
+ }
+ return notification;
+ }),
+ all: adminProcedure.query(async ({ ctx }) => {
+ return await db.query.notifications.findMany({
+ with: {
+ slack: true,
+ telegram: true,
+ discord: true,
+ email: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ orderBy: desc(notifications.createdAt),
+ where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
+ });
+ }),
+ receiveNotification: publicProcedure
+ .input(
+ z.object({
+ ServerType: z.enum(["Dokploy", "Remote"]).default("Dokploy"),
+ Type: z.enum(["Memory", "CPU"]),
+ Value: z.number(),
+ Threshold: z.number(),
+ Message: z.string(),
+ Timestamp: z.string(),
+ Token: z.string(),
+ }),
+ )
+ .mutation(async ({ input }) => {
+ try {
+ let organizationId = "";
+ let ServerName = "";
+ if (input.ServerType === "Dokploy") {
+ const settings = await getWebServerSettings();
+ if (
+ !settings?.metricsConfig?.server?.token ||
+ settings.metricsConfig.server.token !== input.Token
+ ) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Token not found",
+ });
+ }
+
+ // For Dokploy server type, we don't have a specific organizationId
+ // This might need to be adjusted based on your business logic
+ organizationId = "";
+ ServerName = "Dokploy";
+ } else {
+ const result = await db
+ .select()
+ .from(server)
+ .where(
+ sql`${server.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
+ );
+
+ if (!result?.[0]?.organizationId) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Token not found",
+ });
+ }
+
+ organizationId = result?.[0]?.organizationId;
+ ServerName = "Remote";
+ }
+
+ await sendServerThresholdNotifications(organizationId, {
+ ...input,
+ ServerName,
+ });
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error sending the notification",
+ cause: error,
+ });
+ }
+ }),
+ createGotify: adminProcedure
+ .input(apiCreateGotify)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createGotifyNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateGotify: adminProcedure
+ .input(apiUpdateGotify)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateGotifyNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testGotifyConnection: adminProcedure
+ .input(apiTestGotifyConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendGotifyNotification(
+ input,
+ "Test Notification",
+ "Hi, From Dokploy ๐",
+ );
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error testing the notification",
+ cause: error,
+ });
+ }
+ }),
+ createNtfy: adminProcedure
+ .input(apiCreateNtfy)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createNtfyNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateNtfy: adminProcedure
+ .input(apiUpdateNtfy)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateNtfyNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testNtfyConnection: adminProcedure
+ .input(apiTestNtfyConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendNtfyNotification(
+ input,
+ "Test Notification",
+ "",
+ "view, visit Dokploy on Github, https://github.com/dokploy/dokploy, clear=true;",
+ "Hi, From Dokploy ๐",
+ );
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error testing the notification",
+ cause: error,
+ });
+ }
+ }),
+ createCustom: adminProcedure
+ .input(apiCreateCustom)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createCustomNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateCustom: adminProcedure
+ .input(apiUpdateCustom)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateCustomNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testCustomConnection: adminProcedure
+ .input(apiTestCustomConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendCustomNotification(input, {
+ title: "Test Notification",
+ message: "Hi, From Dokploy ๐",
+ timestamp: new Date().toISOString(),
+ });
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: `${error instanceof Error ? error.message : "Unknown error"}`,
+ cause: error,
+ });
+ }
+ }),
+ createLark: adminProcedure
+ .input(apiCreateLark)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createLarkNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updateLark: adminProcedure
+ .input(apiUpdateLark)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updateLarkNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testLarkConnection: adminProcedure
+ .input(apiTestLarkConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendLarkNotification(input, {
+ msg_type: "text",
+ content: {
+ text: "Hi, From Dokploy ๐",
+ },
+ });
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error testing the notification",
+ cause: error,
+ });
+ }
+ }),
+ createPushover: adminProcedure
+ .input(apiCreatePushover)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ return await createPushoverNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error creating the notification",
+ cause: error,
+ });
+ }
+ }),
+ updatePushover: adminProcedure
+ .input(apiUpdatePushover)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const notification = await findNotificationById(input.notificationId);
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to update this notification",
+ });
+ }
+ return await updatePushoverNotification({
+ ...input,
+ organizationId: ctx.session.activeOrganizationId,
+ });
+ } catch (error) {
+ throw error;
+ }
+ }),
+ testPushoverConnection: adminProcedure
+ .input(apiTestPushoverConnection)
+ .mutation(async ({ input }) => {
+ try {
+ await sendPushoverNotification(
+ input,
+ "Test Notification",
+ "Hi, From Dokploy ๐",
+ );
+ return true;
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error testing the notification",
+ cause: error,
+ });
+ }
+ }),
+ getEmailProviders: adminProcedure.query(async ({ ctx }) => {
+ return await db.query.notifications.findMany({
+ where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
+ with: {
+ email: true,
+ resend: true,
+ },
+ });
+ }),
+});
diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts
index e801a5adb..3f217ceed 100644
--- a/apps/dokploy/server/api/routers/user.ts
+++ b/apps/dokploy/server/api/routers/user.ts
@@ -9,6 +9,7 @@ import {
IS_CLOUD,
removeUserById,
sendEmailNotification,
+ sendResendNotification,
updateUser,
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
@@ -509,15 +510,16 @@ export const userRouter = createTRPCRouter({
const notification = await findNotificationById(input.notificationId);
const email = notification.email;
+ const resend = notification.resend;
const currentInvitation = await db.query.invitation.findFirst({
where: eq(invitation.id, input.invitationId),
});
- if (!email) {
+ if (!email && !resend) {
throw new TRPCError({
code: "NOT_FOUND",
- message: "Email notification not found",
+ message: "Email provider not found",
});
}
@@ -532,16 +534,29 @@ export const userRouter = createTRPCRouter({
);
try {
- await sendEmailNotification(
- {
- ...email,
- toAddresses: [currentInvitation?.email || ""],
- },
- "Invitation to join organization",
- `
-
You are invited to join ${organization?.name || "organization"} on Dokploy. Click the link to accept the invitation: Accept Invitation
- `,
- );
+ const htmlContent = `
+\t\t\t\t
You are invited to join ${organization?.name || "organization"} on Dokploy. Click the link to accept the invitation: Accept Invitation
+\t\t\t\t`;
+
+ if (email) {
+ await sendEmailNotification(
+ {
+ ...email,
+ toAddresses: [currentInvitation?.email || ""],
+ },
+ "Invitation to join organization",
+ htmlContent,
+ );
+ } else if (resend) {
+ await sendResendNotification(
+ {
+ ...resend,
+ toAddresses: [currentInvitation?.email || ""],
+ },
+ "Invitation to join organization",
+ htmlContent,
+ );
+ }
} catch (error) {
console.log(error);
throw error;
diff --git a/packages/server/package.json b/packages/server/package.json
index 820300b15..e424bf00c 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -73,6 +73,7 @@
"qrcode": "^1.5.4",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "resend": "^6.0.2",
"shell-quote": "^1.8.1",
"slugify": "^1.6.6",
"ssh2": "1.15.0",
diff --git a/packages/server/schema.dbml b/packages/server/schema.dbml
index 0fe7c05e8..0bb22d80d 100644
--- a/packages/server/schema.dbml
+++ b/packages/server/schema.dbml
@@ -69,6 +69,7 @@ enum notificationType {
telegram
discord
email
+ resend
gotify
ntfy
custom
@@ -456,6 +457,13 @@ table email {
toAddress text[] [not null]
}
+table resend {
+ resendId text [pk, not null]
+ apiKey text [not null]
+ fromAddress text [not null]
+ toAddress text[] [not null]
+}
+
table environment {
environmentId text [pk, not null]
name text [not null]
@@ -695,6 +703,7 @@ table notification {
telegramId text
discordId text
emailId text
+ resendId text
gotifyId text
ntfyId text
customId text
@@ -1139,6 +1148,8 @@ ref: notification.discordId - discord.discordId
ref: notification.emailId - email.emailId
+ref: notification.resendId - resend.resendId
+
ref: notification.gotifyId - gotify.gotifyId
ref: notification.ntfyId - ntfy.ntfyId
@@ -1197,4 +1208,4 @@ ref: volume_backup.redisId - redis.redisId
ref: volume_backup.composeId - compose.composeId
-ref: volume_backup.destinationId - destination.destinationId
\ No newline at end of file
+ref: volume_backup.destinationId - destination.destinationId
diff --git a/packages/server/src/db/schema/notification.ts b/packages/server/src/db/schema/notification.ts
index 3075459ba..73af358d0 100644
--- a/packages/server/src/db/schema/notification.ts
+++ b/packages/server/src/db/schema/notification.ts
@@ -17,6 +17,7 @@ export const notificationType = pgEnum("notificationType", [
"telegram",
"discord",
"email",
+ "resend",
"gotify",
"ntfy",
"pushover",
@@ -53,6 +54,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",
}),
@@ -114,6 +118,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()
@@ -182,6 +196,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],
@@ -335,6 +353,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,
@@ -534,6 +582,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(),
diff --git a/packages/server/src/services/notification.ts b/packages/server/src/services/notification.ts
index 453a61ca0..f11619aac 100644
--- a/packages/server/src/services/notification.ts
+++ b/packages/server/src/services/notification.ts
@@ -1,12 +1,13 @@
-import { db } from "@dokploy/server/db";
-import {
- type apiCreateCustom,
- type apiCreateDiscord,
- type apiCreateEmail,
+import { db } from "@dokploy/server/db";
+import {
+ type apiCreateCustom,
+ type apiCreateDiscord,
+ type apiCreateEmail,
type apiCreateGotify,
type apiCreateLark,
type apiCreateNtfy,
type apiCreatePushover,
+ type apiCreateResend,
type apiCreateSlack,
type apiCreateTelegram,
type apiUpdateCustom,
@@ -16,6 +17,7 @@ import {
type apiUpdateLark,
type apiUpdateNtfy,
type apiUpdatePushover,
+ type apiUpdateResend,
type apiUpdateSlack,
type apiUpdateTelegram,
custom,
@@ -26,894 +28,990 @@ import {
notifications,
ntfy,
pushover,
+ resend,
slack,
telegram,
} from "@dokploy/server/db/schema";
-import { TRPCError } from "@trpc/server";
-import { eq } from "drizzle-orm";
-
-export type Notification = typeof notifications.$inferSelect;
-
-export const createSlackNotification = async (
- input: typeof apiCreateSlack._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newSlack = await tx
- .insert(slack)
- .values({
- channel: input.channel,
- webhookUrl: input.webhookUrl,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newSlack) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting slack",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- slackId: newSlack.slackId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "slack",
- organizationId: organizationId,
- serverThreshold: input.serverThreshold,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateSlackNotification = async (
- input: typeof apiUpdateSlack._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(slack)
- .set({
- channel: input.channel,
- webhookUrl: input.webhookUrl,
- })
- .where(eq(slack.slackId, input.slackId))
- .returning()
- .then((value) => value[0]);
-
- return newDestination;
- });
-};
-
-export const createTelegramNotification = async (
- input: typeof apiCreateTelegram._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newTelegram = await tx
- .insert(telegram)
- .values({
- botToken: input.botToken,
- chatId: input.chatId,
- messageThreadId: input.messageThreadId,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newTelegram) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting telegram",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- telegramId: newTelegram.telegramId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "telegram",
- organizationId: organizationId,
- serverThreshold: input.serverThreshold,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateTelegramNotification = async (
- input: typeof apiUpdateTelegram._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(telegram)
- .set({
- botToken: input.botToken,
- chatId: input.chatId,
- messageThreadId: input.messageThreadId,
- })
- .where(eq(telegram.telegramId, input.telegramId))
- .returning()
- .then((value) => value[0]);
-
- return newDestination;
- });
-};
-
-export const createDiscordNotification = async (
- input: typeof apiCreateDiscord._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newDiscord = await tx
- .insert(discord)
- .values({
- webhookUrl: input.webhookUrl,
- decoration: input.decoration,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDiscord) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting discord",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- discordId: newDiscord.discordId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "discord",
- organizationId: organizationId,
- serverThreshold: input.serverThreshold,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateDiscordNotification = async (
- input: typeof apiUpdateDiscord._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(discord)
- .set({
- webhookUrl: input.webhookUrl,
- decoration: input.decoration,
- })
- .where(eq(discord.discordId, input.discordId))
- .returning()
- .then((value) => value[0]);
-
- return newDestination;
- });
-};
-
-export const createEmailNotification = async (
- input: typeof apiCreateEmail._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newEmail = await tx
- .insert(email)
- .values({
- smtpServer: input.smtpServer,
- smtpPort: input.smtpPort,
- username: input.username,
- password: input.password,
- fromAddress: input.fromAddress,
- toAddresses: input.toAddresses,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newEmail) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting email",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- emailId: newEmail.emailId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "email",
- organizationId: organizationId,
- serverThreshold: input.serverThreshold,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateEmailNotification = async (
- input: typeof apiUpdateEmail._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(email)
- .set({
- smtpServer: input.smtpServer,
- smtpPort: input.smtpPort,
- username: input.username,
- password: input.password,
- fromAddress: input.fromAddress,
- toAddresses: input.toAddresses,
- })
- .where(eq(email.emailId, input.emailId))
- .returning()
- .then((value) => value[0]);
-
- return newDestination;
- });
-};
-
-export const createGotifyNotification = async (
- input: typeof apiCreateGotify._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newGotify = await tx
- .insert(gotify)
- .values({
- serverUrl: input.serverUrl,
- appToken: input.appToken,
- priority: input.priority,
- decoration: input.decoration,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newGotify) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting gotify",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- gotifyId: newGotify.gotifyId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "gotify",
- organizationId: organizationId,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateGotifyNotification = async (
- input: typeof apiUpdateGotify._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(gotify)
- .set({
- serverUrl: input.serverUrl,
- appToken: input.appToken,
- priority: input.priority,
- decoration: input.decoration,
- })
- .where(eq(gotify.gotifyId, input.gotifyId));
-
- return newDestination;
- });
-};
-
-export const createNtfyNotification = async (
- input: typeof apiCreateNtfy._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newNtfy = await tx
- .insert(ntfy)
- .values({
- serverUrl: input.serverUrl,
- topic: input.topic,
- accessToken: input.accessToken ?? null,
- priority: input.priority,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newNtfy) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting ntfy",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- ntfyId: newNtfy.ntfyId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "ntfy",
- organizationId: organizationId,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateNtfyNotification = async (
- input: typeof apiUpdateNtfy._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(ntfy)
- .set({
- serverUrl: input.serverUrl,
- topic: input.topic,
- accessToken: input.accessToken ?? null,
- priority: input.priority,
- })
- .where(eq(ntfy.ntfyId, input.ntfyId));
-
- return newDestination;
- });
-};
-
-export const createCustomNotification = async (
- input: typeof apiCreateCustom._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newCustom = await tx
- .insert(custom)
- .values({
- endpoint: input.endpoint,
- headers: input.headers,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newCustom) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting custom",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- customId: newCustom.customId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "custom",
- organizationId: organizationId,
- serverThreshold: input.serverThreshold,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateCustomNotification = async (
- input: typeof apiUpdateCustom._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(custom)
- .set({
- endpoint: input.endpoint,
- headers: input.headers,
- })
- .where(eq(custom.customId, input.customId));
-
- return newDestination;
- });
-};
-
-export const findNotificationById = async (notificationId: string) => {
- const notification = await db.query.notifications.findFirst({
- where: eq(notifications.notificationId, notificationId),
- with: {
- slack: true,
- telegram: true,
- discord: true,
- email: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- });
- if (!notification) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Notification not found",
- });
- }
- return notification;
-};
-
-export const removeNotificationById = async (notificationId: string) => {
- const result = await db
- .delete(notifications)
- .where(eq(notifications.notificationId, notificationId))
- .returning();
-
- return result[0];
-};
-
-export const createLarkNotification = async (
- input: typeof apiCreateLark._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newLark = await tx
- .insert(lark)
- .values({
- webhookUrl: input.webhookUrl,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newLark) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting lark",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- larkId: newLark.larkId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- notificationType: "lark",
- organizationId: organizationId,
- serverThreshold: input.serverThreshold,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updateLarkNotification = async (
- input: typeof apiUpdateLark._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(lark)
- .set({
- webhookUrl: input.webhookUrl,
- })
- .where(eq(lark.larkId, input.larkId))
- .returning()
- .then((value) => value[0]);
-
- return newDestination;
- });
-};
-
-export const updateNotificationById = async (
- notificationId: string,
- notificationData: Partial
,
-) => {
- const result = await db
- .update(notifications)
- .set({
- ...notificationData,
- })
- .where(eq(notifications.notificationId, notificationId))
- .returning();
-
- return result[0];
-};
-
-export const createPushoverNotification = async (
- input: typeof apiCreatePushover._type,
- organizationId: string,
-) => {
- await db.transaction(async (tx) => {
- const newPushover = await tx
- .insert(pushover)
- .values({
- userKey: input.userKey,
- apiToken: input.apiToken,
- priority: input.priority,
- retry: input.retry,
- expire: input.expire,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newPushover) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting pushover",
- });
- }
-
- const newDestination = await tx
- .insert(notifications)
- .values({
- pushoverId: newPushover.pushoverId,
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- serverThreshold: input.serverThreshold,
- notificationType: "pushover",
- organizationId: organizationId,
- })
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error input: Inserting notification",
- });
- }
-
- return newDestination;
- });
-};
-
-export const updatePushoverNotification = async (
- input: typeof apiUpdatePushover._type,
-) => {
- await db.transaction(async (tx) => {
- const newDestination = await tx
- .update(notifications)
- .set({
- name: input.name,
- appDeploy: input.appDeploy,
- appBuildError: input.appBuildError,
- databaseBackup: input.databaseBackup,
- volumeBackup: input.volumeBackup,
- dokployRestart: input.dokployRestart,
- dockerCleanup: input.dockerCleanup,
- organizationId: input.organizationId,
- serverThreshold: input.serverThreshold,
- })
- .where(eq(notifications.notificationId, input.notificationId))
- .returning()
- .then((value) => value[0]);
-
- if (!newDestination) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error Updating notification",
- });
- }
-
- await tx
- .update(pushover)
- .set({
- userKey: input.userKey,
- apiToken: input.apiToken,
- priority: input.priority,
- retry: input.retry,
- expire: input.expire,
- })
- .where(eq(pushover.pushoverId, input.pushoverId));
-
- return newDestination;
- });
-};
+import { TRPCError } from "@trpc/server";
+import { eq } from "drizzle-orm";
+
+export type Notification = typeof notifications.$inferSelect;
+
+export const createSlackNotification = async (
+ input: typeof apiCreateSlack._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newSlack = await tx
+ .insert(slack)
+ .values({
+ channel: input.channel,
+ webhookUrl: input.webhookUrl,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newSlack) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting slack",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ slackId: newSlack.slackId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "slack",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateSlackNotification = async (
+ input: typeof apiUpdateSlack._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(slack)
+ .set({
+ channel: input.channel,
+ webhookUrl: input.webhookUrl,
+ })
+ .where(eq(slack.slackId, input.slackId))
+ .returning()
+ .then((value) => value[0]);
+
+ return newDestination;
+ });
+};
+
+export const createTelegramNotification = async (
+ input: typeof apiCreateTelegram._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newTelegram = await tx
+ .insert(telegram)
+ .values({
+ botToken: input.botToken,
+ chatId: input.chatId,
+ messageThreadId: input.messageThreadId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newTelegram) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting telegram",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ telegramId: newTelegram.telegramId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "telegram",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateTelegramNotification = async (
+ input: typeof apiUpdateTelegram._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(telegram)
+ .set({
+ botToken: input.botToken,
+ chatId: input.chatId,
+ messageThreadId: input.messageThreadId,
+ })
+ .where(eq(telegram.telegramId, input.telegramId))
+ .returning()
+ .then((value) => value[0]);
+
+ return newDestination;
+ });
+};
+
+export const createDiscordNotification = async (
+ input: typeof apiCreateDiscord._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newDiscord = await tx
+ .insert(discord)
+ .values({
+ webhookUrl: input.webhookUrl,
+ decoration: input.decoration,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDiscord) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting discord",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ discordId: newDiscord.discordId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "discord",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateDiscordNotification = async (
+ input: typeof apiUpdateDiscord._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(discord)
+ .set({
+ webhookUrl: input.webhookUrl,
+ decoration: input.decoration,
+ })
+ .where(eq(discord.discordId, input.discordId))
+ .returning()
+ .then((value) => value[0]);
+
+ return newDestination;
+ });
+};
+
+export const createEmailNotification = async (
+ input: typeof apiCreateEmail._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newEmail = await tx
+ .insert(email)
+ .values({
+ smtpServer: input.smtpServer,
+ smtpPort: input.smtpPort,
+ username: input.username,
+ password: input.password,
+ fromAddress: input.fromAddress,
+ toAddresses: input.toAddresses,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newEmail) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting email",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ emailId: newEmail.emailId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "email",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateEmailNotification = async (
+ input: typeof apiUpdateEmail._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(email)
+ .set({
+ smtpServer: input.smtpServer,
+ smtpPort: input.smtpPort,
+ username: input.username,
+ password: input.password,
+ fromAddress: input.fromAddress,
+ toAddresses: input.toAddresses,
+ })
+ .where(eq(email.emailId, input.emailId))
+ .returning()
+ .then((value) => value[0]);
+
+ return newDestination;
+ });
+};
+
+export const createResendNotification = async (
+ input: typeof apiCreateResend._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newResend = await tx
+ .insert(resend)
+ .values({
+ apiKey: input.apiKey,
+ fromAddress: input.fromAddress,
+ toAddresses: input.toAddresses,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newResend) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting resend",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ resendId: newResend.resendId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "resend",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateResendNotification = async (
+ input: typeof apiUpdateResend._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(resend)
+ .set({
+ apiKey: input.apiKey,
+ fromAddress: input.fromAddress,
+ toAddresses: input.toAddresses,
+ })
+ .where(eq(resend.resendId, input.resendId))
+ .returning()
+ .then((value) => value[0]);
+
+ return newDestination;
+ });
+};
+
+export const createGotifyNotification = async (
+ input: typeof apiCreateGotify._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newGotify = await tx
+ .insert(gotify)
+ .values({
+ serverUrl: input.serverUrl,
+ appToken: input.appToken,
+ priority: input.priority,
+ decoration: input.decoration,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newGotify) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting gotify",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ gotifyId: newGotify.gotifyId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "gotify",
+ organizationId: organizationId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateGotifyNotification = async (
+ input: typeof apiUpdateGotify._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(gotify)
+ .set({
+ serverUrl: input.serverUrl,
+ appToken: input.appToken,
+ priority: input.priority,
+ decoration: input.decoration,
+ })
+ .where(eq(gotify.gotifyId, input.gotifyId));
+
+ return newDestination;
+ });
+};
+
+export const createNtfyNotification = async (
+ input: typeof apiCreateNtfy._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newNtfy = await tx
+ .insert(ntfy)
+ .values({
+ serverUrl: input.serverUrl,
+ topic: input.topic,
+ accessToken: input.accessToken ?? null,
+ priority: input.priority,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newNtfy) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting ntfy",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ ntfyId: newNtfy.ntfyId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "ntfy",
+ organizationId: organizationId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateNtfyNotification = async (
+ input: typeof apiUpdateNtfy._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(ntfy)
+ .set({
+ serverUrl: input.serverUrl,
+ topic: input.topic,
+ accessToken: input.accessToken ?? null,
+ priority: input.priority,
+ })
+ .where(eq(ntfy.ntfyId, input.ntfyId));
+
+ return newDestination;
+ });
+};
+
+export const createCustomNotification = async (
+ input: typeof apiCreateCustom._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newCustom = await tx
+ .insert(custom)
+ .values({
+ endpoint: input.endpoint,
+ headers: input.headers,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newCustom) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting custom",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ customId: newCustom.customId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "custom",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateCustomNotification = async (
+ input: typeof apiUpdateCustom._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(custom)
+ .set({
+ endpoint: input.endpoint,
+ headers: input.headers,
+ })
+ .where(eq(custom.customId, input.customId));
+
+ return newDestination;
+ });
+};
+
+export const findNotificationById = async (notificationId: string) => {
+ const notification = await db.query.notifications.findFirst({
+ where: eq(notifications.notificationId, notificationId),
+ with: {
+ slack: true,
+ telegram: true,
+ discord: true,
+ email: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ });
+ if (!notification) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Notification not found",
+ });
+ }
+ return notification;
+};
+
+export const removeNotificationById = async (notificationId: string) => {
+ const result = await db
+ .delete(notifications)
+ .where(eq(notifications.notificationId, notificationId))
+ .returning();
+
+ return result[0];
+};
+
+export const createLarkNotification = async (
+ input: typeof apiCreateLark._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newLark = await tx
+ .insert(lark)
+ .values({
+ webhookUrl: input.webhookUrl,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newLark) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting lark",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ larkId: newLark.larkId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ notificationType: "lark",
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updateLarkNotification = async (
+ input: typeof apiUpdateLark._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(lark)
+ .set({
+ webhookUrl: input.webhookUrl,
+ })
+ .where(eq(lark.larkId, input.larkId))
+ .returning()
+ .then((value) => value[0]);
+
+ return newDestination;
+ });
+};
+
+export const updateNotificationById = async (
+ notificationId: string,
+ notificationData: Partial,
+) => {
+ const result = await db
+ .update(notifications)
+ .set({
+ ...notificationData,
+ })
+ .where(eq(notifications.notificationId, notificationId))
+ .returning();
+
+ return result[0];
+};
+
+export const createPushoverNotification = async (
+ input: typeof apiCreatePushover._type,
+ organizationId: string,
+) => {
+ await db.transaction(async (tx) => {
+ const newPushover = await tx
+ .insert(pushover)
+ .values({
+ userKey: input.userKey,
+ apiToken: input.apiToken,
+ priority: input.priority,
+ retry: input.retry,
+ expire: input.expire,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newPushover) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting pushover",
+ });
+ }
+
+ const newDestination = await tx
+ .insert(notifications)
+ .values({
+ pushoverId: newPushover.pushoverId,
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ serverThreshold: input.serverThreshold,
+ notificationType: "pushover",
+ organizationId: organizationId,
+ })
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error input: Inserting notification",
+ });
+ }
+
+ return newDestination;
+ });
+};
+
+export const updatePushoverNotification = async (
+ input: typeof apiUpdatePushover._type,
+) => {
+ await db.transaction(async (tx) => {
+ const newDestination = await tx
+ .update(notifications)
+ .set({
+ name: input.name,
+ appDeploy: input.appDeploy,
+ appBuildError: input.appBuildError,
+ databaseBackup: input.databaseBackup,
+ volumeBackup: input.volumeBackup,
+ dokployRestart: input.dokployRestart,
+ dockerCleanup: input.dockerCleanup,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
+ })
+ .where(eq(notifications.notificationId, input.notificationId))
+ .returning()
+ .then((value) => value[0]);
+
+ if (!newDestination) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error Updating notification",
+ });
+ }
+
+ await tx
+ .update(pushover)
+ .set({
+ userKey: input.userKey,
+ apiToken: input.apiToken,
+ priority: input.priority,
+ retry: input.retry,
+ expire: input.expire,
+ })
+ .where(eq(pushover.pushoverId, input.pushoverId));
+
+ return newDestination;
+ });
+};
diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts
index 3c2497324..021ccd05a 100644
--- a/packages/server/src/utils/notifications/build-error.ts
+++ b/packages/server/src/utils/notifications/build-error.ts
@@ -1,61 +1,64 @@
-import { db } from "@dokploy/server/db";
-import { notifications } from "@dokploy/server/db/schema";
-import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";
-import { renderAsync } from "@react-email/components";
-import { format } from "date-fns";
-import { and, eq } from "drizzle-orm";
-import {
- sendCustomNotification,
- sendDiscordNotification,
+import { db } from "@dokploy/server/db";
+import { notifications } from "@dokploy/server/db/schema";
+import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";
+import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
+import {
+ sendCustomNotification,
+ sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-
-interface Props {
- projectName: string;
- applicationName: string;
- applicationType: string;
- errorMessage: string;
- buildLink: string;
- organizationId: string;
-}
-
-export const sendBuildErrorNotifications = async ({
- projectName,
- applicationName,
- applicationType,
- errorMessage,
- buildLink,
- organizationId,
-}: Props) => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: and(
- eq(notifications.appBuildError, true),
- eq(notifications.organizationId, organizationId),
- ),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- });
-
- for (const notification of notificationList) {
+
+interface Props {
+ projectName: string;
+ applicationName: string;
+ applicationType: string;
+ errorMessage: string;
+ buildLink: string;
+ organizationId: string;
+}
+
+export const sendBuildErrorNotifications = async ({
+ projectName,
+ applicationName,
+ applicationType,
+ errorMessage,
+ buildLink,
+ organizationId,
+}: Props) => {
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.appBuildError, true),
+ eq(notifications.organizationId, organizationId),
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ });
+
+ for (const notification of notificationList) {
const {
email,
+ resend,
discord,
telegram,
slack,
@@ -65,311 +68,322 @@ export const sendBuildErrorNotifications = async ({
lark,
pushover,
} = notification;
- try {
- if (email) {
- const template = await renderAsync(
- BuildFailedEmail({
- projectName,
- applicationName,
- applicationType,
- errorMessage: errorMessage,
- buildLink,
- date: date.toLocaleString(),
- }),
- ).catch();
- await sendEmailNotification(
- email,
- "Build failed for dokploy",
- template,
- );
- }
-
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- const limitCharacter = 800;
- const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
- await sendDiscordNotification(discord, {
- title: decorate(">", "`โ ๏ธ` Build Failed"),
- color: 0xed4245,
- fields: [
- {
- name: decorate("`๐ ๏ธ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`โ๏ธ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: applicationType,
- inline: true,
- },
- {
- name: decorate("`๐
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: "Failed",
- inline: true,
- },
- {
- name: decorate("`โ ๏ธ`", "Error Message"),
- value: `\`\`\`${truncatedErrorMessage}\`\`\``,
- },
- {
- name: decorate("`๐งท`", "Build Link"),
- value: `[Click here to access build link](${buildLink})`,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Build Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("โ ๏ธ", "Build Failed"),
- `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
- `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
- `${decorate("โ", `Type: ${applicationType}`)}` +
- `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
- `${decorate("โ ๏ธ", `Error:\n${errorMessage}`)}` +
- `${decorate("๐", `Build details:\n${buildLink}`)}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Build Failed",
- "warning",
- `view, Build details, ${buildLink}, clear=true;`,
- `๐ ๏ธProject: ${projectName}\n` +
- `โ๏ธApplication: ${applicationName}\n` +
- `โType: ${applicationType}\n` +
- `๐Date: ${date.toLocaleString()}\n` +
- `โ ๏ธError:\n${errorMessage}`,
- );
- }
-
- if (telegram) {
- const inlineButton = [
- [
- {
- text: "Deployment Logs",
- url: buildLink,
- },
- ],
- ];
-
- await sendTelegramNotification(
- telegram,
- `โ ๏ธ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}\n\nError:\n${errorMessage}`,
- inlineButton,
- );
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#FF0000",
- pretext: ":warning: *Build Failed*",
- fields: [
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Type",
- value: applicationType,
- short: true,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- {
- title: "Error",
- value: `\`\`\`${errorMessage}\`\`\``,
- short: false,
- },
- ],
- actions: [
- {
- type: "button",
- text: "View Build Details",
- url: buildLink,
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- await sendCustomNotification(custom, {
- title: "Build Error",
- message: "Build failed with errors",
- projectName,
- applicationName,
- applicationType,
- errorMessage,
- buildLink,
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: "error",
- type: "build",
- });
- }
-
- if (lark) {
- const limitCharacter = 800;
- const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content: "โ ๏ธ Build Failed",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "red",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Project:**\n${projectName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Type:**\n${applicationType}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Application:**\n${applicationName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- {
- tag: "button",
- text: {
- tag: "plain_text",
- content: "View Build Details",
- },
- type: "danger",
- width: "default",
- size: "medium",
- behaviors: [
- {
- type: "open_url",
- default_url: buildLink,
- pc_url: "",
- ios_url: "",
- android_url: "",
- },
- ],
- margin: "0px 0px 0px 0px",
- },
- ],
- },
- },
- });
- }
-
- if (pushover) {
- await sendPushoverNotification(
- pushover,
- "Build Failed",
- `Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`,
- );
- }
- } catch (error) {
- console.log(error);
- }
- }
-};
+ try {
+ if (email || resend) {
+ const template = await renderAsync(
+ BuildFailedEmail({
+ projectName,
+ applicationName,
+ applicationType,
+ errorMessage: errorMessage,
+ buildLink,
+ date: date.toLocaleString(),
+ }),
+ ).catch();
+
+ if (email) {
+ await sendEmailNotification(
+ email,
+ "Build failed for dokploy",
+ template,
+ );
+ }
+
+ if (resend) {
+ await sendResendNotification(
+ resend,
+ "Build failed for dokploy",
+ template,
+ );
+ }
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ const limitCharacter = 800;
+ const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`โ ๏ธ` Build Failed"),
+ color: 0xed4245,
+ fields: [
+ {
+ name: decorate("`๐ ๏ธ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ๏ธ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: applicationType,
+ inline: true,
+ },
+ {
+ name: decorate("`๐
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: "Failed",
+ inline: true,
+ },
+ {
+ name: decorate("`โ ๏ธ`", "Error Message"),
+ value: `\`\`\`${truncatedErrorMessage}\`\`\``,
+ },
+ {
+ name: decorate("`๐งท`", "Build Link"),
+ value: `[Click here to access build link](${buildLink})`,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Build Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("โ ๏ธ", "Build Failed"),
+ `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
+ `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
+ `${decorate("โ", `Type: ${applicationType}`)}` +
+ `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
+ `${decorate("โ ๏ธ", `Error:\n${errorMessage}`)}` +
+ `${decorate("๐", `Build details:\n${buildLink}`)}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Build Failed",
+ "warning",
+ `view, Build details, ${buildLink}, clear=true;`,
+ `๐ ๏ธProject: ${projectName}\n` +
+ `โ๏ธApplication: ${applicationName}\n` +
+ `โType: ${applicationType}\n` +
+ `๐Date: ${date.toLocaleString()}\n` +
+ `โ ๏ธError:\n${errorMessage}`,
+ );
+ }
+
+ if (telegram) {
+ const inlineButton = [
+ [
+ {
+ text: "Deployment Logs",
+ url: buildLink,
+ },
+ ],
+ ];
+
+ await sendTelegramNotification(
+ telegram,
+ `โ ๏ธ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}\n\nError:\n${errorMessage}`,
+ inlineButton,
+ );
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#FF0000",
+ pretext: ":warning: *Build Failed*",
+ fields: [
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: applicationType,
+ short: true,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ {
+ title: "Error",
+ value: `\`\`\`${errorMessage}\`\`\``,
+ short: false,
+ },
+ ],
+ actions: [
+ {
+ type: "button",
+ text: "View Build Details",
+ url: buildLink,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: "Build Error",
+ message: "Build failed with errors",
+ projectName,
+ applicationName,
+ applicationType,
+ errorMessage,
+ buildLink,
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ status: "error",
+ type: "build",
+ });
+ }
+
+ if (lark) {
+ const limitCharacter = 800;
+ const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "โ ๏ธ Build Failed",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "red",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Project:**\n${projectName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Type:**\n${applicationType}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Application:**\n${applicationName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ {
+ tag: "button",
+ text: {
+ tag: "plain_text",
+ content: "View Build Details",
+ },
+ type: "danger",
+ width: "default",
+ size: "medium",
+ behaviors: [
+ {
+ type: "open_url",
+ default_url: buildLink,
+ pc_url: "",
+ ios_url: "",
+ android_url: "",
+ },
+ ],
+ margin: "0px 0px 0px 0px",
+ },
+ ],
+ },
+ },
+ });
+ }
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Build Failed",
+ `Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+};
diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts
index d1bc04796..e5a735b4d 100644
--- a/packages/server/src/utils/notifications/build-success.ts
+++ b/packages/server/src/utils/notifications/build-success.ts
@@ -1,64 +1,67 @@
-import { db } from "@dokploy/server/db";
-import { notifications } from "@dokploy/server/db/schema";
-import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success";
-import type { Domain } from "@dokploy/server/services/domain";
-import { renderAsync } from "@react-email/components";
-import { format } from "date-fns";
-import { and, eq } from "drizzle-orm";
-import {
- sendCustomNotification,
- sendDiscordNotification,
+import { db } from "@dokploy/server/db";
+import { notifications } from "@dokploy/server/db/schema";
+import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success";
+import type { Domain } from "@dokploy/server/services/domain";
+import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
+import {
+ sendCustomNotification,
+ sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-
-interface Props {
- projectName: string;
- applicationName: string;
- applicationType: string;
- buildLink: string;
- organizationId: string;
- domains: Domain[];
- environmentName: string;
-}
-
-export const sendBuildSuccessNotifications = async ({
- projectName,
- applicationName,
- applicationType,
- buildLink,
- organizationId,
- domains,
- environmentName,
-}: Props) => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: and(
- eq(notifications.appDeploy, true),
- eq(notifications.organizationId, organizationId),
- ),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- });
-
- for (const notification of notificationList) {
+
+interface Props {
+ projectName: string;
+ applicationName: string;
+ applicationType: string;
+ buildLink: string;
+ organizationId: string;
+ domains: Domain[];
+ environmentName: string;
+}
+
+export const sendBuildSuccessNotifications = async ({
+ projectName,
+ applicationName,
+ applicationType,
+ buildLink,
+ organizationId,
+ domains,
+ environmentName,
+}: Props) => {
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.appDeploy, true),
+ eq(notifications.organizationId, organizationId),
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ });
+
+ for (const notification of notificationList) {
const {
email,
+ resend,
discord,
telegram,
slack,
@@ -68,322 +71,333 @@ export const sendBuildSuccessNotifications = async ({
lark,
pushover,
} = notification;
- try {
- if (email) {
- const template = await renderAsync(
- BuildSuccessEmail({
- projectName,
- applicationName,
- applicationType,
- buildLink,
- date: date.toLocaleString(),
- environmentName,
- }),
- ).catch();
- await sendEmailNotification(
- email,
- "Build success for dokploy",
- template,
- );
- }
-
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(discord, {
- title: decorate(">", "`โ
` Build Successes"),
- color: 0x57f287,
- fields: [
- {
- name: decorate("`๐ ๏ธ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`โ๏ธ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`๐`", "Environment"),
- value: environmentName,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: applicationType,
- inline: true,
- },
- {
- name: decorate("`๐
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: "Successful",
- inline: true,
- },
- {
- name: decorate("`๐งท`", "Build Link"),
- value: `[Click here to access build link](${buildLink})`,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Build Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("โ
", "Build Success"),
- `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
- `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
- `${decorate("๐", `Environment: ${environmentName}`)}` +
- `${decorate("โ", `Type: ${applicationType}`)}` +
- `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
- `${decorate("๐", `Build details:\n${buildLink}`)}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Build Success",
- "white_check_mark",
- `view, Build details, ${buildLink}, clear=true;`,
- `๐ Project: ${projectName}\n` +
- `โ๏ธApplication: ${applicationName}\n` +
- `๐Environment: ${environmentName}\n` +
- `โType: ${applicationType}\n` +
- `๐Date: ${date.toLocaleString()}`,
- );
- }
-
- if (telegram) {
- const chunkArray = (array: T[], chunkSize: number): T[][] =>
- Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
- array.slice(i * chunkSize, i * chunkSize + chunkSize),
- );
-
- const inlineButton = [
- [
- {
- text: "Deployment Logs",
- url: buildLink,
- },
- ],
- ...chunkArray(domains, 2).map((chunk) =>
- chunk.map((data) => ({
- text: data.host,
- url: `${data.https ? "https" : "http"}://${data.host}`,
- })),
- ),
- ];
-
- await sendTelegramNotification(
- telegram,
- `โ
Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${format(
- date,
- "PP",
- )}\nTime: ${format(date, "pp")}`,
- inlineButton,
- );
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#00FF00",
- pretext: ":white_check_mark: *Build Success*",
- fields: [
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Environment",
- value: environmentName,
- short: true,
- },
- {
- title: "Type",
- value: applicationType,
- short: true,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- ],
- actions: [
- {
- type: "button",
- text: "View Build Details",
- url: buildLink,
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- await sendCustomNotification(custom, {
- title: "Build Success",
- message: "Build completed successfully",
- projectName,
- applicationName,
- applicationType,
- buildLink,
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- domains: domains.map((domain) => domain.host).join(", "),
- status: "success",
- type: "build",
- });
- }
-
- if (lark) {
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content: "โ
Build Success",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "green",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Project:**\n${projectName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Environment:**\n${environmentName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Type:**\n${applicationType}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Application:**\n${applicationName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- {
- tag: "button",
- text: {
- tag: "plain_text",
- content: "View Build Details",
- },
- type: "primary",
- width: "default",
- size: "medium",
- behaviors: [
- {
- type: "open_url",
- default_url: buildLink,
- pc_url: "",
- ios_url: "",
- android_url: "",
- },
- ],
- margin: "0px 0px 0px 0px",
- },
- ],
- },
- },
- });
- }
-
- if (pushover) {
- await sendPushoverNotification(
- pushover,
- "Build Success",
- `Project: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}`,
- );
- }
- } catch (error) {
- console.log(error);
- }
- }
-};
+ try {
+ if (email || resend) {
+ const template = await renderAsync(
+ BuildSuccessEmail({
+ projectName,
+ applicationName,
+ applicationType,
+ buildLink,
+ date: date.toLocaleString(),
+ environmentName,
+ }),
+ ).catch();
+
+ if (email) {
+ await sendEmailNotification(
+ email,
+ "Build success for dokploy",
+ template,
+ );
+ }
+
+ if (resend) {
+ await sendResendNotification(
+ resend,
+ "Build success for dokploy",
+ template,
+ );
+ }
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`โ
` Build Successes"),
+ color: 0x57f287,
+ fields: [
+ {
+ name: decorate("`๐ ๏ธ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ๏ธ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`๐`", "Environment"),
+ value: environmentName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: applicationType,
+ inline: true,
+ },
+ {
+ name: decorate("`๐
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: "Successful",
+ inline: true,
+ },
+ {
+ name: decorate("`๐งท`", "Build Link"),
+ value: `[Click here to access build link](${buildLink})`,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Build Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("โ
", "Build Success"),
+ `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
+ `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
+ `${decorate("๐", `Environment: ${environmentName}`)}` +
+ `${decorate("โ", `Type: ${applicationType}`)}` +
+ `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
+ `${decorate("๐", `Build details:\n${buildLink}`)}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Build Success",
+ "white_check_mark",
+ `view, Build details, ${buildLink}, clear=true;`,
+ `๐ Project: ${projectName}\n` +
+ `โ๏ธApplication: ${applicationName}\n` +
+ `๐Environment: ${environmentName}\n` +
+ `โType: ${applicationType}\n` +
+ `๐Date: ${date.toLocaleString()}`,
+ );
+ }
+
+ if (telegram) {
+ const chunkArray = (array: T[], chunkSize: number): T[][] =>
+ Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
+ array.slice(i * chunkSize, i * chunkSize + chunkSize),
+ );
+
+ const inlineButton = [
+ [
+ {
+ text: "Deployment Logs",
+ url: buildLink,
+ },
+ ],
+ ...chunkArray(domains, 2).map((chunk) =>
+ chunk.map((data) => ({
+ text: data.host,
+ url: `${data.https ? "https" : "http"}://${data.host}`,
+ })),
+ ),
+ ];
+
+ await sendTelegramNotification(
+ telegram,
+ `โ
Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${format(
+ date,
+ "PP",
+ )}\nTime: ${format(date, "pp")}`,
+ inlineButton,
+ );
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#00FF00",
+ pretext: ":white_check_mark: *Build Success*",
+ fields: [
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Environment",
+ value: environmentName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: applicationType,
+ short: true,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ actions: [
+ {
+ type: "button",
+ text: "View Build Details",
+ url: buildLink,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: "Build Success",
+ message: "Build completed successfully",
+ projectName,
+ applicationName,
+ applicationType,
+ buildLink,
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ domains: domains.map((domain) => domain.host).join(", "),
+ status: "success",
+ type: "build",
+ });
+ }
+
+ if (lark) {
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "โ
Build Success",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "green",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Project:**\n${projectName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Environment:**\n${environmentName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Type:**\n${applicationType}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Application:**\n${applicationName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ {
+ tag: "button",
+ text: {
+ tag: "plain_text",
+ content: "View Build Details",
+ },
+ type: "primary",
+ width: "default",
+ size: "medium",
+ behaviors: [
+ {
+ type: "open_url",
+ default_url: buildLink,
+ pc_url: "",
+ ios_url: "",
+ android_url: "",
+ },
+ ],
+ margin: "0px 0px 0px 0px",
+ },
+ ],
+ },
+ },
+ });
+ }
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Build Success",
+ `Project: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}`,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+};
diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts
index 1b2b49bf1..2c9e5b78e 100644
--- a/packages/server/src/utils/notifications/database-backup.ts
+++ b/packages/server/src/utils/notifications/database-backup.ts
@@ -1,61 +1,64 @@
-import { db } from "@dokploy/server/db";
-import { notifications } from "@dokploy/server/db/schema";
-import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
-import { renderAsync } from "@react-email/components";
-import { format } from "date-fns";
-import { and, eq } from "drizzle-orm";
-import {
- sendCustomNotification,
- sendDiscordNotification,
+import { db } from "@dokploy/server/db";
+import { notifications } from "@dokploy/server/db/schema";
+import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
+import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
+import {
+ sendCustomNotification,
+ sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-
-export const sendDatabaseBackupNotifications = async ({
- projectName,
- applicationName,
- databaseType,
- type,
- errorMessage,
- organizationId,
- databaseName,
-}: {
- projectName: string;
- applicationName: string;
- databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
- type: "error" | "success";
- organizationId: string;
- errorMessage?: string;
- databaseName: string;
-}) => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: and(
- eq(notifications.databaseBackup, true),
- eq(notifications.organizationId, organizationId),
- ),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- });
-
- for (const notification of notificationList) {
+
+export const sendDatabaseBackupNotifications = async ({
+ projectName,
+ applicationName,
+ databaseType,
+ type,
+ errorMessage,
+ organizationId,
+ databaseName,
+}: {
+ projectName: string;
+ applicationName: string;
+ databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
+ type: "error" | "success";
+ organizationId: string;
+ errorMessage?: string;
+ databaseName: string;
+}) => {
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.databaseBackup, true),
+ eq(notifications.organizationId, organizationId),
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ });
+
+ for (const notification of notificationList) {
const {
email,
+ resend,
discord,
telegram,
slack,
@@ -65,339 +68,350 @@ export const sendDatabaseBackupNotifications = async ({
lark,
pushover,
} = notification;
- try {
- if (email) {
- const template = await renderAsync(
- DatabaseBackupEmail({
- projectName,
- applicationName,
- databaseType,
- type,
- errorMessage,
- date: date.toLocaleString(),
- }),
- ).catch();
- await sendEmailNotification(
- email,
- "Database backup for dokploy",
- template,
- );
- }
-
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(discord, {
- title:
- type === "success"
- ? decorate(">", "`โ
` Database Backup Successful")
- : decorate(">", "`โ` Database Backup Failed"),
- color: type === "success" ? 0x57f287 : 0xed4245,
- fields: [
- {
- name: decorate("`๐ ๏ธ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`โ๏ธ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`โ`", "Database"),
- value: databaseType,
- inline: true,
- },
- {
- name: decorate("`๐`", "Database Name"),
- value: databaseName,
- inline: true,
- },
- {
- name: decorate("`๐
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: type
- .replace("error", "Failed")
- .replace("success", "Successful"),
- inline: true,
- },
- ...(type === "error" && errorMessage
- ? [
- {
- name: decorate("`โ ๏ธ`", "Error Message"),
- value: `\`\`\`${errorMessage}\`\`\``,
- },
- ]
- : []),
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Database Backup Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
-
- await sendGotifyNotification(
- gotify,
- decorate(
- type === "success" ? "โ
" : "โ",
- `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- ),
- `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
- `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
- `${decorate("โ", `Type: ${databaseType}`)}` +
- `${decorate("๐", `Database Name: ${databaseName}`)}` +
- `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
- `${type === "error" && errorMessage ? decorate("โ", `Error:\n${errorMessage}`) : ""}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- `${type === "success" ? "white_check_mark" : "x"}`,
- "",
- `๐ Project: ${projectName}\n` +
- `โ๏ธApplication: ${applicationName}\n` +
- `โType: ${databaseType}\n` +
- `๐Database Name: ${databaseName}` +
- `๐Date: ${date.toLocaleString()}\n` +
- `${type === "error" && errorMessage ? `โError:\n${errorMessage}` : ""}`,
- );
- }
-
- if (telegram) {
- const isError = type === "error" && errorMessage;
-
- const statusEmoji = type === "success" ? "โ
" : "โ";
- const typeStatus = type === "success" ? "Successful" : "Failed";
- const errorMsg = isError
- ? `\n\nError:\n${errorMessage}`
- : "";
-
- const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
-
- await sendTelegramNotification(telegram, messageText);
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: type === "success" ? "#00FF00" : "#FF0000",
- pretext:
- type === "success"
- ? ":white_check_mark: *Database Backup Successful*"
- : ":x: *Database Backup Failed*",
- fields: [
- ...(type === "error" && errorMessage
- ? [
- {
- title: "Error Message",
- value: errorMessage,
- short: false,
- },
- ]
- : []),
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Type",
- value: databaseType,
- short: true,
- },
- {
- title: "Database Name",
- value: databaseName,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- {
- title: "Type",
- value: type,
- },
- {
- title: "Status",
- value: type === "success" ? "Successful" : "Failed",
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- await sendCustomNotification(custom, {
- title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- message:
- type === "success"
- ? "Database backup completed successfully"
- : "Database backup failed",
- projectName,
- applicationName,
- databaseType,
- databaseName,
- type,
- errorMessage: errorMessage || "",
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: type,
- });
- }
-
- if (lark) {
- const limitCharacter = 800;
- const truncatedErrorMessage =
- errorMessage && errorMessage.length > limitCharacter
- ? errorMessage.substring(0, limitCharacter)
- : errorMessage;
-
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content:
- type === "success"
- ? "โ
Database Backup Successful"
- : "โ Database Backup Failed",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: type === "success" ? "green" : "red",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Project:**\n${projectName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Database Type:**\n${databaseType}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Status:**\n${type === "success" ? "Successful" : "Failed"}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Application:**\n${applicationName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Database Name:**\n${databaseName}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- ...(type === "error" && truncatedErrorMessage
- ? [
- {
- tag: "markdown",
- content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
- text_align: "left",
- text_size: "normal_v2",
- },
- ]
- : []),
- ],
- },
- },
- });
- }
-
- if (pushover) {
- await sendPushoverNotification(
- pushover,
- `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
- `Project: ${projectName}\nApplication: ${applicationName}\nDatabase: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
- );
- }
- } catch (error) {
- console.log(error);
- }
- }
-};
+ try {
+ if (email || resend) {
+ const template = await renderAsync(
+ DatabaseBackupEmail({
+ projectName,
+ applicationName,
+ databaseType,
+ type,
+ errorMessage,
+ date: date.toLocaleString(),
+ }),
+ ).catch();
+
+ if (email) {
+ await sendEmailNotification(
+ email,
+ "Database backup for dokploy",
+ template,
+ );
+ }
+
+ if (resend) {
+ await sendResendNotification(
+ resend,
+ "Database backup for dokploy",
+ template,
+ );
+ }
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title:
+ type === "success"
+ ? decorate(">", "`โ
` Database Backup Successful")
+ : decorate(">", "`โ` Database Backup Failed"),
+ color: type === "success" ? 0x57f287 : 0xed4245,
+ fields: [
+ {
+ name: decorate("`๐ ๏ธ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ๏ธ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Database"),
+ value: databaseType,
+ inline: true,
+ },
+ {
+ name: decorate("`๐`", "Database Name"),
+ value: databaseName,
+ inline: true,
+ },
+ {
+ name: decorate("`๐
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: type
+ .replace("error", "Failed")
+ .replace("success", "Successful"),
+ inline: true,
+ },
+ ...(type === "error" && errorMessage
+ ? [
+ {
+ name: decorate("`โ ๏ธ`", "Error Message"),
+ value: `\`\`\`${errorMessage}\`\`\``,
+ },
+ ]
+ : []),
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Database Backup Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+
+ await sendGotifyNotification(
+ gotify,
+ decorate(
+ type === "success" ? "โ
" : "โ",
+ `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ ),
+ `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
+ `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
+ `${decorate("โ", `Type: ${databaseType}`)}` +
+ `${decorate("๐", `Database Name: ${databaseName}`)}` +
+ `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
+ `${type === "error" && errorMessage ? decorate("โ", `Error:\n${errorMessage}`) : ""}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `${type === "success" ? "white_check_mark" : "x"}`,
+ "",
+ `๐ Project: ${projectName}\n` +
+ `โ๏ธApplication: ${applicationName}\n` +
+ `โType: ${databaseType}\n` +
+ `๐Database Name: ${databaseName}` +
+ `๐Date: ${date.toLocaleString()}\n` +
+ `${type === "error" && errorMessage ? `โError:\n${errorMessage}` : ""}`,
+ );
+ }
+
+ if (telegram) {
+ const isError = type === "error" && errorMessage;
+
+ const statusEmoji = type === "success" ? "โ
" : "โ";
+ const typeStatus = type === "success" ? "Successful" : "Failed";
+ const errorMsg = isError
+ ? `\n\nError:\n${errorMessage}`
+ : "";
+
+ const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
+
+ await sendTelegramNotification(telegram, messageText);
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: type === "success" ? "#00FF00" : "#FF0000",
+ pretext:
+ type === "success"
+ ? ":white_check_mark: *Database Backup Successful*"
+ : ":x: *Database Backup Failed*",
+ fields: [
+ ...(type === "error" && errorMessage
+ ? [
+ {
+ title: "Error Message",
+ value: errorMessage,
+ short: false,
+ },
+ ]
+ : []),
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: databaseType,
+ short: true,
+ },
+ {
+ title: "Database Name",
+ value: databaseName,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ {
+ title: "Type",
+ value: type,
+ },
+ {
+ title: "Status",
+ value: type === "success" ? "Successful" : "Failed",
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ message:
+ type === "success"
+ ? "Database backup completed successfully"
+ : "Database backup failed",
+ projectName,
+ applicationName,
+ databaseType,
+ databaseName,
+ type,
+ errorMessage: errorMessage || "",
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ status: type,
+ });
+ }
+
+ if (lark) {
+ const limitCharacter = 800;
+ const truncatedErrorMessage =
+ errorMessage && errorMessage.length > limitCharacter
+ ? errorMessage.substring(0, limitCharacter)
+ : errorMessage;
+
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content:
+ type === "success"
+ ? "โ
Database Backup Successful"
+ : "โ Database Backup Failed",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: type === "success" ? "green" : "red",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Project:**\n${projectName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Database Type:**\n${databaseType}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Status:**\n${type === "success" ? "Successful" : "Failed"}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Application:**\n${applicationName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Database Name:**\n${databaseName}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ ...(type === "error" && truncatedErrorMessage
+ ? [
+ {
+ tag: "markdown",
+ content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ]
+ : []),
+ ],
+ },
+ },
+ });
+ }
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `Project: ${projectName}\nApplication: ${applicationName}\nDatabase: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+};
diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts
index 834ff489c..0646a2b64 100644
--- a/packages/server/src/utils/notifications/docker-cleanup.ts
+++ b/packages/server/src/utils/notifications/docker-cleanup.ts
@@ -1,48 +1,51 @@
-import { db } from "@dokploy/server/db";
-import { notifications } from "@dokploy/server/db/schema";
-import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup";
-import { renderAsync } from "@react-email/components";
-import { format } from "date-fns";
-import { and, eq } from "drizzle-orm";
-import {
- sendCustomNotification,
- sendDiscordNotification,
+import { db } from "@dokploy/server/db";
+import { notifications } from "@dokploy/server/db/schema";
+import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup";
+import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
+import {
+ sendCustomNotification,
+ sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-
-export const sendDockerCleanupNotifications = async (
- organizationId: string,
- message = "Docker cleanup for dokploy",
-) => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: and(
- eq(notifications.dockerCleanup, true),
- eq(notifications.organizationId, organizationId),
- ),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- });
-
- for (const notification of notificationList) {
+
+export const sendDockerCleanupNotifications = async (
+ organizationId: string,
+ message = "Docker cleanup for dokploy",
+) => {
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.dockerCleanup, true),
+ eq(notifications.organizationId, organizationId),
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ });
+
+ for (const notification of notificationList) {
const {
email,
+ resend,
discord,
telegram,
slack,
@@ -52,205 +55,215 @@ export const sendDockerCleanupNotifications = async (
lark,
pushover,
} = notification;
- try {
- if (email) {
- const template = await renderAsync(
- DockerCleanupEmail({ message, date: date.toLocaleString() }),
- ).catch();
-
- await sendEmailNotification(
- email,
- "Docker cleanup for dokploy",
- template,
- );
- }
-
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(discord, {
- title: decorate(">", "`โ
` Docker Cleanup"),
- color: 0x57f287,
- fields: [
- {
- name: decorate("`๐
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: "Successful",
- inline: true,
- },
- {
- name: decorate("`๐`", "Message"),
- value: `\`\`\`${message}\`\`\``,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Docker Cleanup Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("โ
", "Docker Cleanup"),
- `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
- `${decorate("๐", `Message:\n${message}`)}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Docker Cleanup",
- "white_check_mark",
- "",
- `๐Date: ${date.toLocaleString()}\n` + `๐Message:\n${message}`,
- );
- }
-
- if (telegram) {
- await sendTelegramNotification(
- telegram,
- `โ
Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
- );
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#00FF00",
- pretext: ":white_check_mark: *Docker Cleanup*",
- fields: [
- {
- title: "Message",
- value: message,
- },
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- await sendCustomNotification(custom, {
- title: "Docker Cleanup",
- message: "Docker cleanup completed successfully",
- cleanupMessage: message,
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: "success",
- type: "docker-cleanup",
- });
- }
-
- if (lark) {
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content: "โ
Docker Cleanup",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "green",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: "**Status:**\nSuccessful",
- text_align: "left",
- text_size: "normal_v2",
- },
- {
- tag: "markdown",
- content: `**Cleanup Details:**\n${message}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Date:**\n${format(date, "PP pp")}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- ],
- },
- },
- });
- }
-
- if (pushover) {
- await sendPushoverNotification(
- pushover,
- "Docker Cleanup",
- `Date: ${date.toLocaleString()}\nMessage: ${message}`,
- );
- }
- } catch (error) {
- console.log(error);
- }
- }
-};
+ try {
+ if (email || resend) {
+ const template = await renderAsync(
+ DockerCleanupEmail({ message, date: date.toLocaleString() }),
+ ).catch();
+
+ if (email) {
+ await sendEmailNotification(
+ email,
+ "Docker cleanup for dokploy",
+ template,
+ );
+ }
+
+ if (resend) {
+ await sendResendNotification(
+ resend,
+ "Docker cleanup for dokploy",
+ template,
+ );
+ }
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`โ
` Docker Cleanup"),
+ color: 0x57f287,
+ fields: [
+ {
+ name: decorate("`๐
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: "Successful",
+ inline: true,
+ },
+ {
+ name: decorate("`๐`", "Message"),
+ value: `\`\`\`${message}\`\`\``,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Docker Cleanup Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("โ
", "Docker Cleanup"),
+ `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
+ `${decorate("๐", `Message:\n${message}`)}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Docker Cleanup",
+ "white_check_mark",
+ "",
+ `๐Date: ${date.toLocaleString()}\n` + `๐Message:\n${message}`,
+ );
+ }
+
+ if (telegram) {
+ await sendTelegramNotification(
+ telegram,
+ `โ
Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`,
+ );
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#00FF00",
+ pretext: ":white_check_mark: *Docker Cleanup*",
+ fields: [
+ {
+ title: "Message",
+ value: message,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ await sendCustomNotification(custom, {
+ title: "Docker Cleanup",
+ message: "Docker cleanup completed successfully",
+ cleanupMessage: message,
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ status: "success",
+ type: "docker-cleanup",
+ });
+ }
+
+ if (lark) {
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "โ
Docker Cleanup",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "green",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: "**Status:**\nSuccessful",
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ {
+ tag: "markdown",
+ content: `**Cleanup Details:**\n${message}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Date:**\n${format(date, "PP pp")}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ });
+ }
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Docker Cleanup",
+ `Date: ${date.toLocaleString()}\nMessage: ${message}`,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+};
diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts
index f93f31ac5..6526fb222 100644
--- a/packages/server/src/utils/notifications/dokploy-restart.ts
+++ b/packages/server/src/utils/notifications/dokploy-restart.ts
@@ -1,42 +1,45 @@
-import { db } from "@dokploy/server/db";
-import { notifications } from "@dokploy/server/db/schema";
-import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart";
-import { renderAsync } from "@react-email/components";
-import { format } from "date-fns";
-import { eq } from "drizzle-orm";
-import {
- sendCustomNotification,
- sendDiscordNotification,
+import { db } from "@dokploy/server/db";
+import { notifications } from "@dokploy/server/db/schema";
+import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart";
+import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
+import { eq } from "drizzle-orm";
+import {
+ sendCustomNotification,
+ sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-
-export const sendDokployRestartNotifications = async () => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: eq(notifications.dokployRestart, true),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- custom: true,
- lark: true,
- pushover: true,
- },
- });
-
- for (const notification of notificationList) {
+
+export const sendDokployRestartNotifications = async () => {
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: eq(notifications.dokployRestart, true),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ custom: true,
+ lark: true,
+ pushover: true,
+ },
+ });
+
+ for (const notification of notificationList) {
const {
email,
+ resend,
discord,
telegram,
slack,
@@ -46,200 +49,210 @@ export const sendDokployRestartNotifications = async () => {
lark,
pushover,
} = notification;
-
- try {
- if (email) {
- const template = await renderAsync(
- DokployRestartEmail({ date: date.toLocaleString() }),
- ).catch();
-
- await sendEmailNotification(
- email,
- "Dokploy Server Restarted",
- template,
- );
- }
-
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(discord, {
- title: decorate(">", "`โ
` Dokploy Server Restarted"),
- color: 0x57f287,
- fields: [
- {
- name: decorate("`๐
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: "Successful",
- inline: true,
- },
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Restart Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
- await sendGotifyNotification(
- gotify,
- decorate("โ
", "Dokploy Server Restarted"),
- `${decorate("๐", `Date: ${date.toLocaleString()}`)}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- "Dokploy Server Restarted",
- "white_check_mark",
- "",
- `๐Date: ${date.toLocaleString()}`,
- );
- }
-
- if (telegram) {
- await sendTelegramNotification(
- telegram,
- `โ
Dokploy Server Restarted\n\nDate: ${format(
- date,
- "PP",
- )}\nTime: ${format(date, "pp")}`,
- );
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: "#00FF00",
- pretext: ":white_check_mark: *Dokploy Server Restarted*",
- fields: [
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- ],
- },
- ],
- });
- }
-
- if (custom) {
- try {
- await sendCustomNotification(custom, {
- title: "Dokploy Server Restarted",
- message: "Dokploy server has been restarted successfully",
- timestamp: date.toISOString(),
- date: date.toLocaleString(),
- status: "success",
- type: "dokploy-restart",
- });
- } catch (error) {
- console.log(error);
- }
- }
-
- if (lark) {
- await sendLarkNotification(lark, {
- msg_type: "interactive",
- card: {
- schema: "2.0",
- config: {
- update_multi: true,
- style: {
- text_size: {
- normal_v2: {
- default: "normal",
- pc: "normal",
- mobile: "heading",
- },
- },
- },
- },
- header: {
- title: {
- tag: "plain_text",
- content: "โ
Dokploy Server Restarted",
- },
- subtitle: {
- tag: "plain_text",
- content: "",
- },
- template: "green",
- padding: "12px 12px 12px 12px",
- },
- body: {
- direction: "vertical",
- padding: "12px 12px 12px 12px",
- elements: [
- {
- tag: "column_set",
- columns: [
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: "**Status:**\nSuccessful",
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- {
- tag: "column",
- width: "weighted",
- elements: [
- {
- tag: "markdown",
- content: `**Restart Time:**\n${format(
- date,
- "PP pp",
- )}`,
- text_align: "left",
- text_size: "normal_v2",
- },
- ],
- vertical_align: "top",
- weight: 1,
- },
- ],
- },
- ],
- },
- },
- });
- }
-
- if (pushover) {
- await sendPushoverNotification(
- pushover,
- "Dokploy Server Restarted",
- `Date: ${date.toLocaleString()}`,
- );
- }
- } catch (error) {
- console.log(error);
- }
- }
-};
+
+ try {
+ if (email || resend) {
+ const template = await renderAsync(
+ DokployRestartEmail({ date: date.toLocaleString() }),
+ ).catch();
+
+ if (email) {
+ await sendEmailNotification(
+ email,
+ "Dokploy Server Restarted",
+ template,
+ );
+ }
+
+ if (resend) {
+ await sendResendNotification(
+ resend,
+ "Dokploy Server Restarted",
+ template,
+ );
+ }
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title: decorate(">", "`โ
` Dokploy Server Restarted"),
+ color: 0x57f287,
+ fields: [
+ {
+ name: decorate("`๐
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: "Successful",
+ inline: true,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Restart Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+ await sendGotifyNotification(
+ gotify,
+ decorate("โ
", "Dokploy Server Restarted"),
+ `${decorate("๐", `Date: ${date.toLocaleString()}`)}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ "Dokploy Server Restarted",
+ "white_check_mark",
+ "",
+ `๐Date: ${date.toLocaleString()}`,
+ );
+ }
+
+ if (telegram) {
+ await sendTelegramNotification(
+ telegram,
+ `โ
Dokploy Server Restarted\n\nDate: ${format(
+ date,
+ "PP",
+ )}\nTime: ${format(date, "pp")}`,
+ );
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#00FF00",
+ pretext: ":white_check_mark: *Dokploy Server Restarted*",
+ fields: [
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (custom) {
+ try {
+ await sendCustomNotification(custom, {
+ title: "Dokploy Server Restarted",
+ message: "Dokploy server has been restarted successfully",
+ timestamp: date.toISOString(),
+ date: date.toLocaleString(),
+ status: "success",
+ type: "dokploy-restart",
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ if (lark) {
+ await sendLarkNotification(lark, {
+ msg_type: "interactive",
+ card: {
+ schema: "2.0",
+ config: {
+ update_multi: true,
+ style: {
+ text_size: {
+ normal_v2: {
+ default: "normal",
+ pc: "normal",
+ mobile: "heading",
+ },
+ },
+ },
+ },
+ header: {
+ title: {
+ tag: "plain_text",
+ content: "โ
Dokploy Server Restarted",
+ },
+ subtitle: {
+ tag: "plain_text",
+ content: "",
+ },
+ template: "green",
+ padding: "12px 12px 12px 12px",
+ },
+ body: {
+ direction: "vertical",
+ padding: "12px 12px 12px 12px",
+ elements: [
+ {
+ tag: "column_set",
+ columns: [
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: "**Status:**\nSuccessful",
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ {
+ tag: "column",
+ width: "weighted",
+ elements: [
+ {
+ tag: "markdown",
+ content: `**Restart Time:**\n${format(
+ date,
+ "PP pp",
+ )}`,
+ text_align: "left",
+ text_size: "normal_v2",
+ },
+ ],
+ vertical_align: "top",
+ weight: 1,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ });
+ }
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ "Dokploy Server Restarted",
+ `Date: ${date.toLocaleString()}`,
+ );
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ }
+};
diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts
index 170b90e8a..72a6b04be 100644
--- a/packages/server/src/utils/notifications/utils.ts
+++ b/packages/server/src/utils/notifications/utils.ts
@@ -1,256 +1,284 @@
-import type {
- custom,
- discord,
- email,
- gotify,
+import type {
+ custom,
+ discord,
+ email,
+ gotify,
lark,
ntfy,
pushover,
+ resend,
slack,
telegram,
} from "@dokploy/server/db/schema";
-import nodemailer from "nodemailer";
-
-export const sendEmailNotification = async (
- connection: typeof email.$inferInsert,
- subject: string,
- htmlContent: string,
-) => {
- try {
- const {
- smtpServer,
- smtpPort,
- username,
- password,
- fromAddress,
- toAddresses,
- } = connection;
- const transporter = nodemailer.createTransport({
- host: smtpServer,
- port: smtpPort,
- auth: { user: username, pass: password },
- });
-
- await transporter.sendMail({
- from: fromAddress,
- to: toAddresses.join(", "),
- subject,
- html: htmlContent,
- textEncoding: "base64",
- });
- } catch (err) {
- console.log(err);
- throw new Error(
- `Failed to send email notification ${err instanceof Error ? err.message : "Unknown error"}`,
- );
- }
-};
-
-export const sendDiscordNotification = async (
- connection: typeof discord.$inferInsert,
- embed: any,
-) => {
- try {
- const response = await fetch(connection.webhookUrl, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ embeds: [embed] }),
- });
- if (!response.ok) {
- throw new Error(
- `Failed to send discord notification ${response.statusText}`,
- );
- }
- } catch (err) {
- console.log("error", err);
- throw new Error(
- `Failed to send discord notification ${err instanceof Error ? err.message : "Unknown error"}`,
- );
- }
-};
-
-export const sendTelegramNotification = async (
- connection: typeof telegram.$inferInsert,
- messageText: string,
- inlineButton?: {
- text: string;
- url: string;
- }[][],
-) => {
- try {
- const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
- await fetch(url, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- chat_id: connection.chatId,
- message_thread_id: connection.messageThreadId,
- text: messageText,
- parse_mode: "HTML",
- disable_web_page_preview: true,
- reply_markup: {
- inline_keyboard: inlineButton,
- },
- }),
- });
- } catch (err) {
- console.log(err);
- }
-};
-
-export const sendSlackNotification = async (
- connection: typeof slack.$inferInsert,
- message: any,
-) => {
- try {
- const response = await fetch(connection.webhookUrl, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(message),
- });
- if (!response.ok) {
- throw new Error(
- `Failed to send slack notification ${response.statusText}`,
- );
- }
- } catch (err) {
- console.log("error", err);
- throw new Error(
- `Failed to send slack notification ${err instanceof Error ? err.message : "Unknown error"}`,
- );
- }
-};
-
-export const sendGotifyNotification = async (
- connection: typeof gotify.$inferInsert,
- title: string,
- message: string,
-) => {
- const response = await fetch(`${connection.serverUrl}/message`, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-Gotify-Key": connection.appToken,
- },
- body: JSON.stringify({
- title: title,
- message: message,
- priority: connection.priority,
- extras: {
- "client::display": {
- contentType: "text/plain",
- },
- },
- }),
- });
-
- if (!response.ok) {
- throw new Error(
- `Failed to send Gotify notification: ${response.statusText}`,
- );
- }
-};
-
-export const sendNtfyNotification = async (
- connection: typeof ntfy.$inferInsert,
- title: string,
- tags: string,
- actions: string,
- message: string,
-) => {
- const response = await fetch(`${connection.serverUrl}/${connection.topic}`, {
- method: "POST",
- headers: {
- ...(connection.accessToken && {
- Authorization: `Bearer ${connection.accessToken}`,
- }),
- "X-Priority": connection.priority?.toString() || "3",
- "X-Title": title,
- "X-Tags": tags,
- "X-Actions": actions,
- },
- body: message,
- });
-
- if (!response.ok) {
- throw new Error(`Failed to send ntfy notification: ${response.statusText}`);
- }
-};
-
-export const sendCustomNotification = async (
- connection: typeof custom.$inferInsert,
- payload: Record,
-) => {
- try {
- // Merge default headers with custom headers (now already an object from jsonb)
- const headers: Record = {
- "Content-Type": "application/json",
- ...(connection.headers || {}),
- };
-
- // Default body with payload
- const body = JSON.stringify(payload);
-
- const response = await fetch(connection.endpoint, {
- method: "POST",
- headers,
- body,
- });
-
- if (!response.ok) {
- throw new Error(
- `Failed to send custom notification: ${response.statusText}`,
- );
- }
-
- return response;
- } catch (error) {
- console.error("Error sending custom notification:", error);
- throw error;
- }
-};
-
-export const sendLarkNotification = async (
- connection: typeof lark.$inferInsert,
- message: any,
-) => {
- try {
- await fetch(connection.webhookUrl, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(message),
- });
- } catch (err) {
- console.log(err);
- }
-};
-
-export const sendPushoverNotification = async (
- connection: typeof pushover.$inferInsert,
- title: string,
- message: string,
-) => {
- const formData = new URLSearchParams();
- formData.append("token", connection.apiToken);
- formData.append("user", connection.userKey);
- formData.append("title", title);
- formData.append("message", message);
- formData.append("priority", connection.priority?.toString() || "0");
-
- // For emergency priority (2), retry and expire are required
- if (connection.priority === 2) {
- formData.append("retry", connection.retry?.toString() || "30");
- formData.append("expire", connection.expire?.toString() || "3600");
- }
-
- const response = await fetch("https://api.pushover.net/1/messages.json", {
- method: "POST",
- body: formData,
- });
-
- if (!response.ok) {
- throw new Error(
- `Failed to send Pushover notification: ${response.statusText}`,
- );
- }
-};
+import nodemailer from "nodemailer";
+import { Resend } from "resend";
+
+export const sendEmailNotification = async (
+ connection: typeof email.$inferInsert,
+ subject: string,
+ htmlContent: string,
+) => {
+ try {
+ const {
+ smtpServer,
+ smtpPort,
+ username,
+ password,
+ fromAddress,
+ toAddresses,
+ } = connection;
+ const transporter = nodemailer.createTransport({
+ host: smtpServer,
+ port: smtpPort,
+ auth: { user: username, pass: password },
+ });
+
+ await transporter.sendMail({
+ from: fromAddress,
+ to: toAddresses.join(", "),
+ subject,
+ html: htmlContent,
+ textEncoding: "base64",
+ });
+ } catch (err) {
+ console.log(err);
+ throw new Error(
+ `Failed to send email notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
+ }
+};
+
+export const sendResendNotification = async (
+ connection: typeof resend.$inferInsert,
+ subject: string,
+ htmlContent: string,
+) => {
+ try {
+ const client = new Resend(connection.apiKey);
+
+ const result = await client.emails.send({
+ from: connection.fromAddress,
+ to: connection.toAddresses,
+ subject,
+ html: htmlContent,
+ });
+
+ if (result.error) {
+ throw new Error(result.error.message);
+ }
+ } catch (err) {
+ console.log(err);
+ throw new Error(
+ `Failed to send Resend notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
+ }
+};
+
+export const sendDiscordNotification = async (
+ connection: typeof discord.$inferInsert,
+ embed: any,
+) => {
+ try {
+ const response = await fetch(connection.webhookUrl, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ embeds: [embed] }),
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send discord notification ${response.statusText}`,
+ );
+ }
+ } catch (err) {
+ console.log("error", err);
+ throw new Error(
+ `Failed to send discord notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
+ }
+};
+
+export const sendTelegramNotification = async (
+ connection: typeof telegram.$inferInsert,
+ messageText: string,
+ inlineButton?: {
+ text: string;
+ url: string;
+ }[][],
+) => {
+ try {
+ const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
+ await fetch(url, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ chat_id: connection.chatId,
+ message_thread_id: connection.messageThreadId,
+ text: messageText,
+ parse_mode: "HTML",
+ disable_web_page_preview: true,
+ reply_markup: {
+ inline_keyboard: inlineButton,
+ },
+ }),
+ });
+ } catch (err) {
+ console.log(err);
+ }
+};
+
+export const sendSlackNotification = async (
+ connection: typeof slack.$inferInsert,
+ message: any,
+) => {
+ try {
+ const response = await fetch(connection.webhookUrl, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(message),
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send slack notification ${response.statusText}`,
+ );
+ }
+ } catch (err) {
+ console.log("error", err);
+ throw new Error(
+ `Failed to send slack notification ${err instanceof Error ? err.message : "Unknown error"}`,
+ );
+ }
+};
+
+export const sendGotifyNotification = async (
+ connection: typeof gotify.$inferInsert,
+ title: string,
+ message: string,
+) => {
+ const response = await fetch(`${connection.serverUrl}/message`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-Gotify-Key": connection.appToken,
+ },
+ body: JSON.stringify({
+ title: title,
+ message: message,
+ priority: connection.priority,
+ extras: {
+ "client::display": {
+ contentType: "text/plain",
+ },
+ },
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send Gotify notification: ${response.statusText}`,
+ );
+ }
+};
+
+export const sendNtfyNotification = async (
+ connection: typeof ntfy.$inferInsert,
+ title: string,
+ tags: string,
+ actions: string,
+ message: string,
+) => {
+ const response = await fetch(`${connection.serverUrl}/${connection.topic}`, {
+ method: "POST",
+ headers: {
+ ...(connection.accessToken && {
+ Authorization: `Bearer ${connection.accessToken}`,
+ }),
+ "X-Priority": connection.priority?.toString() || "3",
+ "X-Title": title,
+ "X-Tags": tags,
+ "X-Actions": actions,
+ },
+ body: message,
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to send ntfy notification: ${response.statusText}`);
+ }
+};
+
+export const sendCustomNotification = async (
+ connection: typeof custom.$inferInsert,
+ payload: Record,
+) => {
+ try {
+ // Merge default headers with custom headers (now already an object from jsonb)
+ const headers: Record = {
+ "Content-Type": "application/json",
+ ...(connection.headers || {}),
+ };
+
+ // Default body with payload
+ const body = JSON.stringify(payload);
+
+ const response = await fetch(connection.endpoint, {
+ method: "POST",
+ headers,
+ body,
+ });
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send custom notification: ${response.statusText}`,
+ );
+ }
+
+ return response;
+ } catch (error) {
+ console.error("Error sending custom notification:", error);
+ throw error;
+ }
+};
+
+export const sendLarkNotification = async (
+ connection: typeof lark.$inferInsert,
+ message: any,
+) => {
+ try {
+ await fetch(connection.webhookUrl, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(message),
+ });
+ } catch (err) {
+ console.log(err);
+ }
+};
+
+export const sendPushoverNotification = async (
+ connection: typeof pushover.$inferInsert,
+ title: string,
+ message: string,
+) => {
+ const formData = new URLSearchParams();
+ formData.append("token", connection.apiToken);
+ formData.append("user", connection.userKey);
+ formData.append("title", title);
+ formData.append("message", message);
+ formData.append("priority", connection.priority?.toString() || "0");
+
+ // For emergency priority (2), retry and expire are required
+ if (connection.priority === 2) {
+ formData.append("retry", connection.retry?.toString() || "30");
+ formData.append("expire", connection.expire?.toString() || "3600");
+ }
+
+ const response = await fetch("https://api.pushover.net/1/messages.json", {
+ method: "POST",
+ body: formData,
+ });
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send Pushover notification: ${response.statusText}`,
+ );
+ }
+};
diff --git a/packages/server/src/utils/notifications/volume-backup.ts b/packages/server/src/utils/notifications/volume-backup.ts
index 44e2b5fb3..611ccaf85 100644
--- a/packages/server/src/utils/notifications/volume-backup.ts
+++ b/packages/server/src/utils/notifications/volume-backup.ts
@@ -1,285 +1,300 @@
-import { db } from "@dokploy/server/db";
-import { notifications } from "@dokploy/server/db/schema";
-import { VolumeBackupEmail } from "@dokploy/server/emails/emails/volume-backup";
-import { renderAsync } from "@react-email/components";
-import { format } from "date-fns";
-import { and, eq } from "drizzle-orm";
-import {
- sendDiscordNotification,
+import { db } from "@dokploy/server/db";
+import { notifications } from "@dokploy/server/db/schema";
+import { VolumeBackupEmail } from "@dokploy/server/emails/emails/volume-backup";
+import { renderAsync } from "@react-email/components";
+import { format } from "date-fns";
+import { and, eq } from "drizzle-orm";
+import {
+ sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendNtfyNotification,
sendPushoverNotification,
+ sendResendNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
-
-export const sendVolumeBackupNotifications = async ({
- projectName,
- applicationName,
- volumeName,
- serviceType,
- type,
- errorMessage,
- organizationId,
- backupSize,
-}: {
- projectName: string;
- applicationName: string;
- volumeName: string;
- serviceType:
- | "application"
- | "postgres"
- | "mysql"
- | "mongodb"
- | "mariadb"
- | "redis"
- | "compose";
- type: "error" | "success";
- organizationId: string;
- errorMessage?: string;
- backupSize?: string;
-}) => {
- const date = new Date();
- const unixDate = ~~(Number(date) / 1000);
- const notificationList = await db.query.notifications.findMany({
- where: and(
- eq(notifications.volumeBackup, true),
- eq(notifications.organizationId, organizationId),
- ),
- with: {
- email: true,
- discord: true,
- telegram: true,
- slack: true,
- gotify: true,
- ntfy: true,
- pushover: true,
- },
- });
-
- for (const notification of notificationList) {
- const { email, discord, telegram, slack, gotify, ntfy, pushover } =
- notification;
-
- if (email) {
- const subject = `Volume Backup ${type === "success" ? "Successful" : "Failed"} - ${applicationName}`;
- const htmlContent = await renderAsync(
- VolumeBackupEmail({
- projectName,
- applicationName,
- volumeName,
- serviceType,
- type,
- errorMessage,
- backupSize,
- date: date.toISOString(),
- }),
- );
- await sendEmailNotification(email, subject, htmlContent);
- }
-
- if (discord) {
- const decorate = (decoration: string, text: string) =>
- `${discord.decoration ? decoration : ""} ${text}`.trim();
-
- await sendDiscordNotification(discord, {
- title:
- type === "success"
- ? decorate(">", "`โ
` Volume Backup Successful")
- : decorate(">", "`โ` Volume Backup Failed"),
- color: type === "success" ? 0x57f287 : 0xed4245,
- fields: [
- {
- name: decorate("`๐ ๏ธ`", "Project"),
- value: projectName,
- inline: true,
- },
- {
- name: decorate("`โ๏ธ`", "Application"),
- value: applicationName,
- inline: true,
- },
- {
- name: decorate("`๐พ`", "Volume Name"),
- value: volumeName,
- inline: true,
- },
- {
- name: decorate("`๐ง`", "Service Type"),
- value: serviceType,
- inline: true,
- },
- ...(backupSize
- ? [
- {
- name: decorate("`๐`", "Backup Size"),
- value: backupSize,
- inline: true,
- },
- ]
- : []),
- {
- name: decorate("`๐
`", "Date"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Time"),
- value: ``,
- inline: true,
- },
- {
- name: decorate("`โ`", "Type"),
- value: type
- .replace("error", "Failed")
- .replace("success", "Successful"),
- inline: true,
- },
- ...(type === "error" && errorMessage
- ? [
- {
- name: decorate("`โ ๏ธ`", "Error Message"),
- value: `\`\`\`${errorMessage}\`\`\``,
- },
- ]
- : []),
- ],
- timestamp: date.toISOString(),
- footer: {
- text: "Dokploy Volume Backup Notification",
- },
- });
- }
-
- if (gotify) {
- const decorate = (decoration: string, text: string) =>
- `${gotify.decoration ? decoration : ""} ${text}\n`;
-
- await sendGotifyNotification(
- gotify,
- decorate(
- type === "success" ? "โ
" : "โ",
- `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
- ),
- `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
- `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
- `${decorate("๐พ", `Volume Name: ${volumeName}`)}` +
- `${decorate("๐ง", `Service Type: ${serviceType}`)}` +
- `${backupSize ? decorate("๐", `Backup Size: ${backupSize}`) : ""}` +
- `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
- `${type === "error" && errorMessage ? decorate("โ", `Error:\n${errorMessage}`) : ""}`,
- );
- }
-
- if (ntfy) {
- await sendNtfyNotification(
- ntfy,
- `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
- `${type === "success" ? "white_check_mark" : "x"}`,
- "",
- `๐ ๏ธProject: ${projectName}\n` +
- `โ๏ธApplication: ${applicationName}\n` +
- `๐พVolume Name: ${volumeName}\n` +
- `๐งService Type: ${serviceType}\n` +
- `${backupSize ? `๐Backup Size: ${backupSize}\n` : ""}` +
- `๐Date: ${date.toLocaleString()}\n` +
- `${type === "error" && errorMessage ? `โError:\n${errorMessage}` : ""}`,
- );
- }
-
- if (telegram) {
- const isError = type === "error" && errorMessage;
-
- const statusEmoji = type === "success" ? "โ
" : "โ";
- const typeStatus = type === "success" ? "Successful" : "Failed";
- const errorMsg = isError
- ? `\n\nError:\n${errorMessage}`
- : "";
- const sizeInfo = backupSize ? `\nBackup Size: ${backupSize}` : "";
-
- const messageText = `${statusEmoji} Volume Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nVolume Name: ${volumeName}\nService Type: ${serviceType}${sizeInfo}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
-
- await sendTelegramNotification(telegram, messageText);
- }
-
- if (slack) {
- const { channel } = slack;
- await sendSlackNotification(slack, {
- channel: channel,
- attachments: [
- {
- color: type === "success" ? "#00FF00" : "#FF0000",
- pretext:
- type === "success"
- ? ":white_check_mark: *Volume Backup Successful*"
- : ":x: *Volume Backup Failed*",
- fields: [
- ...(type === "error" && errorMessage
- ? [
- {
- title: "Error Message",
- value: errorMessage,
- short: false,
- },
- ]
- : []),
- {
- title: "Project",
- value: projectName,
- short: true,
- },
- {
- title: "Application",
- value: applicationName,
- short: true,
- },
- {
- title: "Volume Name",
- value: volumeName,
- short: true,
- },
- {
- title: "Service Type",
- value: serviceType,
- short: true,
- },
- ...(backupSize
- ? [
- {
- title: "Backup Size",
- value: backupSize,
- short: true,
- },
- ]
- : []),
- {
- title: "Time",
- value: date.toLocaleString(),
- short: true,
- },
- {
- title: "Type",
- value: type,
- short: true,
- },
- {
- title: "Status",
- value: type === "success" ? "Successful" : "Failed",
- short: true,
- },
- ],
- },
- ],
- });
- }
-
- if (pushover) {
- await sendPushoverNotification(
- pushover,
- `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
- `Project: ${projectName}\nApplication: ${applicationName}\nVolume: ${volumeName}\nService Type: ${serviceType}${backupSize ? `\nBackup Size: ${backupSize}` : ""}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
- );
- }
- }
-};
+
+export const sendVolumeBackupNotifications = async ({
+ projectName,
+ applicationName,
+ volumeName,
+ serviceType,
+ type,
+ errorMessage,
+ organizationId,
+ backupSize,
+}: {
+ projectName: string;
+ applicationName: string;
+ volumeName: string;
+ serviceType:
+ | "application"
+ | "postgres"
+ | "mysql"
+ | "mongodb"
+ | "mariadb"
+ | "redis"
+ | "compose";
+ type: "error" | "success";
+ organizationId: string;
+ errorMessage?: string;
+ backupSize?: string;
+}) => {
+ const date = new Date();
+ const unixDate = ~~(Number(date) / 1000);
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.volumeBackup, true),
+ eq(notifications.organizationId, organizationId),
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ resend: true,
+ gotify: true,
+ ntfy: true,
+ pushover: true,
+ },
+ });
+
+ for (const notification of notificationList) {
+ const {
+ email,
+ resend,
+ discord,
+ telegram,
+ slack,
+ gotify,
+ ntfy,
+ pushover,
+ } = notification;
+
+ if (email || resend) {
+ const subject = `Volume Backup ${type === "success" ? "Successful" : "Failed"} - ${applicationName}`;
+ const htmlContent = await renderAsync(
+ VolumeBackupEmail({
+ projectName,
+ applicationName,
+ volumeName,
+ serviceType,
+ type,
+ errorMessage,
+ backupSize,
+ date: date.toISOString(),
+ }),
+ );
+ if (email) {
+ await sendEmailNotification(email, subject, htmlContent);
+ }
+ if (resend) {
+ await sendResendNotification(resend, subject, htmlContent);
+ }
+ }
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title:
+ type === "success"
+ ? decorate(">", "`โ
` Volume Backup Successful")
+ : decorate(">", "`โ` Volume Backup Failed"),
+ color: type === "success" ? 0x57f287 : 0xed4245,
+ fields: [
+ {
+ name: decorate("`๐ ๏ธ`", "Project"),
+ value: projectName,
+ inline: true,
+ },
+ {
+ name: decorate("`โ๏ธ`", "Application"),
+ value: applicationName,
+ inline: true,
+ },
+ {
+ name: decorate("`๐พ`", "Volume Name"),
+ value: volumeName,
+ inline: true,
+ },
+ {
+ name: decorate("`๐ง`", "Service Type"),
+ value: serviceType,
+ inline: true,
+ },
+ ...(backupSize
+ ? [
+ {
+ name: decorate("`๐`", "Backup Size"),
+ value: backupSize,
+ inline: true,
+ },
+ ]
+ : []),
+ {
+ name: decorate("`๐
`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`โ`", "Type"),
+ value: type
+ .replace("error", "Failed")
+ .replace("success", "Successful"),
+ inline: true,
+ },
+ ...(type === "error" && errorMessage
+ ? [
+ {
+ name: decorate("`โ ๏ธ`", "Error Message"),
+ value: `\`\`\`${errorMessage}\`\`\``,
+ },
+ ]
+ : []),
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Volume Backup Notification",
+ },
+ });
+ }
+
+ if (gotify) {
+ const decorate = (decoration: string, text: string) =>
+ `${gotify.decoration ? decoration : ""} ${text}\n`;
+
+ await sendGotifyNotification(
+ gotify,
+ decorate(
+ type === "success" ? "โ
" : "โ",
+ `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
+ ),
+ `${decorate("๐ ๏ธ", `Project: ${projectName}`)}` +
+ `${decorate("โ๏ธ", `Application: ${applicationName}`)}` +
+ `${decorate("๐พ", `Volume Name: ${volumeName}`)}` +
+ `${decorate("๐ง", `Service Type: ${serviceType}`)}` +
+ `${backupSize ? decorate("๐", `Backup Size: ${backupSize}`) : ""}` +
+ `${decorate("๐", `Date: ${date.toLocaleString()}`)}` +
+ `${type === "error" && errorMessage ? decorate("โ", `Error:\n${errorMessage}`) : ""}`,
+ );
+ }
+
+ if (ntfy) {
+ await sendNtfyNotification(
+ ntfy,
+ `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `${type === "success" ? "white_check_mark" : "x"}`,
+ "",
+ `๐ ๏ธProject: ${projectName}\n` +
+ `โ๏ธApplication: ${applicationName}\n` +
+ `๐พVolume Name: ${volumeName}\n` +
+ `๐งService Type: ${serviceType}\n` +
+ `${backupSize ? `๐Backup Size: ${backupSize}\n` : ""}` +
+ `๐Date: ${date.toLocaleString()}\n` +
+ `${type === "error" && errorMessage ? `โError:\n${errorMessage}` : ""}`,
+ );
+ }
+
+ if (telegram) {
+ const isError = type === "error" && errorMessage;
+
+ const statusEmoji = type === "success" ? "โ
" : "โ";
+ const typeStatus = type === "success" ? "Successful" : "Failed";
+ const errorMsg = isError
+ ? `\n\nError:\n${errorMessage}`
+ : "";
+ const sizeInfo = backupSize ? `\nBackup Size: ${backupSize}` : "";
+
+ const messageText = `${statusEmoji} Volume Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nVolume Name: ${volumeName}\nService Type: ${serviceType}${sizeInfo}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`;
+
+ await sendTelegramNotification(telegram, messageText);
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: type === "success" ? "#00FF00" : "#FF0000",
+ pretext:
+ type === "success"
+ ? ":white_check_mark: *Volume Backup Successful*"
+ : ":x: *Volume Backup Failed*",
+ fields: [
+ ...(type === "error" && errorMessage
+ ? [
+ {
+ title: "Error Message",
+ value: errorMessage,
+ short: false,
+ },
+ ]
+ : []),
+ {
+ title: "Project",
+ value: projectName,
+ short: true,
+ },
+ {
+ title: "Application",
+ value: applicationName,
+ short: true,
+ },
+ {
+ title: "Volume Name",
+ value: volumeName,
+ short: true,
+ },
+ {
+ title: "Service Type",
+ value: serviceType,
+ short: true,
+ },
+ ...(backupSize
+ ? [
+ {
+ title: "Backup Size",
+ value: backupSize,
+ short: true,
+ },
+ ]
+ : []),
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ {
+ title: "Type",
+ value: type,
+ short: true,
+ },
+ {
+ title: "Status",
+ value: type === "success" ? "Successful" : "Failed",
+ short: true,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ if (pushover) {
+ await sendPushoverNotification(
+ pushover,
+ `Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
+ `Project: ${projectName}\nApplication: ${applicationName}\nVolume: ${volumeName}\nService Type: ${serviceType}${backupSize ? `\nBackup Size: ${backupSize}` : ""}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
+ );
+ }
+ }
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d5fc7f074..ac0450e5f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -711,6 +711,9 @@ importers:
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
+ resend:
+ specifier: ^6.0.2
+ version: 6.8.0(@react-email/render@0.0.16(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
semver:
specifier: 7.7.3
version: 7.7.3
@@ -3637,6 +3640,9 @@ packages:
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
engines: {node: '>=14.16'}
+ '@stablelib/base64@1.0.1':
+ resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
+
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
@@ -5013,6 +5019,9 @@ packages:
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+ fast-sha256@1.3.0:
+ resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
+
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -6686,6 +6695,15 @@ packages:
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+ resend@6.8.0:
+ resolution: {integrity: sha512-fDOXGqafQfQXl8nXe93wr93pus8tW7YPpowenE3SmG7dJJf0hH3xUWm3xqacnPvhqjCQTJH9xETg07rmUeSuqQ==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@react-email/render': '*'
+ peerDependenciesMeta:
+ '@react-email/render':
+ optional: true
+
resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
@@ -6898,6 +6916,9 @@ packages:
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
+ standardwebhooks@1.0.0:
+ resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
+
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
@@ -6993,6 +7014,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ svix@1.84.1:
+ resolution: {integrity: sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==}
+
swagger-client@3.35.3:
resolution: {integrity: sha512-4bO+dhBbasP485Ak67o46cWNVUnV0/92ypb2997bhvxTO2M+IuQZM1ilkN/7nSaiGuxDKJhkuL54I35PVI3AAw==}
@@ -7273,6 +7297,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ uuid@10.0.0:
+ resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
+ hasBin: true
+
uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
@@ -10346,6 +10374,8 @@ snapshots:
'@sindresorhus/is@5.6.0': {}
+ '@stablelib/base64@1.0.1': {}
+
'@standard-schema/spec@1.0.0': {}
'@stepperize/react@4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
@@ -12007,6 +12037,8 @@ snapshots:
fast-safe-stringify@2.1.1: {}
+ fast-sha256@1.3.0: {}
+
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -13914,6 +13946,12 @@ snapshots:
reselect@5.1.1: {}
+ resend@6.8.0(@react-email/render@0.0.16(react-dom@18.2.0(react@18.2.0))(react@18.2.0)):
+ dependencies:
+ svix: 1.84.1
+ optionalDependencies:
+ '@react-email/render': 0.0.16(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+
resolve-alpn@1.2.1: {}
resolve-pkg-maps@1.0.0: {}
@@ -14148,6 +14186,11 @@ snapshots:
standard-as-callback@2.1.0: {}
+ standardwebhooks@1.0.0:
+ dependencies:
+ '@stablelib/base64': 1.0.1
+ fast-sha256: 1.3.0
+
statuses@2.0.1: {}
std-env@3.9.0: {}
@@ -14241,6 +14284,11 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ svix@1.84.1:
+ dependencies:
+ standardwebhooks: 1.0.0
+ uuid: 10.0.0
+
swagger-client@3.35.3:
dependencies:
'@babel/runtime-corejs3': 7.27.3
@@ -14585,6 +14633,8 @@ snapshots:
util-deprecate@1.0.2: {}
+ uuid@10.0.0: {}
+
uuid@9.0.1: {}
vfile-message@4.0.2:
diff --git a/schema.dbml b/schema.dbml
index d0845f3ed..3ee3e763a 100644
--- a/schema.dbml
+++ b/schema.dbml
@@ -69,6 +69,7 @@ enum notificationType {
telegram
discord
email
+ resend
gotify
ntfy
custom
@@ -455,6 +456,13 @@ table email {
toAddress text[] [not null]
}
+table resend {
+ resendId text [pk, not null]
+ apiKey text [not null]
+ fromAddress text [not null]
+ toAddress text[] [not null]
+}
+
table environment {
environmentId text [pk, not null]
name text [not null]
@@ -691,6 +699,7 @@ table notification {
telegramId text
discordId text
emailId text
+ resendId text
gotifyId text
ntfyId text
customId text
@@ -1133,6 +1142,8 @@ ref: notification.discordId - discord.discordId
ref: notification.emailId - email.emailId
+ref: notification.resendId - resend.resendId
+
ref: notification.gotifyId - gotify.gotifyId
ref: notification.ntfyId - ntfy.ntfyId
@@ -1191,4 +1202,4 @@ ref: volume_backup.redisId - redis.redisId
ref: volume_backup.composeId - compose.composeId
-ref: volume_backup.destinationId - destination.destinationId
\ No newline at end of file
+ref: volume_backup.destinationId - destination.destinationId