feat(notifications): add build failed and invitation emails from react-email

This commit is contained in:
Mauricio Siu
2024-07-14 02:49:21 -06:00
parent 5fadd73732
commit 79ad0818f5
38 changed files with 15799 additions and 353 deletions

View File

@@ -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);
}
}
};