From a9aeeef7f4dc993c4c84433ddc112af7a9c6edbe Mon Sep 17 00:00:00 2001 From: "detail-app[bot]" <180357370+detail-app[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 02:49:27 +0000 Subject: [PATCH] fix(notifications): resolve organization id for self-hosted server threshold alerts --- .../receive-notification.test.ts | 95 +++++++++++++++++++ .../server/api/routers/notification.ts | 13 ++- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 apps/dokploy/__test__/notifications/receive-notification.test.ts diff --git a/apps/dokploy/__test__/notifications/receive-notification.test.ts b/apps/dokploy/__test__/notifications/receive-notification.test.ts new file mode 100644 index 000000000..8f4bf72d5 --- /dev/null +++ b/apps/dokploy/__test__/notifications/receive-notification.test.ts @@ -0,0 +1,95 @@ +import { db } from "@dokploy/server/db"; +import { getWebServerSettings } from "@dokploy/server/services/web-server-settings"; +import { sendServerThresholdNotifications } from "@dokploy/server/utils/notifications/server-threshold"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@dokploy/server/services/web-server-settings", () => ({ + getWebServerSettings: vi.fn(), + updateWebServerSettings: vi.fn(), +})); + +vi.mock("@dokploy/server/utils/notifications/server-threshold", () => ({ + sendServerThresholdNotifications: vi.fn(), +})); + +import { notificationRouter } from "@/server/api/routers/notification"; + +const buildCaller = () => + notificationRouter.createCaller({ + session: null, + db, + req: {} as any, + res: {} as any, + user: null, + } as any); + +const dokployInput = { + ServerType: "Dokploy" as const, + Type: "CPU" as const, + Value: 91.5, + Threshold: 80, + Message: "CPU usage (91.50%) exceeded threshold (80.00%)", + Timestamp: "2026-09-03T10:00:00.000Z", + Token: "server-token", +}; + +const memberFindFirst = () => db.query.member.findFirst as any; + +beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(getWebServerSettings).mockResolvedValue({ + metricsConfig: { server: { token: "server-token" } }, + } as any); + memberFindFirst().mockResolvedValue({ + organizationId: "org-123", + organization: { id: "org-123" }, + }); +}); + +describe("receiveNotification — self-hosted Dokploy branch", () => { + it("resolves the admin's default organization and dispatches with a real organizationId", async () => { + const caller = buildCaller(); + await caller.receiveNotification(dokployInput); + + expect(sendServerThresholdNotifications).toHaveBeenCalledTimes(1); + expect(sendServerThresholdNotifications).toHaveBeenCalledWith( + "org-123", + expect.objectContaining({ + ServerName: "Dokploy", + Type: "CPU", + Value: 91.5, + Threshold: 80, + Token: "server-token", + }), + ); + }); + + it("resolves the default membership with the same query shape as auth.ts session resolution", async () => { + memberFindFirst().mockResolvedValue({ + organizationId: "default-org", + organization: { id: "default-org" }, + }); + + const caller = buildCaller(); + await caller.receiveNotification(dokployInput); + + expect(sendServerThresholdNotifications).toHaveBeenCalledWith( + "default-org", + expect.anything(), + ); + expect(memberFindFirst()).toHaveBeenCalledTimes(1); + const queryOpts = memberFindFirst().mock.calls[0][0]; + expect(queryOpts.orderBy).toHaveLength(2); + expect(queryOpts.with).toEqual({ organization: true }); + }); + + it("throws and does not dispatch when no organization membership exists", async () => { + memberFindFirst().mockResolvedValue(undefined); + + const caller = buildCaller(); + await expect( + caller.receiveNotification(dokployInput), + ).rejects.toMatchObject({ code: "BAD_REQUEST" }); + expect(sendServerThresholdNotifications).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts index ae1c35d12..4df9f1bfb 100644 --- a/apps/dokploy/server/api/routers/notification.ts +++ b/apps/dokploy/server/api/routers/notification.ts @@ -89,6 +89,7 @@ import { apiUpdateSlack, apiUpdateTeams, apiUpdateTelegram, + member, notifications, server, } from "@/server/db/schema"; @@ -517,7 +518,17 @@ export const notificationRouter = createTRPCRouter({ }); } - organizationId = ""; + const activeMember = await db.query.member.findFirst({ + orderBy: [desc(member.isDefault), desc(member.createdAt)], + with: { organization: true }, + }); + if (!activeMember?.organizationId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Organization not found", + }); + } + organizationId = activeMember.organizationId; ServerName = "Dokploy"; } else { const result = await db