feat: implement test connection for all the providers

This commit is contained in:
Mauricio Siu
2024-07-12 01:29:24 -06:00
parent 342ff4b589
commit 5fadd73732
12 changed files with 6308 additions and 191 deletions

View File

@@ -19,10 +19,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useFieldArray, useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
@@ -72,6 +71,7 @@ const mySchema = z.discriminatedUnion("type", [
smtpPort: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
fromAddress: z.string().min(1),
toAddresses: z.array(z.string()).min(1),
})
.merge(baseDatabaseSchema),
@@ -101,6 +101,8 @@ type AddNotification = z.infer<typeof mySchema>;
export const AddNotification = () => {
const utils = api.useUtils();
const [visible, setVisible] = useState(false);
const { mutateAsync: testConnection, isLoading: isLoadingConnection } =
api.notification.testConnection.useMutation();
const slackMutation = api.notification.createSlack.useMutation();
const telegramMutation = api.notification.createTelegram.useMutation();
const discordMutation = api.notification.createDiscord.useMutation();
@@ -114,11 +116,23 @@ export const AddNotification = () => {
},
resolver: zodResolver(mySchema),
});
const type = form.watch("type");
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "toAddresses",
});
useEffect(() => {
if (type === "email") {
append("");
}
}, [type, append]);
useEffect(() => {
form.reset();
}, [form, form.reset, form.formState.isSubmitSuccessful]);
const type = form.watch("type");
const activeMutation = {
slack: slackMutation,
telegram: telegramMutation,
@@ -178,6 +192,7 @@ export const AddNotification = () => {
smtpPort: data.smtpPort,
username: data.username,
password: data.password,
fromAddress: data.fromAddress,
toAddresses: data.toAddresses,
name: data.name,
});
@@ -339,7 +354,7 @@ export const AddNotification = () => {
<FormLabel>Bot Token</FormLabel>
<FormControl>
<Input
placeholder="123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
placeholder="6660491268:AAFMGmajZOVewpMNZCgJr5H7cpXpoZPgvXw"
{...field}
/>
</FormControl>
@@ -356,7 +371,7 @@ export const AddNotification = () => {
<FormItem>
<FormLabel>Chat ID</FormLabel>
<FormControl>
<Input placeholder="Chat ID" {...field} />
<Input placeholder="431231869" {...field} />
</FormControl>
<FormMessage />
@@ -452,8 +467,21 @@ export const AddNotification = () => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="fromAddress"
render={({ field }) => (
<FormItem>
<FormLabel>From Address</FormLabel>
<FormControl>
<Input placeholder="from@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* <FormField
control={form.control}
name="toAddresses"
render={({ field }) => (
@@ -466,7 +494,54 @@ export const AddNotification = () => {
<FormMessage />
</FormItem>
)}
/>
/> */}
<div className="flex flex-col gap-2 pt-2">
<FormLabel>To Addresses</FormLabel>
{fields.map((field, index) => (
<div
key={field.id}
className="flex flex-row gap-2 w-full"
>
<FormField
control={form.control}
name={`toAddresses.${index}`}
render={({ field }) => (
<FormItem className="w-full">
<FormControl>
<Input
placeholder="email@example.com"
className="w-full"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
variant="outline"
type="button"
onClick={() => {
remove(index);
}}
>
Remove
</Button>
</div>
))}
</div>
<Button
variant="outline"
type="button"
onClick={() => {
append("");
}}
>
Add
</Button>
</>
)}
</div>
@@ -561,7 +636,35 @@ export const AddNotification = () => {
</div>
</form>
<DialogFooter>
<DialogFooter className="flex flex-row gap-2 !justify-between w-full">
<Button
isLoading={isLoadingConnection}
variant="secondary"
onClick={async () => {
await testConnection({
webhookUrl: form.getValues("webhookUrl"),
channel: form.getValues("channel"),
notificationType: type,
botToken: form.getValues("botToken"),
chatId: form.getValues("chatId"),
//
smtpPort: form.getValues("smtpPort"),
smtpServer: form.getValues("smtpServer"),
username: form.getValues("username"),
password: form.getValues("password"),
toAddresses: form.getValues("toAddresses"),
fromAddress: form.getValues("fromAddress"),
})
.then(async () => {
toast.success("Connection Success");
})
.catch(() => {
toast.error("Error to connect the provider");
});
}}
>
Send Test
</Button>
<Button
isLoading={form.formState.isSubmitting}
form="hook-form"

View File

@@ -0,0 +1,7 @@
DO $$ BEGIN
CREATE TYPE "public"."notificationType" AS ENUM('slack', 'telegram', 'discord', 'email');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "notification" ADD COLUMN "notificationType" "notificationType" NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "email" ADD COLUMN "fromAddress" text NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -141,6 +141,20 @@
"when": 1720507012671,
"tag": "0019_careless_hardball",
"breakpoints": true
},
{
"idx": 20,
"version": "6",
"when": 1720762222675,
"tag": "0020_ambitious_edwin_jarvis",
"breakpoints": true
},
{
"idx": 21,
"version": "6",
"when": 1720768664067,
"tag": "0021_nervous_dragon_lord",
"breakpoints": true
}
]
}

View File

@@ -1,159 +1,161 @@
{
"name": "dokploy",
"version": "v0.3.1",
"private": true,
"license": "Apache-2.0",
"type": "module",
"scripts": {
"build": "npm run build-server && npm run build-next",
"start": "node dist/server.mjs",
"build-server": "tsx esbuild.config.ts",
"build-next": "next build",
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
"reset-password": "node dist/reset-password.mjs",
"dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
"migration:run": "tsx -r dotenv/config migration.ts",
"migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
"migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
"db:push": "drizzle-kit --config ./server/db/drizzle.config.ts",
"db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
"db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
"lint": "biome lint",
"db:seed": "tsx -r dotenv/config ./server/db/seed.ts",
"db:clean": "tsx -r dotenv/config ./server/db/reset.ts",
"docker:build": "./docker/build.sh",
"docker:push": "./docker/push.sh",
"docker:build:canary": "./docker/build.sh canary",
"docker:push:canary": "./docker/push.sh canary",
"version": "echo $(node -p \"require('./package.json').version\")",
"test": "vitest --config __test__/vitest.config.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "3.515.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1",
"@codemirror/language": "^6.10.1",
"@codemirror/legacy-modes": "6.4.0",
"@dokploy/trpc-openapi": "0.0.4",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^3.3.4",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
"@octokit/webhooks": "^13.2.7",
"@radix-ui/react-accordion": "1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.16.0",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@uiw/codemirror-theme-github": "^4.22.1",
"@uiw/react-codemirror": "^4.22.1",
"@xterm/addon-attach": "0.10.0",
"@xterm/xterm": "^5.4.0",
"bcrypt": "5.1.1",
"bl": "6.0.11",
"boxen": "^7.1.1",
"bullmq": "5.4.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"copy-to-clipboard": "^3.3.3",
"copy-webpack-plugin": "^12.0.2",
"date-fns": "3.6.0",
"dockerode": "4.0.2",
"dockerode-compose": "^1.4.0",
"dockerstats": "2.4.2",
"dotenv": "16.4.5",
"drizzle-orm": "^0.30.8",
"drizzle-zod": "0.5.1",
"hi-base32": "^0.5.1",
"input-otp": "^1.2.4",
"js-yaml": "4.1.0",
"k6": "^0.0.0",
"lodash": "4.17.21",
"lucia": "^3.0.1",
"lucide-react": "^0.312.0",
"nanoid": "3",
"next": "^14.1.3",
"next-themes": "^0.2.1",
"node-os-utils": "1.3.7",
"node-pty": "1.0.0",
"node-schedule": "2.1.1",
"octokit": "3.1.2",
"otpauth": "^9.2.3",
"postgres": "3.4.4",
"public-ip": "6.0.2",
"qrcode": "^1.5.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.49.3",
"recharts": "^2.12.3",
"slugify": "^1.6.6",
"sonner": "^1.4.0",
"superjson": "^2.2.1",
"swagger-ui-react": "^5.17.14",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"tar-fs": "3.0.5",
"use-resize-observer": "9.1.0",
"ws": "8.16.0",
"xterm-addon-fit": "^0.8.0",
"zod": "^3.23.4"
},
"devDependencies": {
"@biomejs/biome": "1.7.1",
"@types/bcrypt": "5.0.2",
"@types/dockerode": "3.3.23",
"@types/js-yaml": "4.0.9",
"@types/lodash": "4.17.4",
"@types/node": "^18.17.0",
"@types/node-os-utils": "1.3.4",
"@types/node-schedule": "2.1.6",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/swagger-ui-react": "^4.18.3",
"@types/tar-fs": "2.0.4",
"@types/ws": "8.5.10",
"autoprefixer": "^10.4.14",
"drizzle-kit": "^0.21.1",
"esbuild": "0.20.2",
"localtunnel": "2.0.2",
"postcss": "^8.4.31",
"prettier": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.0",
"typescript": "^5.4.2",
"vite-tsconfig-paths": "4.3.2",
"vitest": "^1.6.0",
"xterm-readline": "1.1.1"
},
"ct3aMetadata": {
"initVersion": "7.25.2"
},
"engines": {
"node": "^18.18.0",
"pnpm": ">=8.15.4"
}
"name": "dokploy",
"version": "v0.3.1",
"private": true,
"license": "Apache-2.0",
"type": "module",
"scripts": {
"build": "npm run build-server && npm run build-next",
"start": "node dist/server.mjs",
"build-server": "tsx esbuild.config.ts",
"build-next": "next build",
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
"reset-password": "node dist/reset-password.mjs",
"dev": "tsx watch -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
"migration:run": "tsx -r dotenv/config migration.ts",
"migration:up": "drizzle-kit up --config ./server/db/drizzle.config.ts",
"migration:drop": "drizzle-kit drop --config ./server/db/drizzle.config.ts",
"db:push": "drizzle-kit --config ./server/db/drizzle.config.ts",
"db:truncate": "tsx -r dotenv/config ./server/db/reset.ts",
"db:studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
"lint": "biome lint",
"db:seed": "tsx -r dotenv/config ./server/db/seed.ts",
"db:clean": "tsx -r dotenv/config ./server/db/reset.ts",
"docker:build": "./docker/build.sh",
"docker:push": "./docker/push.sh",
"docker:build:canary": "./docker/build.sh canary",
"docker:push:canary": "./docker/push.sh canary",
"version": "echo $(node -p \"require('./package.json').version\")",
"test": "vitest --config __test__/vitest.config.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "3.515.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1",
"@codemirror/language": "^6.10.1",
"@codemirror/legacy-modes": "6.4.0",
"@dokploy/trpc-openapi": "0.0.4",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^3.3.4",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
"@octokit/webhooks": "^13.2.7",
"@radix-ui/react-accordion": "1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.16.0",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@uiw/codemirror-theme-github": "^4.22.1",
"@uiw/react-codemirror": "^4.22.1",
"@xterm/addon-attach": "0.10.0",
"@xterm/xterm": "^5.4.0",
"bcrypt": "5.1.1",
"bl": "6.0.11",
"boxen": "^7.1.1",
"bullmq": "5.4.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"copy-to-clipboard": "^3.3.3",
"copy-webpack-plugin": "^12.0.2",
"date-fns": "3.6.0",
"dockerode": "4.0.2",
"dockerode-compose": "^1.4.0",
"dockerstats": "2.4.2",
"dotenv": "16.4.5",
"drizzle-orm": "^0.30.8",
"drizzle-zod": "0.5.1",
"hi-base32": "^0.5.1",
"input-otp": "^1.2.4",
"js-yaml": "4.1.0",
"k6": "^0.0.0",
"lodash": "4.17.21",
"lucia": "^3.0.1",
"lucide-react": "^0.312.0",
"nanoid": "3",
"next": "^14.1.3",
"next-themes": "^0.2.1",
"node-os-utils": "1.3.7",
"node-pty": "1.0.0",
"node-schedule": "2.1.1",
"octokit": "3.1.2",
"otpauth": "^9.2.3",
"postgres": "3.4.4",
"public-ip": "6.0.2",
"qrcode": "^1.5.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.49.3",
"recharts": "^2.12.3",
"slugify": "^1.6.6",
"sonner": "^1.4.0",
"superjson": "^2.2.1",
"swagger-ui-react": "^5.17.14",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"tar-fs": "3.0.5",
"use-resize-observer": "9.1.0",
"ws": "8.16.0",
"xterm-addon-fit": "^0.8.0",
"zod": "^3.23.4",
"nodemailer": "6.9.14"
},
"devDependencies": {
"@biomejs/biome": "1.7.1",
"@types/bcrypt": "5.0.2",
"@types/dockerode": "3.3.23",
"@types/js-yaml": "4.0.9",
"@types/lodash": "4.17.4",
"@types/node": "^18.17.0",
"@types/node-os-utils": "1.3.4",
"@types/node-schedule": "2.1.6",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/swagger-ui-react": "^4.18.3",
"@types/tar-fs": "2.0.4",
"@types/ws": "8.5.10",
"autoprefixer": "^10.4.14",
"drizzle-kit": "^0.21.1",
"esbuild": "0.20.2",
"localtunnel": "2.0.2",
"postcss": "^8.4.31",
"prettier": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.0",
"typescript": "^5.4.2",
"vite-tsconfig-paths": "4.3.2",
"vitest": "^1.6.0",
"xterm-readline": "1.1.1",
"@types/nodemailer": "^6.4.15"
},
"ct3aMetadata": {
"initVersion": "7.25.2"
},
"engines": {
"node": "^18.18.0",
"pnpm": ">=8.15.4"
}
}

17
pnpm-lock.yaml generated
View File

@@ -209,6 +209,9 @@ dependencies:
node-schedule:
specifier: 2.1.1
version: 2.1.1
nodemailer:
specifier: 6.9.14
version: 6.9.14
octokit:
specifier: 3.1.2
version: 3.1.2
@@ -295,6 +298,9 @@ devDependencies:
'@types/node-schedule':
specifier: 2.1.6
version: 2.1.6
'@types/nodemailer':
specifier: ^6.4.15
version: 6.4.15
'@types/qrcode':
specifier: ^1.5.5
version: 1.5.5
@@ -5210,6 +5216,12 @@ packages:
undici-types: 5.26.5
dev: false
/@types/nodemailer@6.4.15:
resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==}
dependencies:
'@types/node': 18.19.24
dev: true
/@types/prop-types@15.7.11:
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
@@ -8306,6 +8318,11 @@ packages:
sorted-array-functions: 1.3.0
dev: false
/nodemailer@6.9.14:
resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==}
engines: {node: '>=6.0.0'}
dev: false
/nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}

View File

@@ -5,15 +5,14 @@ import {
} from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateDestination,
apiCreateDiscord,
apiCreateEmail,
apiCreateSlack,
apiCreateTelegram,
apiFindOneNotification,
apiSendTest,
apiUpdateDestination,
} from "@/server/db/schema";
import { HeadBucketCommand, S3Client } from "@aws-sdk/client-s3";
import { TRPCError } from "@trpc/server";
import { updateDestinationById } from "../services/destination";
import {
@@ -24,6 +23,7 @@ import {
findNotificationById,
removeNotificationById,
} from "../services/notification";
import nodemailer from "nodemailer";
export const notificationRouter = createTRPCRouter({
createSlack: adminProcedure
@@ -32,6 +32,7 @@ export const notificationRouter = createTRPCRouter({
try {
return await createSlackNotification(input);
} catch (error) {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the destination",
@@ -97,30 +98,123 @@ export const notificationRouter = createTRPCRouter({
return notification;
}),
testConnection: adminProcedure
.input(apiCreateDestination)
.input(apiSendTest)
.mutation(async ({ input }) => {
const { secretAccessKey, bucket, region, endpoint, accessKey } = input;
const s3Client = new S3Client({
region: region,
...(endpoint && {
endpoint: endpoint,
}),
credentials: {
accessKeyId: accessKey,
secretAccessKey: secretAccessKey,
},
forcePathStyle: true,
});
const headBucketCommand = new HeadBucketCommand({ Bucket: bucket });
const notificationType = input.notificationType;
console.log(input);
try {
await s3Client.send(headBucketCommand);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to connect to bucket",
cause: error,
});
if (notificationType === "slack") {
// go to your slack dashboard
// go to integrations
// add a new integration
// select incoming webhook
// copy the webhook url
console.log("test slack");
const { webhookUrl, channel } = input;
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);
}
} else if (notificationType === "telegram") {
// start telegram
// search BotFather
// send /newbot
// name
// name-with-bot-at-the-end
// copy the token
// search @userinfobot
// send /start
// copy the Id
const { botToken, chatId } = input;
try {
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: "Test notification",
}),
});
if (!response.ok) {
throw new Error(
`Error sending Telegram notification: ${response.statusText}`,
);
}
console.log("Telegram notification sent successfully");
} catch (error) {
console.error("Error sending Telegram notification:", error);
throw new Error("Error sending Telegram notification");
}
} else if (notificationType === "discord") {
const { webhookUrl } = input;
try {
// 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: "Test notification",
}),
});
if (!response.ok) {
throw new Error(
`Error sending Discord notification: ${response.statusText}`,
);
}
console.log("Discord notification sent successfully");
} catch (error) {
console.error("Error sending Discord notification:", error);
throw new Error("Error sending Discord notification");
}
} else if (notificationType === "email") {
const { smtpServer, smtpPort, username, password, toAddresses } = input;
try {
const transporter = nodemailer.createTransport({
host: smtpServer,
port: smtpPort,
secure: smtpPort === "465",
auth: {
user: username,
pass: password,
},
});
// need to add a valid from address
const fromAddress = "no-reply@emails.dokploy.com";
const mailOptions = {
from: fromAddress,
to: toAddresses?.join(", "),
subject: "Test email",
text: "Test email",
};
await transporter.sendMail(mailOptions);
console.log("Email notification sent successfully");
} catch (error) {
console.error("Error sending Email notification:", error);
throw new Error("Error sending Email notification");
}
}
}),

View File

@@ -45,6 +45,7 @@ export const createSlackNotification = async (
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
notificationType: "slack",
})
.returning()
.then((value) => value[0]);
@@ -90,6 +91,7 @@ export const createTelegramNotification = async (
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
notificationType: "telegram",
})
.returning()
.then((value) => value[0]);
@@ -134,6 +136,7 @@ export const createDiscordNotification = async (
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
notificationType: "discord",
})
.returning()
.then((value) => value[0]);
@@ -182,6 +185,7 @@ export const createEmailNotification = async (
appBuildError: input.appBuildError,
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
notificationType: "email",
})
.returning()
.then((value) => value[0]);
@@ -239,3 +243,22 @@ export const updateDestinationById = async (
return result[0];
};
export const sendNotification = async (
notificationData: Partial<Notification>,
) => {
// 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);
// }
// }
};

View File

@@ -20,7 +20,6 @@ import {
} from "drizzle-orm/pg-core";
import { generateAppName } from "./utils";
import { registry } from "./registry";
import { generatePassword } from "@/templates/utils";
export const sourceType = pgEnum("sourceType", ["docker", "git", "github"]);

View File

@@ -1,9 +1,16 @@
import { nanoid } from "nanoid";
import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { z } from "zod";
import { createInsertSchema } from "drizzle-zod";
export const notificationType = pgEnum("notificationType", [
"slack",
"telegram",
"discord",
"email",
]);
export const notifications = pgTable("notification", {
notificationId: text("notificationId")
.notNull()
@@ -15,6 +22,7 @@ export const notifications = pgTable("notification", {
appBuildError: boolean("appBuildError").notNull().default(false),
databaseBackup: boolean("databaseBackup").notNull().default(false),
dokployRestart: boolean("dokployRestart").notNull().default(false),
notificationType: notificationType("notificationType").notNull(),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -67,6 +75,7 @@ export const email = pgTable("email", {
smtpPort: text("smtpPort").notNull(),
username: text("username").notNull(),
password: text("password").notNull(),
fromAddress: text("fromAddress").notNull(),
toAddresses: text("toAddress").array().notNull(),
});
@@ -149,6 +158,7 @@ export const apiCreateEmail = notificationsSchema
smtpPort: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
fromAddress: z.string().min(1),
toAddresses: z.array(z.string()).min(1),
})
.required();
@@ -158,3 +168,18 @@ export const apiFindOneNotification = notificationsSchema
notificationId: true,
})
.required();
export const apiSendTest = notificationsSchema
.extend({
botToken: z.string(),
chatId: z.string(),
webhookUrl: z.string(),
channel: z.string(),
smtpServer: z.string(),
smtpPort: z.string(),
fromAddress: z.string(),
username: z.string(),
password: z.string(),
toAddresses: z.array(z.string()),
})
.partial();