mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(notifications): add build failed and invitation emails from react-email
This commit is contained in:
@@ -4,14 +4,26 @@ import {
|
||||
type apiCreateEmail,
|
||||
type apiCreateSlack,
|
||||
type apiCreateTelegram,
|
||||
type apiTestDiscordConnection,
|
||||
type apiTestEmailConnection,
|
||||
type apiTestSlackConnection,
|
||||
type apiTestTelegramConnection,
|
||||
type apiUpdateDiscord,
|
||||
type apiUpdateEmail,
|
||||
type apiUpdateSlack,
|
||||
type apiUpdateTelegram,
|
||||
discord,
|
||||
email,
|
||||
notifications,
|
||||
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 { and, eq, isNotNull } from "drizzle-orm";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import { BuildFailedEmail } from "@/emails/emails/build-failed";
|
||||
|
||||
export type Notification = typeof notifications.$inferSelect;
|
||||
|
||||
@@ -61,6 +73,45 @@ export const createSlackNotification = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const updateSlackNotification = async (
|
||||
input: typeof apiUpdateSlack._type,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDestination = await tx
|
||||
.update(notifications)
|
||||
.set({
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
userJoin: input.userJoin,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error Updating notification",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(slack)
|
||||
.set({
|
||||
channel: input.channel,
|
||||
webhookUrl: input.webhookUrl,
|
||||
})
|
||||
.where(eq(slack.slackId, input.slackId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const createTelegramNotification = async (
|
||||
input: typeof apiCreateTelegram._type,
|
||||
) => {
|
||||
@@ -107,6 +158,45 @@ export const createTelegramNotification = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const updateTelegramNotification = async (
|
||||
input: typeof apiUpdateTelegram._type,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDestination = await tx
|
||||
.update(notifications)
|
||||
.set({
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
userJoin: input.userJoin,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error Updating notification",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(telegram)
|
||||
.set({
|
||||
botToken: input.botToken,
|
||||
chatId: input.chatId,
|
||||
})
|
||||
.where(eq(telegram.telegramId, input.telegramId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const createDiscordNotification = async (
|
||||
input: typeof apiCreateDiscord._type,
|
||||
) => {
|
||||
@@ -152,6 +242,44 @@ export const createDiscordNotification = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const updateDiscordNotification = async (
|
||||
input: typeof apiUpdateDiscord._type,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDestination = await tx
|
||||
.update(notifications)
|
||||
.set({
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
userJoin: input.userJoin,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error Updating notification",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(discord)
|
||||
.set({
|
||||
webhookUrl: input.webhookUrl,
|
||||
})
|
||||
.where(eq(discord.discordId, input.discordId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const createEmailNotification = async (
|
||||
input: typeof apiCreateEmail._type,
|
||||
) => {
|
||||
@@ -163,6 +291,7 @@ export const createEmailNotification = async (
|
||||
smtpPort: input.smtpPort,
|
||||
username: input.username,
|
||||
password: input.password,
|
||||
fromAddress: input.fromAddress,
|
||||
toAddresses: input.toAddresses,
|
||||
})
|
||||
.returning()
|
||||
@@ -201,6 +330,49 @@ export const createEmailNotification = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const updateEmailNotification = async (
|
||||
input: typeof apiUpdateEmail._type,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDestination = await tx
|
||||
.update(notifications)
|
||||
.set({
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
userJoin: input.userJoin,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error Updating notification",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(email)
|
||||
.set({
|
||||
smtpServer: input.smtpServer,
|
||||
smtpPort: input.smtpPort,
|
||||
username: input.username,
|
||||
password: input.password,
|
||||
fromAddress: input.fromAddress,
|
||||
toAddresses: input.toAddresses,
|
||||
})
|
||||
.where(eq(email.emailId, input.emailId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const findNotificationById = async (notificationId: string) => {
|
||||
const notification = await db.query.notifications.findFirst({
|
||||
where: eq(notifications.notificationId, notificationId),
|
||||
@@ -244,21 +416,187 @@ export const updateDestinationById = async (
|
||||
return result[0];
|
||||
};
|
||||
|
||||
export const sendNotification = async (
|
||||
notificationData: Partial<Notification>,
|
||||
export const sendSlackTestNotification = async (
|
||||
slackTestConnection: typeof apiTestSlackConnection._type,
|
||||
) => {
|
||||
// if(notificationData.notificationType === "slack"){
|
||||
// const { webhookUrl, channel } = notificationData;
|
||||
// try {
|
||||
// const response = await fetch(webhookUrl, {
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// body: JSON.stringify({ text: "Test notification", channel }),
|
||||
// });
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// }
|
||||
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");
|
||||
};
|
||||
|
||||
// export const sendInvitationEmail = async (
|
||||
// emailTestConnection: typeof apiTestEmailConnection._type,
|
||||
// inviteLink: string,
|
||||
// toEmail: string,
|
||||
// ) => {
|
||||
// const { smtpServer, smtpPort, username, password, 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: toEmail,
|
||||
// subject: "Invitation to join Dokploy",
|
||||
// html: InvitationTemplate({
|
||||
// inviteLink: inviteLink,
|
||||
// toEmail: toEmail,
|
||||
// }),
|
||||
// };
|
||||
|
||||
// await transporter.sendMail(mailOptions);
|
||||
|
||||
// console.log("Email notification sent successfully");
|
||||
// };
|
||||
|
||||
export const sendBuildFailedEmail = async ({
|
||||
projectName,
|
||||
applicationName,
|
||||
applicationType,
|
||||
errorMessage,
|
||||
buildLink,
|
||||
}: {
|
||||
projectName: string;
|
||||
applicationName: string;
|
||||
applicationType: string;
|
||||
errorMessage: string;
|
||||
buildLink: string;
|
||||
}) => {
|
||||
const notificationList = await db.query.notifications.findMany({
|
||||
where: and(
|
||||
isNotNull(notifications.emailId),
|
||||
eq(notifications.appBuildError, true),
|
||||
),
|
||||
with: {
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email } = 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(
|
||||
BuildFailedEmail({
|
||||
projectName,
|
||||
applicationName,
|
||||
applicationType,
|
||||
errorMessage,
|
||||
buildLink,
|
||||
}),
|
||||
),
|
||||
};
|
||||
await transporter.sendMail(mailOptions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user