diff --git a/server/api/routers/notification.ts b/server/api/routers/notification.ts index 4ed5e22f..80606a93 100644 --- a/server/api/routers/notification.ts +++ b/server/api/routers/notification.ts @@ -20,6 +20,12 @@ import { apiUpdateTelegram, notifications, } from "@/server/db/schema"; +import { + sendDiscordNotification, + sendEmailNotification, + sendSlackNotification, + sendTelegramNotification, +} from "@/server/utils/notifications/utils"; import { TRPCError } from "@trpc/server"; import { desc } from "drizzle-orm"; import { @@ -29,10 +35,6 @@ import { createTelegramNotification, findNotificationById, removeNotificationById, - sendDiscordNotification, - sendEmailNotification, - sendSlackNotification, - sendTelegramNotification, updateDiscordNotification, updateEmailNotification, updateSlackNotification, diff --git a/server/api/services/admin.ts b/server/api/services/admin.ts index 118f2e10..8a2e301a 100644 --- a/server/api/services/admin.ts +++ b/server/api/services/admin.ts @@ -146,3 +146,12 @@ export const removeUserByAuthId = async (authId: string) => { .returning() .then((res) => res[0]); }; + +export const getDokployUrl = async () => { + const admin = await findAdmin(); + + if (admin.host) { + return `https://${admin.host}`; + } + return `http://${admin.serverIp}:${process.env.PORT}`; +}; diff --git a/server/api/services/application.ts b/server/api/services/application.ts index 10040e55..b2399e75 100644 --- a/server/api/services/application.ts +++ b/server/api/services/application.ts @@ -15,7 +15,7 @@ import { createTraefikConfig } from "@/server/utils/traefik/application"; import { generatePassword } from "@/templates/utils"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import { findAdmin } from "./admin"; +import { findAdmin, getDokployUrl } from "./admin"; import { createDeployment, updateDeploymentStatus } from "./deployment"; import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error"; @@ -140,6 +140,7 @@ export const deployApplication = async ({ descriptionLog: string; }) => { const application = await findApplicationById(applicationId); + const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`; const admin = await findAdmin(); const deployment = await createDeployment({ applicationId: applicationId, @@ -164,7 +165,7 @@ export const deployApplication = async ({ projectName: application.project.name, applicationName: application.name, applicationType: "application", - buildLink: deployment.logPath, + buildLink, }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); @@ -173,8 +174,9 @@ export const deployApplication = async ({ projectName: application.project.name, applicationName: application.name, applicationType: "application", + // @ts-ignore errorMessage: error?.message || "Error to build", - buildLink: deployment.logPath, + buildLink, }); console.log( diff --git a/server/api/services/compose.ts b/server/api/services/compose.ts index e2248e4c..2f4e09c3 100644 --- a/server/api/services/compose.ts +++ b/server/api/services/compose.ts @@ -5,6 +5,8 @@ import { type apiCreateCompose, compose } from "@/server/db/schema"; import { generateAppName } from "@/server/db/schema/utils"; import { buildCompose } from "@/server/utils/builders/compose"; import type { ComposeSpecification } from "@/server/utils/docker/types"; +import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error"; +import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success"; import { execAsync } from "@/server/utils/process/execAsync"; import { cloneGitRepository } from "@/server/utils/providers/git"; import { cloneGithubRepository } from "@/server/utils/providers/github"; @@ -13,7 +15,7 @@ import { generatePassword } from "@/templates/utils"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { load } from "js-yaml"; -import { findAdmin } from "./admin"; +import { findAdmin, getDokployUrl } from "./admin"; import { createDeploymentCompose, updateDeploymentStatus } from "./deployment"; import { validUniqueServerAppName } from "./project"; @@ -142,6 +144,7 @@ export const deployCompose = async ({ }) => { const compose = await findComposeById(composeId); const admin = await findAdmin(); + const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`; const deployment = await createDeploymentCompose({ composeId: composeId, title: titleLog, @@ -161,11 +164,26 @@ export const deployCompose = async ({ await updateCompose(composeId, { composeStatus: "done", }); + + await sendBuildSuccessNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + buildLink, + }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); await updateCompose(composeId, { composeStatus: "error", }); + await sendBuildErrorNotifications({ + projectName: compose.project.name, + applicationName: compose.name, + applicationType: "compose", + // @ts-ignore + errorMessage: error?.message || "Error to build", + buildLink, + }); throw error; } }; diff --git a/server/server.ts b/server/server.ts index 1f6b5c4f..607a5fa0 100644 --- a/server/server.ts +++ b/server/server.ts @@ -59,6 +59,7 @@ void app.prepare().then(async () => { await migration(); await sendDokployRestartNotifications(); } + server.listen(PORT); console.log("Server Started:", PORT); deploymentWorker.run(); diff --git a/server/utils/notifications/build-error.ts b/server/utils/notifications/build-error.ts index 53482365..4a2a5037 100644 --- a/server/utils/notifications/build-error.ts +++ b/server/utils/notifications/build-error.ts @@ -1,7 +1,7 @@ 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 { renderAsync } from "@react-email/components"; import { eq } from "drizzle-orm"; import { sendDiscordNotification, @@ -39,20 +39,17 @@ export const sendBuildErrorNotifications = async ({ 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(), - }), - ), - ); + 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) { @@ -149,7 +146,7 @@ export const sendBuildErrorNotifications = async ({ { type: "button", text: "View Build Details", - url: "https://doks.dev/build-details", + url: buildLink, }, ], }, diff --git a/server/utils/notifications/build-success.ts b/server/utils/notifications/build-success.ts index c51bdca7..d8bea0ec 100644 --- a/server/utils/notifications/build-success.ts +++ b/server/utils/notifications/build-success.ts @@ -1,7 +1,7 @@ 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 { renderAsync } from "@react-email/components"; import { eq } from "drizzle-orm"; import { sendDiscordNotification, @@ -38,19 +38,16 @@ export const sendBuildSuccessNotifications = async ({ const { email, discord, telegram, slack } = notification; if (email) { - await sendEmailNotification( - email, - "Build success for dokploy", - render( - BuildSuccessEmail({ - projectName, - applicationName, - applicationType, - buildLink, - date: date.toLocaleString(), - }), - ), - ); + const template = await renderAsync( + BuildSuccessEmail({ + projectName, + applicationName, + applicationType, + buildLink, + date: date.toLocaleString(), + }), + ).catch(); + await sendEmailNotification(email, "Build success for dokploy", template); } if (discord) { @@ -130,16 +127,12 @@ export const sendBuildSuccessNotifications = async ({ value: date.toLocaleString(), short: true, }, - { - title: "Build Link", - value: buildLink, - }, ], actions: [ { type: "button", text: "View Build Details", - url: "https://doks.dev/build-details", + url: buildLink, }, ], }, diff --git a/server/utils/notifications/database-backup.ts b/server/utils/notifications/database-backup.ts index 9555799f..f04a4c25 100644 --- a/server/utils/notifications/database-backup.ts +++ b/server/utils/notifications/database-backup.ts @@ -1,7 +1,7 @@ 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 { renderAsync } from "@react-email/components"; import { eq } from "drizzle-orm"; import { sendDiscordNotification, @@ -38,19 +38,20 @@ export const sendDatabaseBackupNotifications = async ({ const { email, discord, telegram, slack } = notification; if (email) { + const template = await renderAsync( + DatabaseBackupEmail({ + projectName, + applicationName, + databaseType, + type, + errorMessage, + date: date.toLocaleString(), + }), + ).catch(); await sendEmailNotification( email, "Database backup for dokploy", - render( - DatabaseBackupEmail({ - projectName, - applicationName, - databaseType, - type, - errorMessage, - date: date.toLocaleString(), - }), - ), + template, ); } @@ -168,13 +169,6 @@ export const sendDatabaseBackupNotifications = async ({ value: type === "success" ? "Successful" : "Failed", }, ], - actions: [ - { - type: "button", - text: "View Build Details", - url: "https://doks.dev/build-details", - }, - ], }, ], }); diff --git a/server/utils/notifications/docker-cleanup.ts b/server/utils/notifications/docker-cleanup.ts index c2d39a0e..8567b20e 100644 --- a/server/utils/notifications/docker-cleanup.ts +++ b/server/utils/notifications/docker-cleanup.ts @@ -1,7 +1,7 @@ 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 { renderAsync } from "@react-email/components"; import { eq } from "drizzle-orm"; import { sendDiscordNotification, @@ -28,10 +28,14 @@ export const sendDockerCleanupNotifications = async ( const { email, discord, telegram, slack } = notification; if (email) { + const template = await renderAsync( + DockerCleanupEmail({ message, date: date.toLocaleString() }), + ).catch(); + await sendEmailNotification( email, "Docker cleanup for dokploy", - render(DockerCleanupEmail({ message, date: date.toLocaleString() })), + template, ); } @@ -82,13 +86,6 @@ export const sendDockerCleanupNotifications = async ( short: true, }, ], - actions: [ - { - type: "button", - text: "View Build Details", - url: "https://doks.dev/build-details", - }, - ], }, ], }); diff --git a/server/utils/notifications/dokploy-restart.ts b/server/utils/notifications/dokploy-restart.ts index 6535b446..e86c2dbd 100644 --- a/server/utils/notifications/dokploy-restart.ts +++ b/server/utils/notifications/dokploy-restart.ts @@ -1,7 +1,7 @@ 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 { renderAsync } from "@react-email/components"; import { eq } from "drizzle-orm"; import { sendDiscordNotification, @@ -26,17 +26,16 @@ export const sendDokployRestartNotifications = async () => { const { email, discord, telegram, slack } = notification; if (email) { - await sendEmailNotification( - email, - "Dokploy Server Restarted", - render(DokployRestartEmail({ date: date.toLocaleString() })), - ); + const template = await renderAsync( + DokployRestartEmail({ date: date.toLocaleString() }), + ).catch(); + await sendEmailNotification(email, "Dokploy Server Restarted", template); } if (discord) { await sendDiscordNotification(discord, { title: "✅ Dokploy Server Restarted", - color: 0x00ff00, + color: 0xff0000, fields: [ { name: "Time", @@ -67,7 +66,7 @@ export const sendDokployRestartNotifications = async () => { channel: channel, attachments: [ { - color: "#FF0000", + color: "#00FF00", pretext: ":white_check_mark: *Dokploy Server Restarted*", fields: [ { @@ -76,13 +75,6 @@ export const sendDokployRestartNotifications = async () => { short: true, }, ], - actions: [ - { - type: "button", - text: "View Build Details", - url: "https://doks.dev/build-details", - }, - ], }, ], }); diff --git a/server/utils/notifications/utils.ts b/server/utils/notifications/utils.ts index 3b1513bf..309bb8ec 100644 --- a/server/utils/notifications/utils.ts +++ b/server/utils/notifications/utils.ts @@ -6,64 +6,80 @@ export const sendEmailNotification = async ( 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 }, - }); + try { + 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, - }); + await transporter.sendMail({ + from: fromAddress, + to: toAddresses.join(", "), + subject, + html: htmlContent, + }); + } catch (err) { + console.log(err); + } }; 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"); + try { + await fetch(connection.webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ embeds: [embed] }), + }); + } catch (err) { + console.log(err); + } }; 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"); + 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, + text: messageText, + parse_mode: "HTML", + disable_web_page_preview: true, + }), + }); + } catch (err) { + console.log(err); + } }; 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"); + try { + await fetch(connection.webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(message), + }); + } catch (err) { + console.log(err); + } };