Skip to content

Commit 87cdf01

Browse files
committed
refactor(webhooks): add proper typing to webhook API and frontend components
- Add OpenAPI response decorators to WebhooksAdminController - Regenerate backend client types - Use @shipsec/shared types (WebhookConfiguration, WebhookDelivery) in frontend API service - Fix type mismatches and remove 'any' casts in WebhookEditorPage Signed-off-by: betterclever <[email protected]>
1 parent 977baaf commit 87cdf01

File tree

5 files changed

+447
-70
lines changed

5 files changed

+447
-70
lines changed

backend/src/webhooks/webhooks.admin.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,21 @@ export class WebhooksAdminController {
3535

3636
@Get()
3737
@ApiOperation({ summary: 'List all webhook configurations' })
38+
@ApiOkResponse({ type: [WebhookConfigurationResponseDto] })
3839
async list(@CurrentAuth() auth: AuthContext) {
3940
return this.webhooksService.list(auth);
4041
}
4142

4243
@Get(':id')
4344
@ApiOperation({ summary: 'Get a webhook configuration by ID' })
45+
@ApiOkResponse({ type: WebhookConfigurationResponseDto })
4446
async get(@CurrentAuth() auth: AuthContext, @Param('id') id: string) {
4547
return this.webhooksService.get(auth, id);
4648
}
4749

4850
@Post()
4951
@ApiOperation({ summary: 'Create a new webhook configuration' })
52+
@ApiOkResponse({ type: WebhookConfigurationResponseDto })
5053
async create(
5154
@CurrentAuth() auth: AuthContext,
5255
@Body(new ZodValidationPipe(CreateWebhookRequestDto.schema)) dto: CreateWebhookRequestDto,
@@ -56,6 +59,7 @@ export class WebhooksAdminController {
5659

5760
@Put(':id')
5861
@ApiOperation({ summary: 'Update a webhook configuration' })
62+
@ApiOkResponse({ type: WebhookConfigurationResponseDto })
5963
async update(
6064
@CurrentAuth() auth: AuthContext,
6165
@Param('id') id: string,
@@ -73,12 +77,14 @@ export class WebhooksAdminController {
7377

7478
@Post(':id/regenerate-path')
7579
@ApiOperation({ summary: 'Regenerate webhook path (creates new URL)' })
80+
@ApiOkResponse({ type: RegeneratePathResponseDto })
7681
async regeneratePath(@CurrentAuth() auth: AuthContext, @Param('id') id: string) {
7782
return this.webhooksService.regeneratePath(auth, id);
7883
}
7984

8085
@Get(':id/url')
8186
@ApiOperation({ summary: 'Get the webhook URL for a configuration' })
87+
@ApiOkResponse({ type: GetWebhookUrlResponseDto })
8288
async getUrl(@CurrentAuth() auth: AuthContext, @Param('id') id: string) {
8389
return this.webhooksService.getUrl(auth, id);
8490
}
@@ -95,12 +101,14 @@ export class WebhooksAdminController {
95101

96102
@Get(':id/deliveries')
97103
@ApiOperation({ summary: 'List delivery history for a webhook' })
104+
@ApiOkResponse({ type: [WebhookDeliveryResponseDto] })
98105
async listDeliveries(@CurrentAuth() auth: AuthContext, @Param('id') id: string) {
99106
return this.webhooksService.listDeliveries(auth, id);
100107
}
101108

102109
@Get(':id/deliveries/:deliveryId')
103110
@ApiOperation({ summary: 'Get details of a specific delivery' })
111+
@ApiOkResponse({ type: WebhookDeliveryResponseDto })
104112
async getDelivery(@CurrentAuth() auth: AuthContext, @Param('deliveryId') deliveryId: string) {
105113
return this.webhooksService.getDelivery(auth, deliveryId);
106114
}

frontend/src/pages/WebhookEditorPage.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { useToast } from '@/components/ui/use-toast'
2525
import Editor, { loader } from '@monaco-editor/react'
2626
import { cn } from '@/lib/utils'
2727
import { SaveButton } from '@/components/ui/save-button'
28-
import type { WebhookConfiguration } from '@shipsec/shared'
28+
import type { WebhookConfiguration, WebhookDelivery, WebhookInputDefinition } from '@shipsec/shared'
2929

3030
// Configure Monaco
3131
loader.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/[email protected]/min/vs' } })
@@ -35,13 +35,7 @@ interface WebhookFormState {
3535
name: string
3636
description: string
3737
parsingScript: string
38-
expectedInputs: Array<{
39-
id: string
40-
label: string
41-
type: string
42-
required: boolean
43-
description?: string
44-
}>
38+
expectedInputs: WebhookInputDefinition[]
4539
}
4640

4741
const DEFAULT_PARSING_SCRIPT = `// Transform the incoming webhook payload into workflow inputs
@@ -125,7 +119,7 @@ export function WebhookEditorPage() {
125119
// Resources
126120
const [workflows, setWorkflows] = useState<Array<{ id: string; name: string }>>([])
127121
const [workflowRuntimeInputs, setWorkflowRuntimeInputs] = useState<Array<any>>([])
128-
const [deliveries, setDeliveries] = useState<Array<any>>([])
122+
const [deliveries, setDeliveries] = useState<WebhookDelivery[]>([])
129123
const [isLoadingDeliveries, setIsLoadingDeliveries] = useState(false)
130124

131125
// Load Initial Data
@@ -180,15 +174,15 @@ export function WebhookEditorPage() {
180174

181175
api.workflows.getRuntimeInputs(form.workflowId)
182176
.then(res => {
183-
const inputs = res.inputs || []
184-
setWorkflowRuntimeInputs(inputs)
177+
const runtimeInputs = res.inputs || []
178+
setWorkflowRuntimeInputs(runtimeInputs)
185179

186180
// Automatically populate expectedInputs from workflow runtime inputs
187181
// This ensures the backend validation passes
188-
const mappedInputs = inputs.map((input: any) => ({
182+
const mappedInputs: WebhookInputDefinition[] = runtimeInputs.map((input: any) => ({
189183
id: input.id,
190184
label: input.label || input.id,
191-
type: input.type || 'string',
185+
type: (input.type === 'string' ? 'text' : input.type) || 'text',
192186
required: input.required !== false,
193187
description: input.description || '',
194188
}))
@@ -202,7 +196,7 @@ export function WebhookEditorPage() {
202196
if (isNew || !id) return
203197
setIsLoadingDeliveries(true)
204198
api.webhooks.listDeliveries(id)
205-
.then(data => setDeliveries((data as any) || []))
199+
.then(data => setDeliveries(data || []))
206200
.catch(console.error)
207201
.finally(() => setIsLoadingDeliveries(false))
208202
}, [id, isNew])
@@ -540,7 +534,7 @@ export function WebhookEditorPage() {
540534
<div>Timestamp</div>
541535
<div className="text-right">Action</div>
542536
</div>
543-
{deliveries.map((delivery: any) => (
537+
{deliveries.map((delivery) => (
544538
<div key={delivery.id} className="grid grid-cols-4 gap-4 p-4 border-b last:border-0 items-center text-sm">
545539
<div className="flex items-center gap-2">
546540
{delivery.status === 'delivered' ?

frontend/src/services/api.ts

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type {
44
RunArtifactsResponse,
55
WorkflowSchedule,
66
ScheduleStatus,
7+
WebhookConfiguration,
8+
WebhookDelivery,
9+
TestWebhookScriptResponse,
710
} from '@shipsec/shared'
811
import { useAuthStore } from '@/store/authStore'
912
import { getFreshClerkToken } from '@/utils/clerk-token'
@@ -795,72 +798,49 @@ export const api = {
795798
},
796799

797800
webhooks: {
798-
list: async () => {
801+
list: async (): Promise<WebhookConfiguration[]> => {
799802
const response = await apiClient.listWebhookConfigurations()
800803
if (response.error) throw new Error('Failed to fetch webhook configurations')
801-
return response.data || []
804+
return (response.data || []) as WebhookConfiguration[]
802805
},
803806

804-
get: async (id: string) => {
807+
get: async (id: string): Promise<WebhookConfiguration> => {
805808
const response = await apiClient.getWebhookConfiguration(id)
806809
if (response.error || !response.data) throw new Error('Failed to fetch webhook configuration')
807-
return response.data
810+
return response.data as WebhookConfiguration
808811
},
809812

810-
create: async (payload: {
811-
workflowId: string
812-
name: string
813-
description?: string
814-
parsingScript: string
815-
expectedInputs: Array<{
816-
id: string
817-
label: string
818-
type: string
819-
required: boolean
820-
description?: string
821-
}>
822-
}) => {
813+
create: async (payload: Partial<WebhookConfiguration>): Promise<WebhookConfiguration> => {
823814
const response = await apiClient.createWebhookConfiguration(payload as any)
824815
if (response.error) throw new Error('Failed to create webhook configuration')
825-
return response.data
816+
return response.data as WebhookConfiguration
826817
},
827818

828-
update: async (id: string, payload: {
829-
name?: string
830-
description?: string
831-
parsingScript?: string
832-
expectedInputs?: Array<{
833-
id: string
834-
label: string
835-
type: string
836-
required: boolean
837-
description?: string
838-
}>
839-
}) => {
819+
update: async (id: string, payload: Partial<WebhookConfiguration>): Promise<WebhookConfiguration> => {
840820
const response = await apiClient.updateWebhookConfiguration(id, payload as any)
841821
if (response.error) throw new Error('Failed to update webhook configuration')
842-
return response.data
822+
return response.data as WebhookConfiguration
843823
},
844824

845825
delete: async (id: string) => {
846826
const response = await apiClient.deleteWebhookConfiguration(id)
847827
if (response.error) throw new Error('Failed to delete webhook configuration')
848828
},
849829

850-
testScript: async (payload: { script: string; payload: any; headers: Record<string, string> }) => {
830+
testScript: async (payload: { script: string; payload: any; headers: Record<string, string> }): Promise<TestWebhookScriptResponse> => {
851831
const response = await apiClient.testWebhookScript({
852832
parsingScript: payload.script,
853833
testPayload: payload.payload,
854834
testHeaders: payload.headers,
855835
})
856836
if (response.error) throw new Error('Failed to test webhook script')
857-
return response.data
837+
return response.data as TestWebhookScriptResponse
858838
},
859839

860-
listDeliveries: async (id: string) => {
840+
listDeliveries: async (id: string): Promise<WebhookDelivery[]> => {
861841
const response = await apiClient.listDeliveries(id)
862842
if (response.error) throw new Error('Failed to fetch webhook deliveries')
863-
return response.data
843+
return (response.data || []) as WebhookDelivery[]
864844
},
865845
},
866846

0 commit comments

Comments
 (0)