Skip to content

Commit

Permalink
fix(api-service): Nv 5379 refinements needed for sdks and api documen…
Browse files Browse the repository at this point in the history
…tation (#7835)

Co-authored-by: George Djabarov <[email protected]>
  • Loading branch information
tatarco and djabarovgeorge authored Mar 11, 2025
1 parent ac3caf8 commit c0c4057
Show file tree
Hide file tree
Showing 26 changed files with 549 additions and 667 deletions.
3 changes: 1 addition & 2 deletions apps/api/src/app/events/events.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
ApiOkResponse,
ApiResponse,
} from '../shared/framework/response.decorator';
import { DataBooleanDto } from '../shared/dtos/data-wrapper-dto';
import { ThrottlerCategory, ThrottlerCost } from '../rate-limiting/guards';
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { SdkGroupName, SdkMethodName, SdkUsageExample } from '../shared/framework/swagger/sdk.decorators';
Expand Down Expand Up @@ -190,7 +189,7 @@ export class EventsController {
@UserAuthentication()
@Delete('/trigger/:transactionId')
@ApiOkResponse({
type: DataBooleanDto,
type: Boolean,
})
@ApiOperation({
summary: 'Cancel triggered event',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Nimma from 'nimma';
import { OpenAPIObject } from '@nestjs/swagger';
import { API_KEY_SWAGGER_SECURITY_NAME } from '@novu/application-generic';
import { OperationObject, PathItemObject, PathsObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';

const jpath = '$.paths..responses["200","201"].content["application/json"]';

Expand Down Expand Up @@ -34,6 +35,7 @@ function liftDataProperty(scope) {
// eslint-disable-next-line no-param-reassign
scope.value.schema = data;
}

export function removeEndpointsWithoutApiKey<T>(openApiDocument: T): T {
const parsedDocument = JSON.parse(JSON.stringify(openApiDocument));

Expand Down Expand Up @@ -88,6 +90,7 @@ export function overloadDocumentForSdkGeneration(inputDocument: OpenAPIObject, i

return addIdempotencyKeyHeader(openAPIObject) as OpenAPIObject;
}

export function addIdempotencyKeyHeader<T>(openApiDocument: T): T {
const parsedDocument = JSON.parse(JSON.stringify(openApiDocument));

Expand Down Expand Up @@ -127,3 +130,95 @@ export function addIdempotencyKeyHeader<T>(openApiDocument: T): T {

return parsedDocument;
}
export function sortOpenAPIDocument(openApiDoc: OpenAPIObject): OpenAPIObject {
// Create a deep copy of the original document
const sortedDoc: OpenAPIObject = JSON.parse(JSON.stringify(openApiDoc));

// Remove empty tag references
if (sortedDoc.tags) {
sortedDoc.tags = sortedDoc.tags.filter((tag) => tag.name && tag.name.trim() !== '');
}

// Sort paths
if (sortedDoc.paths) {
const sortedPaths: PathsObject = {};

// Sort path keys based on version (v2 before v1) and then alphabetically
const sortedPathKeys = Object.keys(sortedDoc.paths).sort((a, b) => {
// Extract version from path
const getVersion = (path: string) => {
const versionMatch = path.match(/\/v(\d+)/);

return versionMatch ? parseInt(versionMatch[1], 10) : 0;
};

const versionA = getVersion(a);
const versionB = getVersion(b);

// Sort by version (newer first)
if (versionA !== versionB) {
return versionB - versionA;
}

// If versions are the same, sort alphabetically
return a.localeCompare(b);
});

// Debugging function to extract operation details
const extractOperationDetails = (method: string, url: string, operation?: OperationObject) => {
if (!operation) return null;

return {
method: method.toUpperCase(),
url,
operationId: operation.operationId || 'N/A',
tags: operation.tags || [],
summary: operation.summary || 'N/A',
};
};

// Debugging array to collect all operations
const debugOperations: any[] = [];

// Reconstruct paths with sorted keys and sorted methods within each path
sortedPathKeys.forEach((pathKey) => {
const pathItem = sortedDoc.paths[pathKey];

// Define method order priority
const methodPriority = ['post', 'put', 'patch', 'get', 'delete', 'options', 'head', 'trace'];

// Collect operations for debugging
methodPriority.forEach((method) => {
const operation = pathItem[method as keyof PathItemObject] as OperationObject | undefined;
const operationDetails = extractOperationDetails(method, pathKey, operation);
if (operationDetails) {
debugOperations.push(operationDetails);
}
});

// Sort methods within the path item
sortedPaths[pathKey] = {
...pathItem,
...Object.fromEntries(
methodPriority
.map((method) => {
const operation = pathItem[method as keyof PathItemObject];

return operation ? [method, operation] : null;
})
.filter((entry): entry is [string, OperationObject] => entry !== null)
.sort((a, b) => {
const opIdA = a[1].operationId || '';
const opIdB = b[1].operationId || '';

return opIdA.localeCompare(opIdB);
})
),
};
});

sortedDoc.paths = sortedPaths;
}

return sortedDoc;
}
19 changes: 12 additions & 7 deletions apps/api/src/app/shared/framework/swagger/swagger.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
/* eslint-disable max-len */
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger';
import { INestApplication } from '@nestjs/common';
import { SecuritySchemeObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
import { injectDocumentComponents } from './injection';
import { overloadDocumentForSdkGeneration, removeEndpointsWithoutApiKey } from './open.api.manipulation.component';
import metadata from '../../../../metadata';
import { API_KEY_SWAGGER_SECURITY_NAME, BEARER_SWAGGER_SECURITY_NAME } from '@novu/application-generic';
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from "@nestjs/swagger";
import { INestApplication } from "@nestjs/common";
import { SecuritySchemeObject } from "@nestjs/swagger/dist/interfaces/open-api-spec.interface";
import { injectDocumentComponents } from "./injection";
import {
overloadDocumentForSdkGeneration,
removeEndpointsWithoutApiKey,
sortOpenAPIDocument
} from "./open.api.manipulation.component";
import metadata from "../../../../metadata";
import { API_KEY_SWAGGER_SECURITY_NAME, BEARER_SWAGGER_SECURITY_NAME } from "@novu/application-generic";

export const API_KEY_SECURITY_DEFINITIONS: SecuritySchemeObject = {
type: 'apiKey',
Expand Down Expand Up @@ -208,6 +212,7 @@ function publishSdkSpecificDocumentAndReturnDocument(
overloadNamingGuidelines(document);
overloadGlobalSdkRetrySettings(document);
let sdkDocument: OpenAPIObject = overloadDocumentForSdkGeneration(document, internalSdkGeneration);
sdkDocument = sortOpenAPIDocument(sdkDocument);
SwaggerModule.setup('openapi.sdk', app, sdkDocument, {
jsonDocumentUrl: 'openapi.sdk.json',
yamlDocumentUrl: 'openapi.sdk.yaml',
Expand Down
4 changes: 2 additions & 2 deletions libs/internal-sdk/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ export const SDK_METADATA = {
language: "typescript",
openapiDocVersion: "1.0",
sdkVersion: "0.1.19",
genVersion: "2.536.0",
userAgent: "speakeasy-sdk/typescript 0.1.19 2.536.0 1.0 @novu/api",
genVersion: "2.545.4",
userAgent: "speakeasy-sdk/typescript 0.1.19 2.545.4 1.0 @novu/api",
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ export type ActivityNotificationJobResponseDtoType = ClosedEnum<
typeof ActivityNotificationJobResponseDtoType
>;

/**
* Optional overrides for the job
*/
export type ActivityNotificationJobResponseDtoOverrides = {};

/**
* Optional payload for the job
*/
Expand Down Expand Up @@ -85,9 +80,9 @@ export type ActivityNotificationJobResponseDto = {
*/
step: ActivityNotificationStepResponseDto;
/**
* Optional overrides for the job
* Optional context object for additional error details.
*/
overrides?: ActivityNotificationJobResponseDtoOverrides | undefined;
overrides?: { [k: string]: any } | undefined;
/**
* Optional payload for the job
*/
Expand Down Expand Up @@ -130,67 +125,6 @@ export namespace ActivityNotificationJobResponseDtoType$ {
ActivityNotificationJobResponseDtoType$outboundSchema;
}

/** @internal */
export const ActivityNotificationJobResponseDtoOverrides$inboundSchema:
z.ZodType<
ActivityNotificationJobResponseDtoOverrides,
z.ZodTypeDef,
unknown
> = z.object({});

/** @internal */
export type ActivityNotificationJobResponseDtoOverrides$Outbound = {};

/** @internal */
export const ActivityNotificationJobResponseDtoOverrides$outboundSchema:
z.ZodType<
ActivityNotificationJobResponseDtoOverrides$Outbound,
z.ZodTypeDef,
ActivityNotificationJobResponseDtoOverrides
> = z.object({});

/**
* @internal
* @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module.
*/
export namespace ActivityNotificationJobResponseDtoOverrides$ {
/** @deprecated use `ActivityNotificationJobResponseDtoOverrides$inboundSchema` instead. */
export const inboundSchema =
ActivityNotificationJobResponseDtoOverrides$inboundSchema;
/** @deprecated use `ActivityNotificationJobResponseDtoOverrides$outboundSchema` instead. */
export const outboundSchema =
ActivityNotificationJobResponseDtoOverrides$outboundSchema;
/** @deprecated use `ActivityNotificationJobResponseDtoOverrides$Outbound` instead. */
export type Outbound = ActivityNotificationJobResponseDtoOverrides$Outbound;
}

export function activityNotificationJobResponseDtoOverridesToJSON(
activityNotificationJobResponseDtoOverrides:
ActivityNotificationJobResponseDtoOverrides,
): string {
return JSON.stringify(
ActivityNotificationJobResponseDtoOverrides$outboundSchema.parse(
activityNotificationJobResponseDtoOverrides,
),
);
}

export function activityNotificationJobResponseDtoOverridesFromJSON(
jsonString: string,
): SafeParseResult<
ActivityNotificationJobResponseDtoOverrides,
SDKValidationError
> {
return safeParse(
jsonString,
(x) =>
ActivityNotificationJobResponseDtoOverrides$inboundSchema.parse(
JSON.parse(x),
),
`Failed to parse 'ActivityNotificationJobResponseDtoOverrides' from JSON`,
);
}

/** @internal */
export const Payload$inboundSchema: z.ZodType<Payload, z.ZodTypeDef, unknown> =
z.object({});
Expand Down Expand Up @@ -245,9 +179,7 @@ export const ActivityNotificationJobResponseDto$inboundSchema: z.ZodType<
ActivityNotificationExecutionDetailResponseDto$inboundSchema,
),
step: ActivityNotificationStepResponseDto$inboundSchema,
overrides: z.lazy(() =>
ActivityNotificationJobResponseDtoOverrides$inboundSchema
).optional(),
overrides: z.record(z.any()).optional(),
payload: z.lazy(() => Payload$inboundSchema).optional(),
providerId: ProvidersIdEnum$inboundSchema,
status: z.string(),
Expand All @@ -267,7 +199,7 @@ export type ActivityNotificationJobResponseDto$Outbound = {
ActivityNotificationExecutionDetailResponseDto$Outbound
>;
step: ActivityNotificationStepResponseDto$Outbound;
overrides?: ActivityNotificationJobResponseDtoOverrides$Outbound | undefined;
overrides?: { [k: string]: any } | undefined;
payload?: Payload$Outbound | undefined;
providerId: string;
status: string;
Expand All @@ -287,9 +219,7 @@ export const ActivityNotificationJobResponseDto$outboundSchema: z.ZodType<
ActivityNotificationExecutionDetailResponseDto$outboundSchema,
),
step: ActivityNotificationStepResponseDto$outboundSchema,
overrides: z.lazy(() =>
ActivityNotificationJobResponseDtoOverrides$outboundSchema
).optional(),
overrides: z.record(z.any()).optional(),
payload: z.lazy(() => Payload$outboundSchema).optional(),
providerId: ProvidersIdEnum$outboundSchema,
status: z.string(),
Expand Down
32 changes: 14 additions & 18 deletions libs/internal-sdk/src/models/components/channelsettingsdto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
/**
* The provider identifier for the credentials
*/
export const ChannelSettingsDtoProviderId = {
export const ProviderId = {
Slack: "slack",
Discord: "discord",
Msteams: "msteams",
Expand All @@ -40,15 +40,13 @@ export const ChannelSettingsDtoProviderId = {
/**
* The provider identifier for the credentials
*/
export type ChannelSettingsDtoProviderId = ClosedEnum<
typeof ChannelSettingsDtoProviderId
>;
export type ProviderId = ClosedEnum<typeof ProviderId>;

export type ChannelSettingsDto = {
/**
* The provider identifier for the credentials
*/
providerId: ChannelSettingsDtoProviderId;
providerId: ProviderId;
/**
* The integration identifier
*/
Expand All @@ -64,24 +62,22 @@ export type ChannelSettingsDto = {
};

/** @internal */
export const ChannelSettingsDtoProviderId$inboundSchema: z.ZodNativeEnum<
typeof ChannelSettingsDtoProviderId
> = z.nativeEnum(ChannelSettingsDtoProviderId);
export const ProviderId$inboundSchema: z.ZodNativeEnum<typeof ProviderId> = z
.nativeEnum(ProviderId);

/** @internal */
export const ChannelSettingsDtoProviderId$outboundSchema: z.ZodNativeEnum<
typeof ChannelSettingsDtoProviderId
> = ChannelSettingsDtoProviderId$inboundSchema;
export const ProviderId$outboundSchema: z.ZodNativeEnum<typeof ProviderId> =
ProviderId$inboundSchema;

/**
* @internal
* @deprecated This namespace will be removed in future versions. Use schemas and types that are exported directly from this module.
*/
export namespace ChannelSettingsDtoProviderId$ {
/** @deprecated use `ChannelSettingsDtoProviderId$inboundSchema` instead. */
export const inboundSchema = ChannelSettingsDtoProviderId$inboundSchema;
/** @deprecated use `ChannelSettingsDtoProviderId$outboundSchema` instead. */
export const outboundSchema = ChannelSettingsDtoProviderId$outboundSchema;
export namespace ProviderId$ {
/** @deprecated use `ProviderId$inboundSchema` instead. */
export const inboundSchema = ProviderId$inboundSchema;
/** @deprecated use `ProviderId$outboundSchema` instead. */
export const outboundSchema = ProviderId$outboundSchema;
}

/** @internal */
Expand All @@ -90,7 +86,7 @@ export const ChannelSettingsDto$inboundSchema: z.ZodType<
z.ZodTypeDef,
unknown
> = z.object({
providerId: ChannelSettingsDtoProviderId$inboundSchema,
providerId: ProviderId$inboundSchema,
integrationIdentifier: z.string().optional(),
credentials: ChannelCredentials$inboundSchema,
_integrationId: z.string(),
Expand All @@ -114,7 +110,7 @@ export const ChannelSettingsDto$outboundSchema: z.ZodType<
z.ZodTypeDef,
ChannelSettingsDto
> = z.object({
providerId: ChannelSettingsDtoProviderId$outboundSchema,
providerId: ProviderId$outboundSchema,
integrationIdentifier: z.string().optional(),
credentials: ChannelCredentials$outboundSchema,
integrationId: z.string(),
Expand Down
Loading

0 comments on commit c0c4057

Please sign in to comment.