Files
dokploy/packages/server/src/services/notification.ts
Mauricio Siu a04a4c05ea chore(dependencies): update Next.js to version 16.0.10 and remove turbopack script from package.json
- Updated Next.js version in both root and dokploy package.json files to 16.0.10 for improved performance and features.
- Removed the turbopack development script from the dokploy package.json to streamline the development process.
- Added volumeBackup property to notification handling in multiple files for enhanced backup options.
2025-12-12 10:09:31 -06:00

820 lines
19 KiB
TypeScript

import { db } from "@dokploy/server/db";
import {
type apiCreateCustom,
type apiCreateDiscord,
type apiCreateEmail,
type apiCreateGotify,
type apiCreateLark,
type apiCreateNtfy,
type apiCreateSlack,
type apiCreateTelegram,
type apiUpdateCustom,
type apiUpdateDiscord,
type apiUpdateEmail,
type apiUpdateGotify,
type apiUpdateLark,
type apiUpdateNtfy,
type apiUpdateSlack,
type apiUpdateTelegram,
custom,
discord,
email,
gotify,
lark,
notifications,
ntfy,
slack,
telegram,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
export type Notification = typeof notifications.$inferSelect;
export const createSlackNotification = async (
input: typeof apiCreateSlack._type,
organizationId: string,
) => {
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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "slack",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.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,
organizationId: string,
) => {
await db.transaction(async (tx) => {
const newTelegram = await tx
.insert(telegram)
.values({
botToken: input.botToken,
chatId: input.chatId,
messageThreadId: input.messageThreadId,
})
.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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "telegram",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.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,
messageThreadId: input.messageThreadId,
})
.where(eq(telegram.telegramId, input.telegramId))
.returning()
.then((value) => value[0]);
return newDestination;
});
};
export const createDiscordNotification = async (
input: typeof apiCreateDiscord._type,
organizationId: string,
) => {
await db.transaction(async (tx) => {
const newDiscord = await tx
.insert(discord)
.values({
webhookUrl: input.webhookUrl,
decoration: input.decoration,
})
.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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "discord",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.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,
decoration: input.decoration,
})
.where(eq(discord.discordId, input.discordId))
.returning()
.then((value) => value[0]);
return newDestination;
});
};
export const createEmailNotification = async (
input: typeof apiCreateEmail._type,
organizationId: string,
) => {
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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "email",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.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,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.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 createGotifyNotification = async (
input: typeof apiCreateGotify._type,
organizationId: string,
) => {
await db.transaction(async (tx) => {
const newGotify = await tx
.insert(gotify)
.values({
serverUrl: input.serverUrl,
appToken: input.appToken,
priority: input.priority,
decoration: input.decoration,
})
.returning()
.then((value) => value[0]);
if (!newGotify) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting gotify",
});
}
const newDestination = await tx
.insert(notifications)
.values({
gotifyId: newGotify.gotifyId,
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "gotify",
organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
if (!newDestination) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting notification",
});
}
return newDestination;
});
};
export const updateGotifyNotification = async (
input: typeof apiUpdateGotify._type,
) => {
await db.transaction(async (tx) => {
const newDestination = await tx
.update(notifications)
.set({
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
})
.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(gotify)
.set({
serverUrl: input.serverUrl,
appToken: input.appToken,
priority: input.priority,
decoration: input.decoration,
})
.where(eq(gotify.gotifyId, input.gotifyId));
return newDestination;
});
};
export const createNtfyNotification = async (
input: typeof apiCreateNtfy._type,
organizationId: string,
) => {
await db.transaction(async (tx) => {
const newNtfy = await tx
.insert(ntfy)
.values({
serverUrl: input.serverUrl,
topic: input.topic,
accessToken: input.accessToken ?? null,
priority: input.priority,
})
.returning()
.then((value) => value[0]);
if (!newNtfy) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting ntfy",
});
}
const newDestination = await tx
.insert(notifications)
.values({
ntfyId: newNtfy.ntfyId,
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "ntfy",
organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
if (!newDestination) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting notification",
});
}
return newDestination;
});
};
export const updateNtfyNotification = async (
input: typeof apiUpdateNtfy._type,
) => {
await db.transaction(async (tx) => {
const newDestination = await tx
.update(notifications)
.set({
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
})
.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(ntfy)
.set({
serverUrl: input.serverUrl,
topic: input.topic,
accessToken: input.accessToken ?? null,
priority: input.priority,
})
.where(eq(ntfy.ntfyId, input.ntfyId));
return newDestination;
});
};
export const createCustomNotification = async (
input: typeof apiCreateCustom._type,
organizationId: string,
) => {
await db.transaction(async (tx) => {
const newCustom = await tx
.insert(custom)
.values({
endpoint: input.endpoint,
headers: input.headers,
})
.returning()
.then((value) => value[0]);
if (!newCustom) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting custom",
});
}
const newDestination = await tx
.insert(notifications)
.values({
customId: newCustom.customId,
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "custom",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
.then((value) => value[0]);
if (!newDestination) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting notification",
});
}
return newDestination;
});
};
export const updateCustomNotification = async (
input: typeof apiUpdateCustom._type,
) => {
await db.transaction(async (tx) => {
const newDestination = await tx
.update(notifications)
.set({
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.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(custom)
.set({
endpoint: input.endpoint,
headers: input.headers,
})
.where(eq(custom.customId, input.customId));
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,
gotify: true,
ntfy: true,
custom: true,
lark: 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 createLarkNotification = async (
input: typeof apiCreateLark._type,
organizationId: string,
) => {
await db.transaction(async (tx) => {
const newLark = await tx
.insert(lark)
.values({
webhookUrl: input.webhookUrl,
})
.returning()
.then((value) => value[0]);
if (!newLark) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting lark",
});
}
const newDestination = await tx
.insert(notifications)
.values({
larkId: newLark.larkId,
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "lark",
organizationId: organizationId,
serverThreshold: input.serverThreshold,
})
.returning()
.then((value) => value[0]);
if (!newDestination) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting notification",
});
}
return newDestination;
});
};
export const updateLarkNotification = async (
input: typeof apiUpdateLark._type,
) => {
await db.transaction(async (tx) => {
const newDestination = await tx
.update(notifications)
.set({
name: input.name,
appDeploy: input.appDeploy,
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
volumeBackup: input.volumeBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
organizationId: input.organizationId,
serverThreshold: input.serverThreshold,
})
.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(lark)
.set({
webhookUrl: input.webhookUrl,
})
.where(eq(lark.larkId, input.larkId))
.returning()
.then((value) => value[0]);
return newDestination;
});
};
export const updateNotificationById = async (
notificationId: string,
notificationData: Partial<Notification>,
) => {
const result = await db
.update(notifications)
.set({
...notificationData,
})
.where(eq(notifications.notificationId, notificationId))
.returning();
return result[0];
};