Skip to content
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
87bb7ce
chore: update to v0.24.10 and fix local development setup
A-D-E Aug 12, 2025
2e8e9be
feat(webhooks): implement core webhook service and database schema
A-D-E Aug 12, 2025
69bff70
feat(webhooks): integrate webhook triggering into deployment service
A-D-E Aug 12, 2025
e018a5d
feat(webhooks): add TRPC API endpoints for webhook management
A-D-E Aug 12, 2025
f25825a
feat(webhooks): add webhook UI components
A-D-E Aug 12, 2025
12a18cb
feat(webhooks): add i18n translation file for webhook UI
A-D-E Aug 12, 2025
f9929fc
feat(webhooks): add i18n translations for multiple languages
A-D-E Aug 12, 2025
3f50a5c
fix(webhooks): add serverSideTranslations to load webhook i18n
A-D-E Aug 12, 2025
5027b05
feat(webhooks): implement core webhook service and database schema
A-D-E Aug 12, 2025
981e765
fix(webhooks): resolve UI issues in webhook components
A-D-E Aug 12, 2025
170f9ba
fix/update gitignore
A-D-E Aug 12, 2025
84f5db2
fix/remove testing files
A-D-E Aug 12, 2025
87c8cc8
Remove test login files from repository
A-D-E Aug 12, 2025
ac097b0
Fix TypeScript build errors in webhook and deployment services
A-D-E Aug 12, 2025
7c7d9ea
chore: add update-server.sh to .gitignore
A-D-E Aug 12, 2025
6065461
fix: revert webhook/deployment services to working state and cleanup …
A-D-E Aug 12, 2025
4b383f9
refactor: complete webhook components restructuring with fixed modal …
A-D-E Aug 13, 2025
d8fea6a
update/ gitignore
A-D-E Aug 13, 2025
2bf0b4f
fix/webhook-form remove unused variable
A-D-E Aug 13, 2025
7861b98
feat: add n8n and Slack webhook templates with enhanced configuration
A-D-E Aug 14, 2025
34ada97
fix/types
A-D-E Aug 14, 2025
04ed123
fix/remove PRD
A-D-E Aug 14, 2025
70192f0
fix/"X-Dokploy-Signature-256", tests
A-D-E Aug 14, 2025
ec3a529
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 14, 2025
66e326b
fix/type error
A-D-E Aug 14, 2025
99a5c21
Merge remote-tracking branch 'refs/remotes/origin/feature/deployment-…
A-D-E Aug 14, 2025
6a7ec71
fix/remove bun.lock, remove md files, update gitignore,
A-D-E Aug 17, 2025
89b873f
fix/revert i18n.ts
A-D-E Aug 17, 2025
2d470ad
fix/revert changes in packages/server/package.json
A-D-E Aug 17, 2025
1c5c734
fix/webhook add ui to compose
A-D-E Aug 17, 2025
5ebb949
feat(webhook): create webhook and webhook_delivery tables with foreig…
Siumauricio Aug 24, 2025
8a9437e
Merge branch 'canary' into feature/deployment-webhooks
Siumauricio Aug 24, 2025
8316222
refactor: consolidate webhook modals into single handle-webhook compo…
A-D-E Aug 31, 2025
e80aeb0
chore: remove deprecated webhook and snapshot SQL files
Siumauricio Sep 6, 2025
2fcc432
Merge branch 'canary' into feature/deployment-webhooks
Siumauricio Sep 6, 2025
4590fb8
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 6, 2025
b6405ee
feat(database): add webhook and webhook_delivery tables with foreign …
Siumauricio Sep 6, 2025
8b33e3b
chore: clean up .gitignore by removing obsolete entries related to we…
Siumauricio Sep 6, 2025
1146577
fefactor componets and fixing bugs
A-D-E Sep 7, 2025
8c37193
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 7, 2025
9002655
fix type error
A-D-E Sep 7, 2025
a757d50
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 7, 2025
e43431e
Fixed: Changed clearDeliveries.isPending to clearDeliveries.isLoading
A-D-E Sep 7, 2025
f78b4f1
chore: remove webhook and webhook_delivery SQL files
Siumauricio Nov 2, 2025
ce778ca
Merge branch 'canary' into feature/deployment-webhooks
Siumauricio Nov 2, 2025
361d91c
feat(database): add webhook and webhook_delivery tables with foreign …
Siumauricio Nov 2, 2025
85bc2e9
refactor(webhooks): streamline webhook handling and remove cancelled …
Siumauricio Nov 2, 2025
46fe741
chore(tests): remove outdated webhook basic validation tests
Siumauricio Nov 2, 2025
29b3bbd
refactor(webhooks): remove 'deployment.cancelled' event and update we…
Siumauricio Nov 2, 2025
1992ea2
refactor(webhooks): further refine webhook schema by removing 'deploy…
Siumauricio Nov 2, 2025
d074f77
chore(database): remove webhook and webhook_delivery SQL files
Siumauricio Nov 9, 2025
b05c57d
Merge branch 'canary' into feature/deployment-webhooks
Siumauricio Nov 9, 2025
c652683
Add SQL migration for webhook and webhook_delivery tables
Siumauricio Nov 9, 2025
bf426ca
Merge upstream/canary into feature/deployment-webhooks
A-D-E Dec 9, 2025
b847da4
Merge upstream/canary into feature/deployment-webhooks
A-D-E Dec 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ yarn-error.log*
*.pem


.db
.db
cleanup.sh
install.sh
.claude
.claude-flow
.hive-mind
.qodo
commit-analyse-feature-deployment-webhooks.md
272 changes: 272 additions & 0 deletions apps/dokploy/__test__/webhook/webhook-basic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import { describe, expect, test } from "vitest";
import { z } from "zod";

// Test the webhook validation schemas directly
describe("Webhook Basic Validation", () => {
// Basic webhook URL validation
const webhookUrlSchema = z.string().url().startsWith("https://");

describe("Webhook URL Validation", () => {
test("should accept valid HTTPS URLs", () => {
const validUrls = [
"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
"https://example.com/webhook",
"https://api.n8n.io/webhook/test",
"https://discord.com/api/webhooks/123456789/abcdefghijklmnop",
];

validUrls.forEach((url) => {
const result = webhookUrlSchema.safeParse(url);
expect(result.success).toBe(true);
});
});

test("should reject non-HTTPS URLs", () => {
const invalidUrls = [
"http://example.com/webhook",
"ftp://example.com/webhook",
"ws://example.com/webhook",
];

invalidUrls.forEach((url) => {
const result = webhookUrlSchema.safeParse(url);
expect(result.success).toBe(false);
});
});

test("should reject invalid URLs", () => {
const invalidUrls = [
"not-a-url",
"",
"just-text",
"https://",
"://example.com",
];

invalidUrls.forEach((url) => {
const result = webhookUrlSchema.safeParse(url);
expect(result.success).toBe(false);
});
});
});

// Basic webhook event validation
const webhookEventSchema = z.enum([
"deployment.started",
"deployment.success",
"deployment.failed",
"deployment.cancelled",
]);

describe("Webhook Event Validation", () => {
test("should accept valid deployment events", () => {
const validEvents = [
"deployment.started",
"deployment.success",
"deployment.failed",
"deployment.cancelled",
];

validEvents.forEach((event) => {
const result = webhookEventSchema.safeParse(event);
expect(result.success).toBe(true);
});
});

test("should reject invalid events", () => {
const invalidEvents = [
"deployment.unknown",
"application.started",
"deploy.success",
"",
"random-event",
];

invalidEvents.forEach((event) => {
const result = webhookEventSchema.safeParse(event);
expect(result.success).toBe(false);
});
});
});

// Basic template type validation
const templateTypeSchema = z.enum(["slack", "n8n", "generic"]);

describe("Template Type Validation", () => {
test("should accept valid template types", () => {
const validTypes = ["slack", "n8n", "generic"];

validTypes.forEach((type) => {
const result = templateTypeSchema.safeParse(type);
expect(result.success).toBe(true);
});
});

test("should reject invalid template types", () => {
const invalidTypes = ["discord", "teams", "custom", "", "invalid"];

invalidTypes.forEach((type) => {
const result = templateTypeSchema.safeParse(type);
expect(result.success).toBe(false);
});
});
});

// Basic webhook name validation
const webhookNameSchema = z.string().min(1).max(100);

describe("Webhook Name Validation", () => {
test("should accept valid names", () => {
const validNames = [
"My Webhook",
"Production Slack Alert",
"Dev Notifications",
"a",
"A very long webhook name that should still be valid",
];

validNames.forEach((name) => {
const result = webhookNameSchema.safeParse(name);
expect(result.success).toBe(true);
});
});

test("should reject empty names", () => {
const result = webhookNameSchema.safeParse("");
expect(result.success).toBe(false);
});

test("should reject very long names", () => {
const longName = "a".repeat(101);
const result = webhookNameSchema.safeParse(longName);
expect(result.success).toBe(false);
});
});
});

// Test webhook payload structure
describe("Webhook Payload Structure", () => {
test("should have correct HMAC signature header format", () => {
const signature = "abc123def456";
const headerValue = `sha256=${signature}`;

expect(headerValue).toMatch(/^sha256=[a-f0-9]+$/);
expect(headerValue).toContain("sha256=");
expect(headerValue).toContain(signature);
});

test("should have correct webhook headers structure", () => {
const headers = {
"Content-Type": "application/json",
"X-Dokploy-Event": "deployment.success",
"X-Dokploy-Signature-256": "sha256=abc123def456",
"X-Dokploy-Delivery": "delivery-123",
};

expect(headers).toHaveProperty("Content-Type", "application/json");
expect(headers).toHaveProperty("X-Dokploy-Event");
expect(headers).toHaveProperty("X-Dokploy-Signature-256");
expect(headers).toHaveProperty("X-Dokploy-Delivery");
expect(headers["X-Dokploy-Signature-256"]).toMatch(/^sha256=/);
});

test("should have correct generic payload structure", () => {
const genericPayload = {
event: "deployment.success",
timestamp: new Date().toISOString(),
deployment: {
id: "deploy-123",
status: "success",
startedAt: new Date().toISOString(),
finishedAt: new Date().toISOString(),
duration: 60000,
},
application: {
id: "app-456",
name: "My App",
type: "application",
url: "https://my-app.example.com",
domains: ["my-app.example.com"],
},
project: {
id: "proj-123",
name: "My Project",
},
trigger: {
type: "manual",
triggeredBy: "user",
},
};

expect(genericPayload).toHaveProperty("event");
expect(genericPayload).toHaveProperty("timestamp");
expect(genericPayload).toHaveProperty("deployment");
expect(genericPayload).toHaveProperty("application");
expect(genericPayload).toHaveProperty("project");
expect(genericPayload).toHaveProperty("trigger");
expect(genericPayload.deployment).toHaveProperty("id");
expect(genericPayload.deployment).toHaveProperty("status");
expect(genericPayload.deployment).toHaveProperty("startedAt");
});

test("should have correct slack payload structure", () => {
const slackPayload = {
text: "Deployment Successful βœ…",
attachments: [
{
color: "#36a64f",
fields: [
{ title: "Application", value: "My App", short: true },
{ title: "Status", value: "βœ… Success", short: true },
{ title: "Duration", value: "1m", short: true },
{ title: "Project", value: "My Project", short: true },
],
footer: "Dokploy",
ts: Math.floor(Date.now() / 1000),
},
],
};

expect(slackPayload).toHaveProperty("text");
expect(slackPayload).toHaveProperty("attachments");
expect(slackPayload.attachments[0]!).toHaveProperty("color");
expect(slackPayload.attachments[0]!).toHaveProperty("fields");
expect(Array.isArray(slackPayload.attachments[0]!.fields)).toBe(true);
expect(slackPayload.attachments[0]!.fields).toHaveLength(4);
});

test("should have correct n8n payload structure", () => {
const n8nPayload = {
event: "deployment.success",
data: {
deployment: {
id: "deploy-123",
status: "success",
startedAt: new Date().toISOString(),
finishedAt: new Date().toISOString(),
duration: 60000,
},
application: {
id: "app-456",
name: "My App",
type: "application",
},
project: {
id: "proj-123",
name: "My Project",
},
timestamp: new Date().toISOString(),
},
};

expect(n8nPayload).toHaveProperty("event");
expect(n8nPayload).toHaveProperty("data");
expect(n8nPayload.data).toHaveProperty("deployment");
expect(n8nPayload.data).toHaveProperty("application");
expect(n8nPayload.data).toHaveProperty("project");
expect(n8nPayload.data).toHaveProperty("timestamp");
expect(n8nPayload.data.deployment).toHaveProperty("id");
expect(n8nPayload.data.deployment).toHaveProperty("status");
expect(n8nPayload.data.deployment).toHaveProperty("startedAt");
});
});
Loading