refactor(notification): split functions for each notification actio

This commit is contained in:
Mauricio Siu 2024-07-19 21:57:46 -06:00
parent e0bde5cec9
commit 9954d5b209
13 changed files with 758 additions and 716 deletions

View File

@ -17,10 +17,9 @@ import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { findAdmin } from "./admin"; import { findAdmin } from "./admin";
import { createDeployment, updateDeploymentStatus } from "./deployment"; import { createDeployment, updateDeploymentStatus } from "./deployment";
import {
sendBuildErrorNotifications, import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
sendBuildSuccessNotifications, import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
} from "./notification";
import { validUniqueServerAppName } from "./project"; import { validUniqueServerAppName } from "./project";
export type Application = typeof applications.$inferSelect; export type Application = typeof applications.$inferSelect;
@ -168,7 +167,6 @@ export const deployApplication = async ({
buildLink: deployment.logPath, buildLink: deployment.logPath,
}); });
} catch (error) { } catch (error) {
console.log("Error on build", error);
await updateDeploymentStatus(deployment.deploymentId, "error"); await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error"); await updateApplicationStatus(applicationId, "error");
await sendBuildErrorNotifications({ await sendBuildErrorNotifications({

View File

@ -1,18 +1,9 @@
import { BuildFailedEmail } from "@/emails/emails/build-failed";
import BuildSuccessEmail from "@/emails/emails/build-success";
import DatabaseBackupEmail from "@/emails/emails/database-backup";
import DockerCleanupEmail from "@/emails/emails/docker-cleanup";
import DokployRestartEmail from "@/emails/emails/dokploy-restart";
import { db } from "@/server/db"; import { db } from "@/server/db";
import { import {
type apiCreateDiscord, type apiCreateDiscord,
type apiCreateEmail, type apiCreateEmail,
type apiCreateSlack, type apiCreateSlack,
type apiCreateTelegram, type apiCreateTelegram,
type apiTestDiscordConnection,
type apiTestEmailConnection,
type apiTestSlackConnection,
type apiTestTelegramConnection,
type apiUpdateDiscord, type apiUpdateDiscord,
type apiUpdateEmail, type apiUpdateEmail,
type apiUpdateSlack, type apiUpdateSlack,
@ -23,11 +14,8 @@ import {
slack, slack,
telegram, telegram,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { render } from "@react-email/components";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import nodemailer from "nodemailer";
import type SMTPTransport from "nodemailer/lib/smtp-transport";
export type Notification = typeof notifications.$inferSelect; export type Notification = typeof notifications.$inferSelect;
@ -419,697 +407,3 @@ export const updateDestinationById = async (
return result[0]; return result[0];
}; };
interface BuildFailedEmailProps {
projectName: string;
applicationName: string;
applicationType: string;
errorMessage: string;
buildLink: string;
}
export const sendBuildErrorNotifications = async ({
projectName,
applicationName,
applicationType,
errorMessage,
buildLink,
}: BuildFailedEmailProps) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.appBuildError, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Build failed for dokploy",
render(
BuildFailedEmail({
projectName,
applicationName,
applicationType,
errorMessage,
buildLink,
date: date.toLocaleString(),
}),
),
);
}
if (discord) {
const { webhookUrl } = discord;
await sendDiscordNotification(discord, {
title: "⚠️ Build Failed",
color: 0xff0000,
fields: [
{
name: "Project",
value: projectName,
inline: true,
},
{
name: "Application",
value: applicationName,
inline: true,
},
{
name: "Type",
value: applicationType,
inline: true,
},
{
name: "Error",
value: errorMessage,
},
{
name: "Build Link",
value: buildLink,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Build Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Build Failed</b>
<b>Project:</b> ${projectName}
<b>Application:</b> ${applicationName}
<b>Type:</b> ${applicationType}
<b>Time:</b> ${date.toLocaleString()}
<b>Error:</b>
<pre>${errorMessage}</pre>
<b>Build Details:</b> ${buildLink}
`,
);
}
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: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};
interface BuildSuccessEmailProps {
projectName: string;
applicationName: string;
applicationType: string;
buildLink: string;
}
export const sendBuildSuccessNotifications = async ({
projectName,
applicationName,
applicationType,
buildLink,
}: BuildSuccessEmailProps) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.appDeploy, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Build success for dokploy",
render(
BuildSuccessEmail({
projectName,
applicationName,
applicationType,
buildLink,
date: date.toLocaleString(),
}),
),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "✅ Build Success",
color: 0x00ff00,
fields: [
{
name: "Project",
value: projectName,
inline: true,
},
{
name: "Application",
value: applicationName,
inline: true,
},
{
name: "Type",
value: applicationType,
inline: true,
},
{
name: "Build Link",
value: buildLink,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Build Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Build Success</b>
<b>Project:</b> ${projectName}
<b>Application:</b> ${applicationName}
<b>Type:</b> ${applicationType}
<b>Time:</b> ${date.toLocaleString()}
<b>Build Details:</b> ${buildLink}
`,
);
}
if (slack) {
const { webhookUrl, 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: "Type",
value: applicationType,
short: true,
},
{
title: "Time",
value: date.toLocaleString(),
short: true,
},
{
title: "Build Link",
value: buildLink,
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};
export const sendDatabaseBackupNotifications = async ({
projectName,
applicationName,
databaseType,
type,
errorMessage,
}: {
projectName: string;
applicationName: string;
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
type: "error" | "success";
errorMessage?: string;
}) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.databaseBackup, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Database backup for dokploy",
render(
DatabaseBackupEmail({
projectName,
applicationName,
databaseType,
type,
errorMessage,
date: date.toLocaleString(),
}),
),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title:
type === "success"
? "✅ Database Backup Successful"
: "❌ Database Backup Failed",
color: type === "success" ? 0x00ff00 : 0xff0000,
fields: [
{
name: "Project",
value: projectName,
inline: true,
},
{
name: "Application",
value: applicationName,
inline: true,
},
{
name: "Type",
value: databaseType,
inline: true,
},
{
name: "Time",
value: date.toLocaleString(),
inline: true,
},
{
name: "Type",
value: type,
},
...(type === "error" && errorMessage
? [
{
name: "Error Message",
value: errorMessage,
},
]
: []),
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Database Backup Notification",
},
});
}
if (telegram) {
const statusEmoji = type === "success" ? "✅" : "❌";
const messageText = `
<b>${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"}</b>
<b>Project:</b> ${projectName}
<b>Application:</b> ${applicationName}
<b>Type:</b> ${databaseType}
<b>Time:</b> ${date.toLocaleString()}
<b>Status:</b> ${type === "success" ? "Successful" : "Failed"}
${type === "error" && errorMessage ? `<b>Error:</b> ${errorMessage}` : ""}
`;
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: "Time",
value: date.toLocaleString(),
short: true,
},
{
title: "Type",
value: type,
},
{
title: "Status",
value: type === "success" ? "Successful" : "Failed",
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};
export const sendDockerCleanupNotifications = async (
message = "Docker cleanup for dokploy",
) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.dockerCleanup, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Docker cleanup for dokploy",
render(DockerCleanupEmail({ message, date: date.toLocaleString() })),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "✅ Docker Cleanup",
color: 0x00ff00,
fields: [
{
name: "Message",
value: message,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Docker Cleanup Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Docker Cleanup</b>
<b>Message:</b> ${message}
<b>Time:</b> ${date.toLocaleString()}
`,
);
}
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,
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};
export const sendEmailNotification = async (
connection: typeof email.$inferInsert,
subject: string,
htmlContent: string,
) => {
const { smtpServer, smtpPort, username, password, fromAddress, toAddresses } =
connection;
const transporter = nodemailer.createTransport({
host: smtpServer,
port: smtpPort,
secure: smtpPort === 465,
auth: { user: username, pass: password },
});
await transporter.sendMail({
from: fromAddress,
to: toAddresses.join(", "),
subject,
html: htmlContent,
});
};
export const sendDiscordNotification = async (
connection: typeof discord.$inferInsert,
embed: any,
) => {
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");
};
export const sendTelegramNotification = async (
connection: typeof telegram.$inferInsert,
messageText: string,
) => {
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: connection.chatId,
text: messageText,
parse_mode: "HTML",
disable_web_page_preview: true,
}),
});
if (!response.ok) throw new Error("Failed to send Telegram notification");
};
export const sendSlackNotification = async (
connection: typeof slack.$inferInsert,
message: any,
) => {
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");
};
export const sendDokployRestartNotifications = async () => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.dokployRestart, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Dokploy Server Restarted",
render(DokployRestartEmail({ date: date.toLocaleString() })),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "✅ Dokploy Server Restarted",
color: 0x00ff00,
fields: [
{
name: "Time",
value: date.toLocaleString(),
inline: true,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Restart Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Dokploy Serverd Restarted</b>
<b>Time:</b> ${date.toLocaleString()}
`,
);
}
if (slack) {
const { channel } = slack;
await sendSlackNotification(slack, {
channel: channel,
attachments: [
{
color: "#FF0000",
pretext: ":white_check_mark: *Dokploy Server Restarted*",
fields: [
{
title: "Time",
value: date.toLocaleString(),
short: true,
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};

View File

@ -2,7 +2,6 @@ import http from "node:http";
import { migration } from "@/server/db/migration"; import { migration } from "@/server/db/migration";
import { config } from "dotenv"; import { config } from "dotenv";
import next from "next"; import next from "next";
import { sendDokployRestartNotifications } from "./api/services/notification";
import { deploymentWorker } from "./queues/deployments-queue"; import { deploymentWorker } from "./queues/deployments-queue";
import { setupDirectories } from "./setup/config-paths"; import { setupDirectories } from "./setup/config-paths";
import { initializePostgres } from "./setup/postgres-setup"; import { initializePostgres } from "./setup/postgres-setup";
@ -15,6 +14,7 @@ import {
initializeTraefik, initializeTraefik,
} from "./setup/traefik-setup"; } from "./setup/traefik-setup";
import { initCronJobs } from "./utils/backups"; import { initCronJobs } from "./utils/backups";
import { sendDokployRestartNotifications } from "./utils/notifications/dokploy-restart";
import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs"; import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs";
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal"; import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats"; import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";

View File

@ -2,9 +2,9 @@ import { unlink } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup"; import type { BackupSchedule } from "@/server/api/services/backup";
import type { Mariadb } from "@/server/api/services/mariadb"; import type { Mariadb } from "@/server/api/services/mariadb";
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
import { findProjectById } from "@/server/api/services/project"; import { findProjectById } from "@/server/api/services/project";
import { getServiceContainer } from "../docker/utils"; import { getServiceContainer } from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync } from "../process/execAsync"; import { execAsync } from "../process/execAsync";
import { uploadToS3 } from "./utils"; import { uploadToS3 } from "./utils";

View File

@ -2,9 +2,9 @@ import { unlink } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup"; import type { BackupSchedule } from "@/server/api/services/backup";
import type { Mongo } from "@/server/api/services/mongo"; import type { Mongo } from "@/server/api/services/mongo";
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
import { findProjectById } from "@/server/api/services/project"; import { findProjectById } from "@/server/api/services/project";
import { getServiceContainer } from "../docker/utils"; import { getServiceContainer } from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync } from "../process/execAsync"; import { execAsync } from "../process/execAsync";
import { uploadToS3 } from "./utils"; import { uploadToS3 } from "./utils";

View File

@ -2,9 +2,9 @@ import { unlink } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup"; import type { BackupSchedule } from "@/server/api/services/backup";
import type { MySql } from "@/server/api/services/mysql"; import type { MySql } from "@/server/api/services/mysql";
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
import { findProjectById } from "@/server/api/services/project"; import { findProjectById } from "@/server/api/services/project";
import { getServiceContainer } from "../docker/utils"; import { getServiceContainer } from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync } from "../process/execAsync"; import { execAsync } from "../process/execAsync";
import { uploadToS3 } from "./utils"; import { uploadToS3 } from "./utils";

View File

@ -1,10 +1,10 @@
import { unlink } from "node:fs/promises"; import { unlink } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import type { BackupSchedule } from "@/server/api/services/backup"; import type { BackupSchedule } from "@/server/api/services/backup";
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
import type { Postgres } from "@/server/api/services/postgres"; import type { Postgres } from "@/server/api/services/postgres";
import { findProjectById } from "@/server/api/services/project"; import { findProjectById } from "@/server/api/services/project";
import { getServiceContainer } from "../docker/utils"; import { getServiceContainer } from "../docker/utils";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync } from "../process/execAsync"; import { execAsync } from "../process/execAsync";
import { uploadToS3 } from "./utils"; import { uploadToS3 } from "./utils";

View File

@ -0,0 +1,160 @@
import BuildFailedEmail from "@/emails/emails/build-failed";
import { db } from "@/server/db";
import { notifications } from "@/server/db/schema";
import { render } from "@react-email/components";
import { eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
interface Props {
projectName: string;
applicationName: string;
applicationType: string;
errorMessage: string;
buildLink: string;
}
export const sendBuildErrorNotifications = async ({
projectName,
applicationName,
applicationType,
errorMessage,
buildLink,
}: Props) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.appBuildError, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Build failed for dokploy",
render(
BuildFailedEmail({
projectName,
applicationName,
applicationType,
errorMessage,
buildLink,
date: date.toLocaleString(),
}),
),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "⚠️ Build Failed",
color: 0xff0000,
fields: [
{
name: "Project",
value: projectName,
inline: true,
},
{
name: "Application",
value: applicationName,
inline: true,
},
{
name: "Type",
value: applicationType,
inline: true,
},
{
name: "Error",
value: errorMessage,
},
{
name: "Build Link",
value: buildLink,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Build Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Build Failed</b>
<b>Project:</b> ${projectName}
<b>Application:</b> ${applicationName}
<b>Type:</b> ${applicationType}
<b>Time:</b> ${date.toLocaleString()}
<b>Error:</b>
<pre>${errorMessage}</pre>
<b>Build Details:</b> ${buildLink}
`,
);
}
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: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};

View File

@ -0,0 +1,150 @@
import BuildSuccessEmail from "@/emails/emails/build-success";
import { db } from "@/server/db";
import { notifications } from "@/server/db/schema";
import { render } from "@react-email/components";
import { eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
interface Props {
projectName: string;
applicationName: string;
applicationType: string;
buildLink: string;
}
export const sendBuildSuccessNotifications = async ({
projectName,
applicationName,
applicationType,
buildLink,
}: Props) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.appDeploy, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Build success for dokploy",
render(
BuildSuccessEmail({
projectName,
applicationName,
applicationType,
buildLink,
date: date.toLocaleString(),
}),
),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "✅ Build Success",
color: 0x00ff00,
fields: [
{
name: "Project",
value: projectName,
inline: true,
},
{
name: "Application",
value: applicationName,
inline: true,
},
{
name: "Type",
value: applicationType,
inline: true,
},
{
name: "Build Link",
value: buildLink,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Build Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Build Success</b>
<b>Project:</b> ${projectName}
<b>Application:</b> ${applicationName}
<b>Type:</b> ${applicationType}
<b>Time:</b> ${date.toLocaleString()}
<b>Build Details:</b> ${buildLink}
`,
);
}
if (slack) {
const { webhookUrl, 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: "Type",
value: applicationType,
short: true,
},
{
title: "Time",
value: date.toLocaleString(),
short: true,
},
{
title: "Build Link",
value: buildLink,
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};

View File

@ -0,0 +1,183 @@
import DatabaseBackupEmail from "@/emails/emails/database-backup";
import { db } from "@/server/db";
import { notifications } from "@/server/db/schema";
import { render } from "@react-email/components";
import { eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
export const sendDatabaseBackupNotifications = async ({
projectName,
applicationName,
databaseType,
type,
errorMessage,
}: {
projectName: string;
applicationName: string;
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
type: "error" | "success";
errorMessage?: string;
}) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.databaseBackup, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Database backup for dokploy",
render(
DatabaseBackupEmail({
projectName,
applicationName,
databaseType,
type,
errorMessage,
date: date.toLocaleString(),
}),
),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title:
type === "success"
? "✅ Database Backup Successful"
: "❌ Database Backup Failed",
color: type === "success" ? 0x00ff00 : 0xff0000,
fields: [
{
name: "Project",
value: projectName,
inline: true,
},
{
name: "Application",
value: applicationName,
inline: true,
},
{
name: "Type",
value: databaseType,
inline: true,
},
{
name: "Time",
value: date.toLocaleString(),
inline: true,
},
{
name: "Type",
value: type,
},
...(type === "error" && errorMessage
? [
{
name: "Error Message",
value: errorMessage,
},
]
: []),
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Database Backup Notification",
},
});
}
if (telegram) {
const statusEmoji = type === "success" ? "✅" : "❌";
const messageText = `
<b>${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"}</b>
<b>Project:</b> ${projectName}
<b>Application:</b> ${applicationName}
<b>Type:</b> ${databaseType}
<b>Time:</b> ${date.toLocaleString()}
<b>Status:</b> ${type === "success" ? "Successful" : "Failed"}
${type === "error" && errorMessage ? `<b>Error:</b> ${errorMessage}` : ""}
`;
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: "Time",
value: date.toLocaleString(),
short: true,
},
{
title: "Type",
value: type,
},
{
title: "Status",
value: type === "success" ? "Successful" : "Failed",
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};

View File

@ -0,0 +1,97 @@
import DockerCleanupEmail from "@/emails/emails/docker-cleanup";
import { db } from "@/server/db";
import { notifications } from "@/server/db/schema";
import { render } from "@react-email/components";
import { eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
export const sendDockerCleanupNotifications = async (
message = "Docker cleanup for dokploy",
) => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.dockerCleanup, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Docker cleanup for dokploy",
render(DockerCleanupEmail({ message, date: date.toLocaleString() })),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "✅ Docker Cleanup",
color: 0x00ff00,
fields: [
{
name: "Message",
value: message,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Docker Cleanup Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Docker Cleanup</b>
<b>Message:</b> ${message}
<b>Time:</b> ${date.toLocaleString()}
`,
);
}
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,
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};

View File

@ -0,0 +1,91 @@
import DokployRestartEmail from "@/emails/emails/dokploy-restart";
import { db } from "@/server/db";
import { notifications } from "@/server/db/schema";
import { render } from "@react-email/components";
import { eq } from "drizzle-orm";
import {
sendDiscordNotification,
sendEmailNotification,
sendSlackNotification,
sendTelegramNotification,
} from "./utils";
export const sendDokployRestartNotifications = async () => {
const date = new Date();
const notificationList = await db.query.notifications.findMany({
where: eq(notifications.dokployRestart, true),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
},
});
for (const notification of notificationList) {
const { email, discord, telegram, slack } = notification;
if (email) {
await sendEmailNotification(
email,
"Dokploy Server Restarted",
render(DokployRestartEmail({ date: date.toLocaleString() })),
);
}
if (discord) {
await sendDiscordNotification(discord, {
title: "✅ Dokploy Server Restarted",
color: 0x00ff00,
fields: [
{
name: "Time",
value: date.toLocaleString(),
inline: true,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Restart Notification",
},
});
}
if (telegram) {
await sendTelegramNotification(
telegram,
`
<b> Dokploy Serverd Restarted</b>
<b>Time:</b> ${date.toLocaleString()}
`,
);
}
if (slack) {
const { channel } = slack;
await sendSlackNotification(slack, {
channel: channel,
attachments: [
{
color: "#FF0000",
pretext: ":white_check_mark: *Dokploy Server Restarted*",
fields: [
{
title: "Time",
value: date.toLocaleString(),
short: true,
},
],
actions: [
{
type: "button",
text: "View Build Details",
url: "https://doks.dev/build-details",
},
],
},
],
});
}
}
};

View File

@ -0,0 +1,69 @@
import type { discord, email, slack, telegram } from "@/server/db/schema";
import nodemailer from "nodemailer";
export const sendEmailNotification = async (
connection: typeof email.$inferInsert,
subject: string,
htmlContent: string,
) => {
const { smtpServer, smtpPort, username, password, fromAddress, toAddresses } =
connection;
const transporter = nodemailer.createTransport({
host: smtpServer,
port: smtpPort,
secure: smtpPort === 465,
auth: { user: username, pass: password },
});
await transporter.sendMail({
from: fromAddress,
to: toAddresses.join(", "),
subject,
html: htmlContent,
});
};
export const sendDiscordNotification = async (
connection: typeof discord.$inferInsert,
embed: any,
) => {
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");
};
export const sendTelegramNotification = async (
connection: typeof telegram.$inferInsert,
messageText: string,
) => {
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: connection.chatId,
text: messageText,
parse_mode: "HTML",
disable_web_page_preview: true,
}),
});
if (!response.ok) throw new Error("Failed to send Telegram notification");
};
export const sendSlackNotification = async (
connection: typeof slack.$inferInsert,
message: any,
) => {
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");
};