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 { findAdmin } from "./admin";
import { createDeployment, updateDeploymentStatus } from "./deployment";
import {
sendBuildErrorNotifications,
sendBuildSuccessNotifications,
} from "./notification";
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
import { validUniqueServerAppName } from "./project";
export type Application = typeof applications.$inferSelect;
@ -168,7 +167,6 @@ export const deployApplication = async ({
buildLink: deployment.logPath,
});
} catch (error) {
console.log("Error on build", error);
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
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 {
type apiCreateDiscord,
type apiCreateEmail,
type apiCreateSlack,
type apiCreateTelegram,
type apiTestDiscordConnection,
type apiTestEmailConnection,
type apiTestSlackConnection,
type apiTestTelegramConnection,
type apiUpdateDiscord,
type apiUpdateEmail,
type apiUpdateSlack,
@ -23,11 +14,8 @@ import {
slack,
telegram,
} from "@/server/db/schema";
import { render } from "@react-email/components";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import nodemailer from "nodemailer";
import type SMTPTransport from "nodemailer/lib/smtp-transport";
export type Notification = typeof notifications.$inferSelect;
@ -419,697 +407,3 @@ export const updateDestinationById = async (
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 { config } from "dotenv";
import next from "next";
import { sendDokployRestartNotifications } from "./api/services/notification";
import { deploymentWorker } from "./queues/deployments-queue";
import { setupDirectories } from "./setup/config-paths";
import { initializePostgres } from "./setup/postgres-setup";
@ -15,6 +14,7 @@ import {
initializeTraefik,
} from "./setup/traefik-setup";
import { initCronJobs } from "./utils/backups";
import { sendDokployRestartNotifications } from "./utils/notifications/dokploy-restart";
import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs";
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";

View File

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

View File

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

View File

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

View File

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