From e0bde5cec9fad63d1032d686c8af511271e3741a Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 19 Jul 2024 01:41:01 -0600 Subject: [PATCH] refactor(notifications): minimize send notifications in more reusable fn --- server/api/routers/notification.ts | 34 +- server/api/services/notification.ts | 605 +++++----------------------- server/server.ts | 3 +- 3 files changed, 132 insertions(+), 510 deletions(-) diff --git a/server/api/routers/notification.ts b/server/api/routers/notification.ts index e7a252fb..4ed5e22f 100644 --- a/server/api/routers/notification.ts +++ b/server/api/routers/notification.ts @@ -21,6 +21,7 @@ import { notifications, } from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; +import { desc } from "drizzle-orm"; import { createDiscordNotification, createEmailNotification, @@ -28,16 +29,15 @@ import { createTelegramNotification, findNotificationById, removeNotificationById, - sendDiscordTestNotification, - sendEmailTestNotification, - sendSlackTestNotification, - sendTelegramTestNotification, + sendDiscordNotification, + sendEmailNotification, + sendSlackNotification, + sendTelegramNotification, updateDiscordNotification, updateEmailNotification, updateSlackNotification, updateTelegramNotification, } from "../services/notification"; -import { desc } from "drizzle-orm"; export const notificationRouter = createTRPCRouter({ createSlack: adminProcedure @@ -71,7 +71,10 @@ export const notificationRouter = createTRPCRouter({ .input(apiTestSlackConnection) .mutation(async ({ input }) => { try { - await sendSlackTestNotification(input); + await sendSlackNotification(input, { + channel: input.channel, + text: "Hi, From Dokploy 👋", + }); return true; } catch (error) { throw new TRPCError({ @@ -112,7 +115,7 @@ export const notificationRouter = createTRPCRouter({ .input(apiTestTelegramConnection) .mutation(async ({ input }) => { try { - await sendTelegramTestNotification(input); + await sendTelegramNotification(input, "Hi, From Dokploy 👋"); return true; } catch (error) { throw new TRPCError({ @@ -126,6 +129,12 @@ export const notificationRouter = createTRPCRouter({ .input(apiCreateDiscord) .mutation(async ({ input }) => { try { + // go to your discord server + // go to settings + // go to integrations + // add a new integration + // select webhook + // copy the webhook url return await createDiscordNotification(input); } catch (error) { throw new TRPCError({ @@ -154,7 +163,10 @@ export const notificationRouter = createTRPCRouter({ .input(apiTestDiscordConnection) .mutation(async ({ input }) => { try { - await sendDiscordTestNotification(input); + await sendDiscordNotification(input, { + title: "Test Notification", + description: "Hi, From Dokploy 👋", + }); return true; } catch (error) { throw new TRPCError({ @@ -195,7 +207,11 @@ export const notificationRouter = createTRPCRouter({ .input(apiTestEmailConnection) .mutation(async ({ input }) => { try { - await sendEmailTestNotification(input); + await sendEmailNotification( + input, + "Test Email", + "

Hi, From Dokploy 👋

", + ); return true; } catch (error) { throw new TRPCError({ diff --git a/server/api/services/notification.ts b/server/api/services/notification.ts index 518f3a99..cbbb7274 100644 --- a/server/api/services/notification.ts +++ b/server/api/services/notification.ts @@ -420,97 +420,6 @@ export const updateDestinationById = async ( return result[0]; }; -export const sendSlackTestNotification = async ( - slackTestConnection: typeof apiTestSlackConnection._type, -) => { - const { webhookUrl, channel } = slackTestConnection; - - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ text: "Hi, From Dokploy 👋", channel }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } -}; - -export const sendTelegramTestNotification = async ( - telegramTestConnection: typeof apiTestTelegramConnection._type, -) => { - const { botToken, chatId } = telegramTestConnection; - const url = `https://api.telegram.org/bot${botToken}/sendMessage`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: "Hi, From Dokploy 👋", - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } -}; - -export const sendDiscordTestNotification = async ( - discordTestConnection: typeof apiTestDiscordConnection._type, -) => { - const { webhookUrl } = discordTestConnection; - // go to your discord server - // go to settings - // go to integrations - // add a new integration - // select webhook - // copy the webhook url - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - content: "Hi, From Dokploy 👋", - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } -}; - -export const sendEmailTestNotification = async ( - emailTestConnection: typeof apiTestEmailConnection._type, -) => { - const { smtpServer, smtpPort, username, password, toAddresses, fromAddress } = - emailTestConnection; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - secure: smtpPort === 465, - auth: { - user: username, - pass: password, - }, - } as SMTPTransport.Options); - // need to add a valid from address - const mailOptions = { - from: fromAddress, - to: toAddresses?.join(", "), - subject: "Test email", - text: "Hi, From Dokploy 👋", - }; - - await transporter.sendMail(mailOptions); - - console.log("Email notification sent successfully"); -}; - interface BuildFailedEmailProps { projectName: string; applicationName: string; @@ -540,28 +449,10 @@ export const sendBuildErrorNotifications = async ({ for (const notification of notificationList) { const { email, discord, telegram, slack } = notification; if (email) { - const { - smtpServer, - smtpPort, - username, - password, - fromAddress, - toAddresses, - } = email; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - secure: smtpPort === 465, - auth: { - user: username, - pass: password, - }, - } as SMTPTransport.Options); - const mailOptions = { - from: fromAddress, - to: toAddresses?.join(", "), - subject: "Build failed for dokploy", - html: render( + await sendEmailNotification( + email, + "Build failed for dokploy", + render( BuildFailedEmail({ projectName, applicationName, @@ -571,13 +462,12 @@ export const sendBuildErrorNotifications = async ({ date: date.toLocaleString(), }), ), - }; - await transporter.sendMail(mailOptions); + ); } if (discord) { const { webhookUrl } = discord; - const embed = { + await sendDiscordNotification(discord, { title: "⚠️ Build Failed", color: 0xff0000, fields: [ @@ -609,59 +499,31 @@ export const sendBuildErrorNotifications = async ({ footer: { text: "Dokploy Build Notification", }, - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - embeds: [embed], - }), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } if (telegram) { - const { botToken, chatId } = telegram; - const messageText = ` - ⚠️ Build Failed - - Project: ${projectName} - Application: ${applicationName} - Type: ${applicationType} - Time: ${date.toLocaleString()} - - Error: -
${errorMessage}
- - Build Details: ${buildLink} - `; - const url = `https://api.telegram.org/bot${botToken}/sendMessage`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: messageText, - parse_mode: "HTML", - disable_web_page_preview: true, - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } + await sendTelegramNotification( + telegram, + ` + ⚠️ Build Failed + + Project: ${projectName} + Application: ${applicationName} + Type: ${applicationType} + Time: ${date.toLocaleString()} + + Error: +
${errorMessage}
+ + Build Details: ${buildLink} + `, + ); } if (slack) { - const { webhookUrl, channel } = slack; - const message = { + const { channel } = slack; + await sendSlackNotification(slack, { channel: channel, attachments: [ { @@ -703,18 +565,7 @@ export const sendBuildErrorNotifications = async ({ ], }, ], - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(message), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } } }; @@ -747,28 +598,10 @@ export const sendBuildSuccessNotifications = async ({ const { email, discord, telegram, slack } = notification; if (email) { - const { - smtpServer, - smtpPort, - username, - password, - fromAddress, - toAddresses, - } = email; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - secure: smtpPort === 465, - auth: { - user: username, - pass: password, - }, - } as SMTPTransport.Options); - const mailOptions = { - from: fromAddress, - to: toAddresses?.join(", "), - subject: "Build success for dokploy", - html: render( + await sendEmailNotification( + email, + "Build success for dokploy", + render( BuildSuccessEmail({ projectName, applicationName, @@ -777,13 +610,11 @@ export const sendBuildSuccessNotifications = async ({ date: date.toLocaleString(), }), ), - }; - await transporter.sendMail(mailOptions); + ); } if (discord) { - const { webhookUrl } = discord; - const embed = { + await sendDiscordNotification(discord, { title: "✅ Build Success", color: 0x00ff00, fields: [ @@ -811,56 +642,28 @@ export const sendBuildSuccessNotifications = async ({ footer: { text: "Dokploy Build Notification", }, - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - embeds: [embed], - }), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } if (telegram) { - const { botToken, chatId } = telegram; - const messageText = ` - ✅ Build Success - - Project: ${projectName} - Application: ${applicationName} - Type: ${applicationType} - Time: ${date.toLocaleString()} - - Build Details: ${buildLink} - `; - const url = `https://api.telegram.org/bot${botToken}/sendMessage`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: messageText, - parse_mode: "HTML", - disable_web_page_preview: true, - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } + await sendTelegramNotification( + telegram, + ` + ✅ Build Success + + Project: ${projectName} + Application: ${applicationName} + Type: ${applicationType} + Time: ${date.toLocaleString()} + + Build Details: ${buildLink} + `, + ); } if (slack) { const { webhookUrl, channel } = slack; - const message = { + await sendSlackNotification(slack, { channel: channel, attachments: [ { @@ -901,18 +704,7 @@ export const sendBuildSuccessNotifications = async ({ ], }, ], - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(message), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } } }; @@ -945,28 +737,10 @@ export const sendDatabaseBackupNotifications = async ({ const { email, discord, telegram, slack } = notification; if (email) { - const { - smtpServer, - smtpPort, - username, - password, - fromAddress, - toAddresses, - } = email; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - secure: smtpPort === 465, - auth: { - user: username, - pass: password, - }, - } as SMTPTransport.Options); - const mailOptions = { - from: fromAddress, - to: toAddresses?.join(", "), - subject: "Database backup for dokploy", - html: render( + await sendEmailNotification( + email, + "Database backup for dokploy", + render( DatabaseBackupEmail({ projectName, applicationName, @@ -976,13 +750,11 @@ export const sendDatabaseBackupNotifications = async ({ date: date.toLocaleString(), }), ), - }; - await transporter.sendMail(mailOptions); + ); } if (discord) { - const { webhookUrl } = discord; - const embed = { + await sendDiscordNotification(discord, { title: type === "success" ? "✅ Database Backup Successful" @@ -1013,36 +785,23 @@ export const sendDatabaseBackupNotifications = async ({ name: "Type", value: type, }, + ...(type === "error" && errorMessage + ? [ + { + name: "Error Message", + value: errorMessage, + }, + ] + : []), ], timestamp: date.toISOString(), footer: { text: "Dokploy Database Backup Notification", }, - }; - - if (type === "error" && errorMessage) { - embed.fields.push({ - name: "Error Message", - value: errorMessage as unknown as string, - }); - } - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - embeds: [embed], - }), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } if (telegram) { - const { botToken, chatId } = telegram; const statusEmoji = type === "success" ? "✅" : "❌"; const messageText = ` ${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"} @@ -1055,28 +814,12 @@ export const sendDatabaseBackupNotifications = async ({ Status: ${type === "success" ? "Successful" : "Failed"} ${type === "error" && errorMessage ? `Error: ${errorMessage}` : ""} `; - const url = `https://api.telegram.org/bot${botToken}/sendMessage`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: messageText, - parse_mode: "HTML", - disable_web_page_preview: true, - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } + await sendTelegramNotification(telegram, messageText); } if (slack) { - const { webhookUrl, channel } = slack; - const message = { + const { channel } = slack; + await sendSlackNotification(slack, { channel: channel, attachments: [ { @@ -1086,6 +829,15 @@ export const sendDatabaseBackupNotifications = async ({ ? ":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, @@ -1124,25 +876,7 @@ export const sendDatabaseBackupNotifications = async ({ ], }, ], - }; - - if (type === "error" && errorMessage) { - message.attachments[0].fields.push({ - title: "Error Message", - value: errorMessage, - }); - } - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(message), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } } }; @@ -1165,40 +899,15 @@ export const sendDockerCleanupNotifications = async ( const { email, discord, telegram, slack } = notification; if (email) { - const { - smtpServer, - smtpPort, - username, - password, - fromAddress, - toAddresses, - } = email; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - secure: smtpPort === 465, - auth: { - user: username, - pass: password, - }, - } as SMTPTransport.Options); - const mailOptions = { - from: fromAddress, - to: toAddresses?.join(", "), - subject: "Docker cleanup for dokploy", - html: render( - DockerCleanupEmail({ - message, - date: date.toLocaleString(), - }), - ), - }; - await transporter.sendMail(mailOptions); + await sendEmailNotification( + email, + "Docker cleanup for dokploy", + render(DockerCleanupEmail({ message, date: date.toLocaleString() })), + ); } if (discord) { - const { webhookUrl } = discord; - const embed = { + await sendDiscordNotification(discord, { title: "✅ Docker Cleanup", color: 0x00ff00, fields: [ @@ -1211,51 +920,23 @@ export const sendDockerCleanupNotifications = async ( footer: { text: "Dokploy Docker Cleanup Notification", }, - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - embeds: [embed], - }), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } if (telegram) { - const { botToken, chatId } = telegram; - const messageText = ` - ✅ Docker Cleanup - Message: ${message} - Time: ${date.toLocaleString()} - `; - const url = `https://api.telegram.org/bot${botToken}/sendMessage`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: messageText, - parse_mode: "HTML", - disable_web_page_preview: true, - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } + await sendTelegramNotification( + telegram, + ` + ✅ Docker Cleanup + Message: ${message} + Time: ${date.toLocaleString()} + `, + ); } if (slack) { - const { webhookUrl, channel } = slack; - const messageResponse = { + const { channel } = slack; + await sendSlackNotification(slack, { channel: channel, attachments: [ { @@ -1281,24 +962,13 @@ export const sendDockerCleanupNotifications = async ( ], }, ], - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(messageResponse), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } } }; export const sendEmailNotification = async ( - connection: typeof email.$inferSelect, + connection: typeof email.$inferInsert, subject: string, htmlContent: string, ) => { @@ -1320,7 +990,7 @@ export const sendEmailNotification = async ( }; export const sendDiscordNotification = async ( - connection: typeof discord.$inferSelect, + connection: typeof discord.$inferInsert, embed: any, ) => { const response = await fetch(connection.webhookUrl, { @@ -1333,7 +1003,7 @@ export const sendDiscordNotification = async ( }; export const sendTelegramNotification = async ( - connection: typeof telegram.$inferSelect, + connection: typeof telegram.$inferInsert, messageText: string, ) => { const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`; @@ -1352,7 +1022,7 @@ export const sendTelegramNotification = async ( }; export const sendSlackNotification = async ( - connection: typeof slack.$inferSelect, + connection: typeof slack.$inferInsert, message: any, ) => { const response = await fetch(connection.webhookUrl, { @@ -1380,41 +1050,17 @@ export const sendDokployRestartNotifications = async () => { const { email, discord, telegram, slack } = notification; if (email) { - const { - smtpServer, - smtpPort, - username, - password, - fromAddress, - toAddresses, - } = email; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - secure: smtpPort === 465, - auth: { - user: username, - pass: password, - }, - } as SMTPTransport.Options); - const mailOptions = { - from: fromAddress, - to: toAddresses?.join(", "), - subject: "Dokploy Server Restarted", - html: render( - DokployRestartEmail({ - date: date.toLocaleString(), - }), - ), - }; - await transporter.sendMail(mailOptions); + await sendEmailNotification( + email, + "Dokploy Server Restarted", + render(DokployRestartEmail({ date: date.toLocaleString() })), + ); } if (discord) { - const { webhookUrl } = discord; - const embed = { + await sendDiscordNotification(discord, { title: "✅ Dokploy Server Restarted", - color: 0xff0000, + color: 0x00ff00, fields: [ { name: "Time", @@ -1426,50 +1072,22 @@ export const sendDokployRestartNotifications = async () => { footer: { text: "Dokploy Restart Notification", }, - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - embeds: [embed], - }), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } if (telegram) { - const { botToken, chatId } = telegram; - const messageText = ` - ✅ Dokploy Serverd Restarted - Time: ${date.toLocaleString()} - `; - const url = `https://api.telegram.org/bot${botToken}/sendMessage`; - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: chatId, - text: messageText, - parse_mode: "HTML", - disable_web_page_preview: true, - }), - }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } + await sendTelegramNotification( + telegram, + ` + ✅ Dokploy Serverd Restarted + Time: ${date.toLocaleString()} + `, + ); } if (slack) { - const { webhookUrl, channel } = slack; - const message = { + const { channel } = slack; + await sendSlackNotification(slack, { channel: channel, attachments: [ { @@ -1491,18 +1109,7 @@ export const sendDokployRestartNotifications = async () => { ], }, ], - }; - const response = await fetch(webhookUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(message), }); - - if (!response.ok) { - throw new Error("Error to send test notification"); - } } } }; diff --git a/server/server.ts b/server/server.ts index 2efee269..0e8649cf 100644 --- a/server/server.ts +++ b/server/server.ts @@ -57,9 +57,8 @@ void app.prepare().then(async () => { // Timeout to wait for the database to be ready await new Promise((resolve) => setTimeout(resolve, 7000)); await migration(); + await sendDokployRestartNotifications(); } - await sendDokployRestartNotifications(); - server.listen(PORT); console.log("Server Started:", PORT); deploymentWorker.run();