mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
778 lines
17 KiB
TypeScript
778 lines
17 KiB
TypeScript
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,
|
|
type apiUpdateTelegram,
|
|
discord,
|
|
email,
|
|
notifications,
|
|
slack,
|
|
telegram,
|
|
} from "@/server/db/schema";
|
|
import { render } from "@react-email/components";
|
|
import { TRPCError } from "@trpc/server";
|
|
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;
|
|
|
|
export const createSlackNotification = async (
|
|
input: typeof apiCreateSlack._type,
|
|
) => {
|
|
await db.transaction(async (tx) => {
|
|
const newSlack = await tx
|
|
.insert(slack)
|
|
.values({
|
|
channel: input.channel,
|
|
webhookUrl: input.webhookUrl,
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newSlack) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting slack",
|
|
});
|
|
}
|
|
|
|
const newDestination = await tx
|
|
.insert(notifications)
|
|
.values({
|
|
slackId: newSlack.slackId,
|
|
name: input.name,
|
|
appDeploy: input.appDeploy,
|
|
userJoin: input.userJoin,
|
|
appBuildError: input.appBuildError,
|
|
databaseBackup: input.databaseBackup,
|
|
dokployRestart: input.dokployRestart,
|
|
notificationType: "slack",
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newDestination) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting notification",
|
|
});
|
|
}
|
|
|
|
return newDestination;
|
|
});
|
|
};
|
|
|
|
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,
|
|
) => {
|
|
await db.transaction(async (tx) => {
|
|
const newTelegram = await tx
|
|
.insert(telegram)
|
|
.values({
|
|
botToken: input.botToken,
|
|
chatId: input.chatId,
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newTelegram) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting telegram",
|
|
});
|
|
}
|
|
|
|
const newDestination = await tx
|
|
.insert(notifications)
|
|
.values({
|
|
telegramId: newTelegram.telegramId,
|
|
name: input.name,
|
|
appDeploy: input.appDeploy,
|
|
userJoin: input.userJoin,
|
|
appBuildError: input.appBuildError,
|
|
databaseBackup: input.databaseBackup,
|
|
dokployRestart: input.dokployRestart,
|
|
notificationType: "telegram",
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newDestination) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting notification",
|
|
});
|
|
}
|
|
|
|
return newDestination;
|
|
});
|
|
};
|
|
|
|
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,
|
|
) => {
|
|
await db.transaction(async (tx) => {
|
|
const newDiscord = await tx
|
|
.insert(discord)
|
|
.values({
|
|
webhookUrl: input.webhookUrl,
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newDiscord) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting discord",
|
|
});
|
|
}
|
|
|
|
const newDestination = await tx
|
|
.insert(notifications)
|
|
.values({
|
|
discordId: newDiscord.discordId,
|
|
name: input.name,
|
|
appDeploy: input.appDeploy,
|
|
userJoin: input.userJoin,
|
|
appBuildError: input.appBuildError,
|
|
databaseBackup: input.databaseBackup,
|
|
dokployRestart: input.dokployRestart,
|
|
notificationType: "discord",
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newDestination) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting notification",
|
|
});
|
|
}
|
|
|
|
return newDestination;
|
|
});
|
|
};
|
|
|
|
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,
|
|
) => {
|
|
await db.transaction(async (tx) => {
|
|
const newEmail = await tx
|
|
.insert(email)
|
|
.values({
|
|
smtpServer: input.smtpServer,
|
|
smtpPort: input.smtpPort,
|
|
username: input.username,
|
|
password: input.password,
|
|
fromAddress: input.fromAddress,
|
|
toAddresses: input.toAddresses,
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newEmail) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting email",
|
|
});
|
|
}
|
|
|
|
const newDestination = await tx
|
|
.insert(notifications)
|
|
.values({
|
|
emailId: newEmail.emailId,
|
|
name: input.name,
|
|
appDeploy: input.appDeploy,
|
|
userJoin: input.userJoin,
|
|
appBuildError: input.appBuildError,
|
|
databaseBackup: input.databaseBackup,
|
|
dokployRestart: input.dokployRestart,
|
|
notificationType: "email",
|
|
})
|
|
.returning()
|
|
.then((value) => value[0]);
|
|
|
|
if (!newDestination) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Error input: Inserting notification",
|
|
});
|
|
}
|
|
|
|
return newDestination;
|
|
});
|
|
};
|
|
|
|
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),
|
|
with: {
|
|
slack: true,
|
|
telegram: true,
|
|
discord: true,
|
|
email: true,
|
|
},
|
|
});
|
|
if (!notification) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Notification not found",
|
|
});
|
|
}
|
|
return notification;
|
|
};
|
|
|
|
export const removeNotificationById = async (notificationId: string) => {
|
|
const result = await db
|
|
.delete(notifications)
|
|
.where(eq(notifications.notificationId, notificationId))
|
|
.returning();
|
|
|
|
return result[0];
|
|
};
|
|
|
|
export const updateDestinationById = async (
|
|
notificationId: string,
|
|
notificationData: Partial<Notification>,
|
|
) => {
|
|
const result = await db
|
|
.update(notifications)
|
|
.set({
|
|
...notificationData,
|
|
})
|
|
.where(eq(notifications.notificationId, notificationId))
|
|
.returning();
|
|
|
|
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");
|
|
};
|
|
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
// export const
|
|
|
|
export const sendBuildErrorNotifications = async ({
|
|
projectName,
|
|
applicationName,
|
|
applicationType,
|
|
errorMessage,
|
|
buildLink,
|
|
}: {
|
|
projectName: string;
|
|
applicationName: string;
|
|
applicationType: string;
|
|
errorMessage: string;
|
|
buildLink: string;
|
|
}) => {
|
|
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) {
|
|
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);
|
|
}
|
|
|
|
if (discord) {
|
|
const { webhookUrl } = discord;
|
|
const embed = {
|
|
title: "⚠️ Build Failed",
|
|
color: 0xff0000, // Rojo
|
|
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",
|
|
},
|
|
};
|
|
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 = `
|
|
<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}
|
|
`;
|
|
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");
|
|
}
|
|
}
|
|
|
|
if (slack) {
|
|
const { webhookUrl, channel } = slack;
|
|
const message = {
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
};
|