Skip to content

Commit 2c10845

Browse files
committed
Fix webhook creation request parsing
1 parent cba49e7 commit 2c10845

2 files changed

Lines changed: 116 additions & 56 deletions

File tree

src/app/api/webhooks/route.js

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,61 @@
1-
import { NextResponse } from 'next/server';
2-
import { withAuth } from '@/lib/api/middleware/auth.js';
3-
4-
/** List webhooks for the authenticated user */
5-
export const GET = withAuth(async (event) => {
6-
const { supabase, user } = event.locals;
7-
8-
const { data, error } = await supabase
9-
.from('webhooks')
10-
.select('id, url, events, created_at')
11-
.eq('user_id', user.id)
12-
.order('created_at', { ascending: false });
13-
14-
if (error) {
15-
console.error('[WEBHOOKS] List error:', error.message);
16-
return NextResponse.json({ error: 'Failed to list webhooks' }, { status: 500 });
17-
}
18-
19-
return NextResponse.json({ webhooks: data });
20-
});
21-
22-
/** Create a new webhook */
23-
export const POST = withAuth(async (event) => {
24-
const { supabase, user } = event.locals;
25-
1+
import { NextResponse } from 'next/server';
2+
import { withAuth } from '@/lib/api/middleware/auth.js';
3+
4+
/** List webhooks for the authenticated user */
5+
export const GET = withAuth(async (event) => {
6+
const { supabase, user } = event.locals;
7+
8+
const { data, error } = await supabase
9+
.from('webhooks')
10+
.select('id, url, events, created_at')
11+
.eq('user_id', user.id)
12+
.order('created_at', { ascending: false });
13+
14+
if (error) {
15+
console.error('[WEBHOOKS] List error:', error.message);
16+
return NextResponse.json({ error: 'Failed to list webhooks' }, { status: 500 });
17+
}
18+
19+
return NextResponse.json({ webhooks: data });
20+
});
21+
22+
/** Create a new webhook */
23+
export const POST = withAuth(async (event) => {
24+
const { supabase, user } = event.locals;
25+
2626
let body;
2727
try {
28-
body = await request.json();
28+
body = await event.request.json();
2929
} catch {
3030
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
3131
}
32-
33-
const { url, events } = body;
34-
35-
if (!url || typeof url !== 'string') {
36-
return NextResponse.json({ error: 'url is required' }, { status: 400 });
37-
}
38-
39-
try {
40-
new URL(url);
41-
} catch {
42-
return NextResponse.json({ error: 'Invalid URL' }, { status: 400 });
43-
}
44-
45-
if (!Array.isArray(events) || events.length === 0) {
46-
return NextResponse.json({ error: 'events must be a non-empty array' }, { status: 400 });
47-
}
48-
49-
const { data, error } = await supabase
50-
.from('webhooks')
51-
.insert({ user_id: user.id, url, events })
52-
.select('id, url, events, created_at')
53-
.single();
54-
55-
if (error) {
56-
console.error('[WEBHOOKS] Create error:', error.message);
57-
return NextResponse.json({ error: 'Failed to create webhook' }, { status: 500 });
58-
}
59-
60-
return NextResponse.json({ webhook: data }, { status: 201 });
61-
});
32+
33+
const { url, events } = body;
34+
35+
if (!url || typeof url !== 'string') {
36+
return NextResponse.json({ error: 'url is required' }, { status: 400 });
37+
}
38+
39+
try {
40+
new URL(url);
41+
} catch {
42+
return NextResponse.json({ error: 'Invalid URL' }, { status: 400 });
43+
}
44+
45+
if (!Array.isArray(events) || events.length === 0) {
46+
return NextResponse.json({ error: 'events must be a non-empty array' }, { status: 400 });
47+
}
48+
49+
const { data, error } = await supabase
50+
.from('webhooks')
51+
.insert({ user_id: user.id, url, events })
52+
.select('id, url, events, created_at')
53+
.single();
54+
55+
if (error) {
56+
console.error('[WEBHOOKS] Create error:', error.message);
57+
return NextResponse.json({ error: 'Failed to create webhook' }, { status: 500 });
58+
}
59+
60+
return NextResponse.json({ webhook: data }, { status: 201 });
61+
});

tests/api/webhooks-route.test.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
3+
const mockSupabase = {
4+
auth: {
5+
getUser: vi.fn(),
6+
},
7+
from: vi.fn(),
8+
};
9+
10+
vi.mock('@/lib/supabase.js', () => ({
11+
createSupabaseServerClient: vi.fn(async () => mockSupabase),
12+
createSupabaseServerClientWithToken: vi.fn(async () => mockSupabase),
13+
}));
14+
15+
import { POST } from '../../src/app/api/webhooks/route.js';
16+
17+
function jsonRequest(body) {
18+
return {
19+
headers: {
20+
get: vi.fn(() => null),
21+
},
22+
json: vi.fn(async () => body),
23+
};
24+
}
25+
26+
describe('POST /api/webhooks', () => {
27+
it('parses the authenticated event request body before creating a webhook', async () => {
28+
mockSupabase.auth.getUser.mockResolvedValue({
29+
data: { user: { id: 'user-123' } },
30+
error: null,
31+
});
32+
33+
const single = vi.fn(async () => ({
34+
data: {
35+
id: 'webhook-123',
36+
url: 'https://example.com/hook',
37+
events: ['message.created'],
38+
created_at: '2026-06-13T00:00:00.000Z',
39+
},
40+
error: null,
41+
}));
42+
const select = vi.fn(() => ({ single }));
43+
const insert = vi.fn(() => ({ select }));
44+
mockSupabase.from.mockReturnValue({ insert });
45+
46+
const response = await POST(jsonRequest({
47+
url: 'https://example.com/hook',
48+
events: ['message.created'],
49+
}));
50+
const body = await response.json();
51+
52+
expect(response.status).toBe(201);
53+
expect(body.webhook.id).toBe('webhook-123');
54+
expect(insert).toHaveBeenCalledWith({
55+
user_id: 'user-123',
56+
url: 'https://example.com/hook',
57+
events: ['message.created'],
58+
});
59+
});
60+
});

0 commit comments

Comments
 (0)