@@ -11,6 +11,8 @@ import {
11
11
} from './open.api.manipulation.component' ;
12
12
import metadata from '../../../../metadata' ;
13
13
import { API_KEY_SWAGGER_SECURITY_NAME , BEARER_SWAGGER_SECURITY_NAME } from '@novu/application-generic' ;
14
+ import { webhookEvents } from '../../../webhooks/webhooks.const' ;
15
+ import { WorkflowResponseDto } from '../../../workflows-v2/dtos/workflow-response.dto' ;
14
16
15
17
export const API_KEY_SECURITY_DEFINITIONS : SecuritySchemeObject = {
16
18
type : 'apiKey' ,
@@ -45,7 +47,7 @@ function buildBaseOptions() {
45
47
)
46
48
. addTag (
47
49
'Subscribers' ,
48
- `A subscriber in Novu represents someone who should receive a message. A subscriber’ s profile information contains important attributes about the subscriber that will be used in messages (name, email). The subscriber object can contain other key-value pairs that can be used to further personalize your messages.` ,
50
+ `A subscriber in Novu represents someone who should receive a message. A subscriber' s profile information contains important attributes about the subscriber that will be used in messages (name, email). The subscriber object can contain other key-value pairs that can be used to further personalize your messages.` ,
49
51
{ url : 'https://docs.novu.co/subscribers/subscribers' }
50
52
)
51
53
. addTag (
@@ -63,6 +65,7 @@ function buildBaseOptions() {
63
65
// `All notifications are sent via a workflow. Each workflow acts as a container for the logic and blueprint that are associated with a type of notification in your system.`,
64
66
// { url: 'https://docs.novu.co/workflows' }
65
67
// )
68
+
66
69
. addTag (
67
70
'Messages' ,
68
71
`A message in Novu represents a notification delivered to a recipient on a particular channel. Messages contain information about the request that triggered its delivery, a view of the data sent to the recipient, and a timeline of its lifecycle events. Learn more about messages.` ,
@@ -82,13 +85,17 @@ function buildOpenApiBaseDocument(internalSdkGeneration: boolean | undefined) {
82
85
}
83
86
84
87
function buildFullDocumentWithPath ( app : INestApplication < any > , baseDocument : Omit < OpenAPIObject , 'paths' > ) {
88
+ // Define extraModels to ensure webhook payload DTOs are included in the schema definitions
89
+ // Add other relevant payload DTOs here if more webhooks are defined
90
+ const allWebhookPayloadDtos = [ ...new Set ( webhookEvents . map ( ( event ) => event . payloadDto ) ) ] ;
91
+
85
92
const document = injectDocumentComponents (
86
93
SwaggerModule . createDocument ( app , baseDocument , {
87
94
operationIdFactory : ( controllerKey : string , methodKey : string ) => `${ controllerKey } _${ methodKey } ` ,
88
95
deepScanRoutes : true ,
89
96
ignoreGlobalPrefix : false ,
90
97
include : [ ] ,
91
- extraModels : [ ] ,
98
+ extraModels : [ ... allWebhookPayloadDtos ] , // Make sure payload DTOs are processed
92
99
} )
93
100
) ;
94
101
return document ;
@@ -112,12 +119,84 @@ function publishLegacyOpenApiDoc(app: INestApplication<any>, document: OpenAPIOb
112
119
} ) ;
113
120
}
114
121
122
+ /**
123
+ * Generates the `x-webhooks` section for the OpenAPI document based on defined events and DTOs.
124
+ * Follows the OpenAPI specification for webhooks: https://spec.openapis.org/oas/v3.1.0#fixed-fields-1:~:text=Webhooks%20Object
125
+ */
126
+ function generateWebhookDefinitions ( document : OpenAPIObject ) {
127
+ const webhooksDefinition : Record < string , any > = { } ; // Structure matches Path Item Object
128
+
129
+ webhookEvents . forEach ( ( webhook ) => {
130
+ // Assume the schema name matches the DTO class name (generated by Swagger)
131
+ const payloadSchemaRef = `#/components/schemas/${ webhook . payloadDto . name } ` ;
132
+ const wrapperSchemaName = `${ webhook . payloadDto . name } WebhookPayloadWrapper` ; // Unique name for the wrapper schema
133
+
134
+ // Define the wrapper schema in components/schemas if it doesn't exist
135
+ if ( document . components && ! document . components . schemas ?. [ wrapperSchemaName ] ) {
136
+ if ( ! document . components . schemas ) {
137
+ document . components . schemas = { } ;
138
+ }
139
+ document . components . schemas [ wrapperSchemaName ] = {
140
+ type : 'object' ,
141
+ properties : {
142
+ type : { type : 'string' , enum : [ webhook . event ] , description : 'The type of the webhook event.' } ,
143
+ data : {
144
+ description : 'The actual event data payload.' ,
145
+ allOf : [ { $ref : payloadSchemaRef } ] , // Use allOf to correctly reference the payload schema
146
+ } ,
147
+ timestamp : { type : 'string' , format : 'date-time' , description : 'ISO timestamp of when the event occurred.' } ,
148
+ environmentId : { type : 'string' , description : 'The ID of the environment associated with the event.' } ,
149
+ object : {
150
+ type : 'string' ,
151
+ enum : [ webhook . objectType ] ,
152
+ description : 'The type of object the event relates to.' ,
153
+ } ,
154
+ } ,
155
+ required : [ 'type' , 'data' , 'timestamp' , 'environmentId' , 'object' ] ,
156
+ } ;
157
+ }
158
+
159
+ webhooksDefinition [ webhook . event ] = {
160
+ // This structure represents a Path Item Object, describing the webhook POST request.
161
+ post : {
162
+ summary : `Event: ${ webhook . event } ` ,
163
+ description : `This webhook is triggered when a \`${ webhook . objectType } \` event (\`${
164
+ webhook . event
165
+ } \`) occurs. The payload contains the details of the event. Configure your webhook endpoint URL in the Novu dashboard.`,
166
+ requestBody : {
167
+ description : `Webhook payload for the \`${ webhook . event } \` event.` ,
168
+ required : true ,
169
+ content : {
170
+ 'application/json' : {
171
+ schema : { $ref : `#/components/schemas/${ wrapperSchemaName } ` } , // Reference the wrapper schema
172
+ } ,
173
+ } ,
174
+ } ,
175
+ responses : {
176
+ '200' : {
177
+ description : 'Acknowledges successful receipt of the webhook. No response body is expected.' ,
178
+ } ,
179
+ // Consider adding other responses (e.g., 4xx for signature validation failure, 5xx for processing errors)
180
+ } ,
181
+ tags : [ 'Webhooks' ] , // Assign to a 'Webhooks' tag
182
+ } ,
183
+ } ;
184
+ } ) ;
185
+
186
+ document [ 'x-webhooks' ] = webhooksDefinition ;
187
+ }
188
+
115
189
export const setupSwagger = async ( app : INestApplication , internalSdkGeneration ?: boolean ) => {
116
190
await SwaggerModule . loadPluginMetadata ( metadata ) ;
117
191
const baseDocument = buildOpenApiBaseDocument ( internalSdkGeneration ) ;
118
192
const document = buildFullDocumentWithPath ( app , baseDocument ) ;
193
+
194
+ // Generate and add x-webhooks section FIRST
195
+ generateWebhookDefinitions ( document ) ;
196
+
119
197
publishDeprecatedDocument ( app , document ) ;
120
198
publishLegacyOpenApiDoc ( app , document ) ;
199
+
121
200
return publishSdkSpecificDocumentAndReturnDocument ( app , document , internalSdkGeneration ) ;
122
201
} ;
123
202
0 commit comments