Skip to content

Commit cb50f6e

Browse files
committed
feat: refactor Discord notification handling and remove unused files
- Consolidated notification logic by replacing `createMessageFromWebhook` and `sendDiscordNotification` with a single `sendNotifications` function that utilizes multiple providers. - Removed unused `discord.ts` and `embed-factory.ts` files to clean up the codebase. - Introduced a new provider registry to manage notification providers more effectively. - Added a comprehensive formatter for Discord embeds to enhance message formatting based on webhook events.
1 parent 7c84e19 commit cb50f6e

File tree

11 files changed

+182
-229
lines changed

11 files changed

+182
-229
lines changed

apps/web/src/app/api/hook/route.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { after } from "next/server";
22
import { ZodError } from "zod/v4";
33

44
import HttpStatusCode from "@/enums/http-status-codes";
5-
import {
6-
createMessageFromWebhook,
7-
sendDiscordNotification,
8-
} from "@/lib/notify";
5+
import { sendNotifications } from "@/lib/notify";
96
import { checkRateLimit, getClientIp } from "@/lib/ratelimit";
107
import { verifySignature } from "@/lib/verify";
118
import { webhookSchema } from "@/schemas/vercel";
@@ -36,8 +33,7 @@ export async function POST(req: Request) {
3633
const payload = JSON.parse(rawBody) as unknown;
3734
const webhook = webhookSchema.parse(payload);
3835

39-
const embed = createMessageFromWebhook(webhook);
40-
after(() => sendDiscordNotification(embed));
36+
after(() => sendNotifications(webhook));
4137

4238
return Response.json({ success: true, message: "Webhook processed" });
4339
} catch (error) {

apps/web/src/consts/discord.ts

Lines changed: 0 additions & 142 deletions
This file was deleted.

apps/web/src/discord/embed-factory.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

apps/web/src/lib/notify.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
import type { Embed } from "@vermaysha/discord-webhook";
2-
3-
import { createEmbedFromWebhook } from "@/discord/embed-factory";
4-
import { sendWebhook } from "@/discord/webhook-sender";
1+
import { providers } from "@/providers/registry";
52
import type { VercelWebhook } from "@/schemas/vercel";
63

7-
export function createMessageFromWebhook(webhook: VercelWebhook): Embed {
8-
return createEmbedFromWebhook(webhook);
9-
}
10-
11-
export function sendDiscordNotification(embed: Embed): Promise<void> {
12-
return sendWebhook(embed);
4+
export async function sendNotifications(webhook: VercelWebhook): Promise<void> {
5+
await Promise.all(providers.map((provider) => provider.send(webhook)));
136
}
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
11
import { type Embed, Webhook } from "@vermaysha/discord-webhook";
22

3-
import { DEFAULT_AVATAR_URL } from "@/consts/discord";
43
import { env } from "@/env";
5-
6-
const WEBHOOK_CONFIG = {
7-
username: env.DISCORD_WEBHOOK_USERNAME || "Vercord",
8-
avatarUrl: env.DISCORD_WEBHOOK_AVATAR_URL || DEFAULT_AVATAR_URL,
9-
} as const;
4+
import { DEFAULT_AVATAR_URL } from "./consts";
105

116
const RETRY_CONFIG = {
127
maxRetries: 3,
138
rateLimitDelay: 5000,
149
backoffMultiplier: 1000,
1510
} as const;
1611

17-
export async function sendWebhook(embed: Embed): Promise<void> {
12+
export async function sendEmbed(embed: Embed): Promise<void> {
1813
const hook = new Webhook(env.DISCORD_WEBHOOK_URL);
19-
hook.setUsername(WEBHOOK_CONFIG.username);
20-
hook.setAvatarUrl(WEBHOOK_CONFIG.avatarUrl);
14+
hook.setUsername(env.DISCORD_WEBHOOK_USERNAME || "Vercord");
15+
hook.setAvatarUrl(env.DISCORD_WEBHOOK_AVATAR_URL || DEFAULT_AVATAR_URL);
2116
hook.addEmbed(embed);
2217

2318
await sendWithRetry(hook);
2419
}
2520

2621
async function sendWithRetry(hook: Webhook, attempt = 0): Promise<void> {
2722
if (attempt >= RETRY_CONFIG.maxRetries) {
28-
throw new Error("Maximum retry attempts reached");
23+
throw new Error("Discord: maximum retry attempts reached");
2924
}
3025

3126
try {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { WebhookType } from "@/schemas/vercel";
2+
import type { StateProperty } from "./types";
3+
4+
export const DEFAULT_AVATAR_URL =
5+
"https://assets.vercel.com/image/upload/front/favicon/vercel/180x180.png" as const;
6+
7+
export const COLORS = {
8+
PROMOTED: 0xd9_98_e3,
9+
SUCCESS: 0x2e_cc_71,
10+
ERROR: 0xe7_4c_3c,
11+
CANCELED: 0x95_a5_a6,
12+
INFO: 0x34_98_db,
13+
PENDING: 0xf1_c4_0f,
14+
WARNING: 0xff_98_00,
15+
REFUNDED: 0x60_7d_8b,
16+
PAID: 0x4c_af_50,
17+
CONNECTED: 0x21_96_f3,
18+
DISCONNECTED: 0xf4_43_36,
19+
CREATED: 0x8b_c3_4a,
20+
REMOVED: 0xff_57_22,
21+
UPGRADED: 0x9c_27_b0,
22+
CONFIRMED: 0x00_bc_d4,
23+
} as const;
24+
25+
export const EMOJIS = {
26+
PROMOTED: "🔗",
27+
SUCCESS: "✅",
28+
ERROR: "❌",
29+
CANCELED: "🚫",
30+
PENDING: "⏳",
31+
BRANCH: "🌿",
32+
COMMIT: "📝",
33+
PROJECT: "📦",
34+
DEPLOY: "🚀",
35+
REFRESH: "🔄",
36+
CLEANUP: "🧹",
37+
DOMAIN: "🌐",
38+
UPGRADE: "🔼",
39+
DISCONNECT: "🔌",
40+
CONFIRM: "✅",
41+
CONNECT: "🔗",
42+
UNLOCK: "🔓",
43+
INVOICE: "📝",
44+
WARNING: "⚠️",
45+
PAYMENT: "💵",
46+
MONEY: "💸",
47+
NEW: "🆕",
48+
TRASH: "🗑️",
49+
ENV: "🔗",
50+
URL: "🌐",
51+
MESSAGE: "💬",
52+
} as const;
53+
54+
const STATE_MAPPINGS = {
55+
color: {
56+
"deployment.created": COLORS.PENDING,
57+
"deployment.succeeded": COLORS.SUCCESS,
58+
"deployment.ready": COLORS.SUCCESS,
59+
"deployment.promoted": COLORS.PROMOTED,
60+
"deployment.error": COLORS.ERROR,
61+
"deployment.canceled": COLORS.CANCELED,
62+
"deployment.check-rerequested": COLORS.INFO,
63+
"deployment.integration.action.start": COLORS.PENDING,
64+
"deployment.integration.action.cancel": COLORS.CANCELED,
65+
"deployment.integration.action.cleanup": COLORS.INFO,
66+
"domain.created": COLORS.SUCCESS,
67+
"integration-configuration.permission-upgraded": COLORS.UPGRADED,
68+
"integration-configuration.removed": COLORS.ERROR,
69+
"integration-configuration.scope-change-confirmed": COLORS.CONFIRMED,
70+
"integration-resource.project-connected": COLORS.CONNECTED,
71+
"integration-resource.project-disconnected": COLORS.DISCONNECTED,
72+
"marketplace.invoice.created": COLORS.INFO,
73+
"marketplace.invoice.notpaid": COLORS.WARNING,
74+
"marketplace.invoice.paid": COLORS.PAID,
75+
"marketplace.invoice.refunded": COLORS.REFUNDED,
76+
"project.created": COLORS.CREATED,
77+
"project.removed": COLORS.REMOVED,
78+
default: COLORS.INFO,
79+
},
80+
emoji: {
81+
"deployment.created": EMOJIS.PENDING,
82+
"deployment.succeeded": EMOJIS.SUCCESS,
83+
"deployment.ready": EMOJIS.SUCCESS,
84+
"deployment.promoted": EMOJIS.PROMOTED,
85+
"deployment.error": EMOJIS.ERROR,
86+
"deployment.canceled": EMOJIS.CANCELED,
87+
"deployment.check-rerequested": EMOJIS.REFRESH,
88+
"deployment.integration.action.start": EMOJIS.PENDING,
89+
"deployment.integration.action.cancel": EMOJIS.CANCELED,
90+
"deployment.integration.action.cleanup": EMOJIS.CLEANUP,
91+
"domain.created": EMOJIS.DOMAIN,
92+
"integration-configuration.permission-upgraded": EMOJIS.UPGRADE,
93+
"integration-configuration.removed": EMOJIS.DISCONNECT,
94+
"integration-configuration.scope-change-confirmed": EMOJIS.CONFIRM,
95+
"integration-resource.project-connected": EMOJIS.CONNECT,
96+
"integration-resource.project-disconnected": EMOJIS.UNLOCK,
97+
"marketplace.invoice.created": EMOJIS.INVOICE,
98+
"marketplace.invoice.notpaid": EMOJIS.WARNING,
99+
"marketplace.invoice.paid": EMOJIS.PAYMENT,
100+
"marketplace.invoice.refunded": EMOJIS.MONEY,
101+
"project.created": EMOJIS.NEW,
102+
"project.removed": EMOJIS.TRASH,
103+
default: EMOJIS.DEPLOY,
104+
},
105+
} as const;
106+
107+
export function getStateProperty(
108+
type: WebhookType,
109+
property: StateProperty
110+
): number | string {
111+
const mapping = STATE_MAPPINGS[property];
112+
return mapping[type as keyof typeof mapping] ?? mapping.default;
113+
}

0 commit comments

Comments
 (0)