Skip to content

Commit 46c0897

Browse files
committed
fix: a
1 parent c89a454 commit 46c0897

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed

apps/api/src/app/shared/framework/swagger/swagger.controller.ts

+81-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
} from './open.api.manipulation.component';
1212
import metadata from '../../../../metadata';
1313
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';
1416

1517
export const API_KEY_SECURITY_DEFINITIONS: SecuritySchemeObject = {
1618
type: 'apiKey',
@@ -45,7 +47,7 @@ function buildBaseOptions() {
4547
)
4648
.addTag(
4749
'Subscribers',
48-
`A subscriber in Novu represents someone who should receive a message. A subscribers 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.`,
4951
{ url: 'https://docs.novu.co/subscribers/subscribers' }
5052
)
5153
.addTag(
@@ -63,6 +65,7 @@ function buildBaseOptions() {
6365
// `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.`,
6466
// { url: 'https://docs.novu.co/workflows' }
6567
// )
68+
6669
.addTag(
6770
'Messages',
6871
`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) {
8285
}
8386

8487
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+
8592
const document = injectDocumentComponents(
8693
SwaggerModule.createDocument(app, baseDocument, {
8794
operationIdFactory: (controllerKey: string, methodKey: string) => `${controllerKey}_${methodKey}`,
8895
deepScanRoutes: true,
8996
ignoreGlobalPrefix: false,
9097
include: [],
91-
extraModels: [],
98+
extraModels: [...allWebhookPayloadDtos], // Make sure payload DTOs are processed
9299
})
93100
);
94101
return document;
@@ -112,12 +119,84 @@ function publishLegacyOpenApiDoc(app: INestApplication<any>, document: OpenAPIOb
112119
});
113120
}
114121

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+
115189
export const setupSwagger = async (app: INestApplication, internalSdkGeneration?: boolean) => {
116190
await SwaggerModule.loadPluginMetadata(metadata);
117191
const baseDocument = buildOpenApiBaseDocument(internalSdkGeneration);
118192
const document = buildFullDocumentWithPath(app, baseDocument);
193+
194+
// Generate and add x-webhooks section FIRST
195+
generateWebhookDefinitions(document);
196+
119197
publishDeprecatedDocument(app, document);
120198
publishLegacyOpenApiDoc(app, document);
199+
121200
return publishSdkSpecificDocumentAndReturnDocument(app, document, internalSdkGeneration);
122201
};
123202

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { WebhookEventEnum, WebhookObjectTypeEnum } from './dtos/webhook-payload.dto';
2+
import { WorkflowResponseDto } from '../workflows-v2/dtos/workflow-response.dto';
3+
4+
export const webhookEvents = [
5+
{
6+
event: WebhookEventEnum.WORKFLOW_UPDATED,
7+
payloadDto: WorkflowResponseDto,
8+
objectType: WebhookObjectTypeEnum.WORKFLOW,
9+
},
10+
{
11+
event: WebhookEventEnum.WORKFLOW_CREATED,
12+
payloadDto: WorkflowResponseDto,
13+
objectType: WebhookObjectTypeEnum.WORKFLOW,
14+
},
15+
{
16+
event: WebhookEventEnum.WORKFLOW_DELETED,
17+
payloadDto: WorkflowResponseDto,
18+
objectType: WebhookObjectTypeEnum.WORKFLOW,
19+
},
20+
// Add other webhook events here as needed
21+
];

0 commit comments

Comments
 (0)