Skip to content

Commit

Permalink
Chore/refactor openapi generation (#569)
Browse files Browse the repository at this point in the history
This PR does the following:

- refactors the code to generate OpenAPI specs from the NestJS
controllers into a common module that can be shared between services.
- removes the path that automatically re-generated the OpenAPI spec file
every time the service was launched.
- adds the `@nestjs/swagger` CLI plugin. This eliminates the need to
manually generate metadata, so that has been removed from all scripts
- Eliminate manual use of `@ApiProperty()` decorators in _most_ places,
and rely instead on the `@nestjs/swagger` package to infer endpoint
types from the metadata. A notable exception to this is cases where
properties are enums.
- Some type cleanup to make sure the generated OpenAPI spec matches the
actual types being returned as fully as possible
- Added some cleanup to the `CacheMonitorService`; found that registered
listeners on the Redis connections were causing application termination
to hang.

Closes #568

---------

Co-authored-by: Aramik <[email protected]>
  • Loading branch information
JoeCap08055 and aramikm authored Oct 2, 2024
1 parent 2840a33 commit 6dee360
Show file tree
Hide file tree
Showing 84 changed files with 3,686 additions and 3,787 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/common/openapi-build/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ runs:
run: npm ci
shell: bash

- name: Generate Swagger Metadata
run: npm run generate:metadata:${{ inputs.service }}
shell: bash

- name: Generate OpenAPI/Swagger JSON
run: npm run generate:openapi:${{ inputs.service }}
shell: bash
Expand Down
50 changes: 0 additions & 50 deletions apps/account-api/src/build-openapi.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export class AccountsControllerV1 {
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Request to Sign In With Frequency' })
@ApiCreatedResponse({ description: 'Signed in successfully', type: WalletLoginResponseDto })
@ApiBody({ type: WalletLoginRequestDto })
async postSignInWithFrequency(@Body() walletLoginRequest: WalletLoginRequestDto): Promise<WalletLoginResponseDto> {
if (this.config.isDeployedReadOnly && walletLoginRequest.signUp) {
throw new ForbiddenException('New account sign-up unavailable in read-only mode');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '#types/dtos/account';
import { DelegationResponse } from '#types/dtos/account/delegation.response.dto';
import { Body, Controller, Get, HttpCode, HttpStatus, Logger, Param, Post, UseGuards } from '@nestjs/common';
import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { AccountIdDto, MsaIdDto, ProviderMsaIdDto } from '#types/dtos/common';

@Controller({ version: '1', path: 'delegation' })
Expand Down Expand Up @@ -65,7 +65,6 @@ export class DelegationControllerV1 {
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Request to revoke a delegation' })
@ApiCreatedResponse({ description: 'Created and queued request to revoke a delegation', type: TransactionResponse })
@ApiBody({ type: RevokeDelegationPayloadRequestDto })
/**
* Posts a request to revoke a delegation.
*
Expand Down
15 changes: 8 additions & 7 deletions apps/account-api/src/controllers/v1/handles-v1.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
BadRequestException,
NotFoundException,
} from '@nestjs/common';
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { HandlesService } from '#account-api/services/handles.service';
import { EnqueueService } from '#account-lib/services/enqueue-request.service';
import {
Expand Down Expand Up @@ -43,8 +43,7 @@ export class HandlesControllerV1 {
@Post()
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Request to create a new handle for an account' })
@ApiOkResponse({ description: 'Handle creation request enqueued' })
@ApiBody({ type: HandleRequestDto })
@ApiOkResponse({ description: 'Handle creation request enqueued', type: TransactionResponse })
/**
* Creates a handle using the provided query parameters.
* @param queryParams - The query parameters for creating the account.
Expand All @@ -66,8 +65,7 @@ export class HandlesControllerV1 {
@Post('/change')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Request to change a handle' })
@ApiOkResponse({ description: 'Handle change request enqueued' })
@ApiBody({ type: HandleRequestDto })
@ApiOkResponse({ description: 'Handle change request enqueued', type: TransactionResponse })
/**
* Using the provided query parameters, removes the old handle and creates a new one.
* @param queryParams - The query parameters for changing the handle.
Expand All @@ -89,7 +87,10 @@ export class HandlesControllerV1 {
@Get('change/:newHandle')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Get a properly encoded ClaimHandlePayload that can be signed.' })
@ApiOkResponse({ description: 'Returned an encoded ClaimHandlePayload for signing' })
@ApiOkResponse({
description: 'Returned an encoded ClaimHandlePayload for signing',
type: ChangeHandlePayloadRequest,
})
/**
* Using the provided query parameters, creates a new payload that can be signed to change handle.
* @param queryParams - The query parameters for changing the handle.
Expand All @@ -110,7 +111,7 @@ export class HandlesControllerV1 {
@Get(':msaId')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Fetch a handle given an MSA Id' })
@ApiOkResponse({ description: 'Found a handle' })
@ApiOkResponse({ description: 'Found a handle', type: HandleResponseDto })
/**
* Gets a handle for msaId.
* @param queryParams - The msaId for finding the handle.
Expand Down
15 changes: 8 additions & 7 deletions apps/account-api/src/controllers/v1/keys-v1.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Query,
BadRequestException,
} from '@nestjs/common';
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { KeysRequestDto, AddKeyRequestDto } from '#types/dtos/account/keys.request.dto';
import { TransactionResponse } from '#types/dtos/account/transaction.response.dto';
import { KeysResponse } from '#types/dtos/account/keys.response.dto';
Expand Down Expand Up @@ -43,8 +43,7 @@ export class KeysControllerV1 {
@Post('add')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Add new control keys for an MSA Id' })
@ApiOkResponse({ description: 'Found public keys' })
@ApiBody({ type: KeysRequestDto })
@ApiOkResponse({ description: 'Found public keys', type: TransactionResponse })
/**
* Add new control keys for an MSA Id.
* @param queryParams - The query parameters for adding the public keys.
Expand All @@ -66,7 +65,7 @@ export class KeysControllerV1 {
@Get(':msaId')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Fetch public keys given an MSA Id' })
@ApiOkResponse({ description: 'Found public keys' })
@ApiOkResponse({ description: 'Found public keys', type: KeysResponse })
/**
* Gets public keys.
* @param queryParams - The query parameters for getting the public keys.
Expand All @@ -80,7 +79,10 @@ export class KeysControllerV1 {
@Get('publicKeyAgreements/getAddKeyPayload')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Get a properly encoded StatefulStorageItemizedSignaturePayloadV2 that can be signed.' })
@ApiOkResponse({ description: 'Returned an encoded StatefulStorageItemizedSignaturePayloadV2 for signing' })
@ApiOkResponse({
description: 'Returned an encoded StatefulStorageItemizedSignaturePayloadV2 for signing',
type: AddNewPublicKeyAgreementPayloadRequest,
})
/**
* Using the provided query parameters, creates a new payload that can be signed to add new graph keys.
* @param queryParams - The query parameters for adding a new key
Expand All @@ -96,8 +98,7 @@ export class KeysControllerV1 {
@Post('publicKeyAgreements')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Request to add a new public Key' })
@ApiOkResponse({ description: 'Add new key request enqueued' })
@ApiBody({ type: AddNewPublicKeyAgreementRequestDto })
@ApiOkResponse({ description: 'Add new key request enqueued', type: TransactionResponse })
/**
* Using the provided query parameters, adds a new public key for the account
* @param queryParams - The query parameters for adding a new graph key
Expand Down
16 changes: 14 additions & 2 deletions apps/account-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import '@frequency-chain/api-augment';
import { NestFactory } from '@nestjs/core';
import { Logger, ValidationPipe, VersioningType } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { initSwagger } from './swagger_config';
import { ApiModule } from './api.module';
import { TimeoutInterceptor } from '#utils/interceptors/timeout.interceptor';
import { NestExpressApplication } from '@nestjs/platform-express';
import apiConfig, { IAccountApiConfig } from './api.config';
import { generateSwaggerDoc, initializeSwaggerUI, writeOpenApiFile } from '#openapi/openapi';

const logger = new Logger('main');

Expand Down Expand Up @@ -42,6 +42,18 @@ async function bootstrap() {
type: VersioningType.URI,
});

const swaggerDoc = await generateSwaggerDoc(app, {
title: 'Account Service',
description: 'Account Service API',
version: '1.0',
});

const args = process.argv.slice(2);
if (args.find((v) => v === '--writeOpenApi')) {
writeOpenApiFile(swaggerDoc, './openapi-specs/account.openapi.json');
process.exit(0);
}

// Get event emitter & register a shutdown listener
const eventEmitter = app.get<EventEmitter2>(EventEmitter2);
eventEmitter.on('shutdown', async () => {
Expand All @@ -63,7 +75,7 @@ async function bootstrap() {
app.useGlobalInterceptors(new TimeoutInterceptor(config.apiTimeoutMs));
app.useBodyParser('json', { limit: config.apiBodyJsonLimit });

await initSwagger(app, '/docs/swagger');
initializeSwaggerUI(app, swaggerDoc);
logger.log(`Listening on port ${config.apiPort}`);
logger.debug('****** DEBUGGING ENABLED ********');
await app.listen(config.apiPort);
Expand Down
25 changes: 0 additions & 25 deletions apps/account-api/src/metadata.ts

This file was deleted.

30 changes: 0 additions & 30 deletions apps/account-api/src/swagger_config.ts

This file was deleted.

53 changes: 0 additions & 53 deletions apps/content-publishing-api/src/build-openapi.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FilesUploadDto, UploadResponseDto } from '#types/dtos/content-publishing';
import { DSNP_VALID_MIME_TYPES } from '#types/dtos/content-publishing/validation.dto';
import { DSNP_VALID_MIME_TYPES } from '#types/dtos/content-publishing/validation';
import {
Controller,
HttpCode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Body, Controller, HttpCode, Logger, Param, Put } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiService } from '../../api.service';
import { ProfileDto, AnnouncementResponseDto, AssetIncludedRequestDto } from '#types/dtos/content-publishing';
import { AnnouncementTypeName } from '#types/enums';
Expand All @@ -17,7 +17,6 @@ export class ProfileControllerV1 {
@Put(':msaId')
@ApiOperation({ summary: "Update a user's Profile" })
@HttpCode(202)
@ApiResponse({ status: '2XX', type: AnnouncementResponseDto })
async profile(@Param() { msaId }: MsaIdDto, @Body() profileDto: ProfileDto): Promise<AnnouncementResponseDto> {
const metadata = await this.apiService.validateAssetsAndFetchMetadata(profileDto as AssetIncludedRequestDto);
return this.apiService.enqueueRequest(AnnouncementTypeName.PROFILE, msaId, profileDto, metadata);
Expand Down
10 changes: 0 additions & 10 deletions apps/content-publishing-api/src/generate-metadata.ts

This file was deleted.

18 changes: 16 additions & 2 deletions apps/content-publishing-api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { NestFactory } from '@nestjs/core';
import { Logger, ValidationPipe } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ApiModule } from './api.module';
import { initSwagger } from './swagger_config';
import apiConfig, { IContentPublishingApiConfig } from './api.config';
import { TimeoutInterceptor } from '#utils/interceptors/timeout.interceptor';
import { NestExpressApplication } from '@nestjs/platform-express';
import { generateSwaggerDoc, initializeSwaggerUI, writeOpenApiFile } from '#openapi/openapi';

const logger = new Logger('main');

Expand All @@ -32,6 +32,19 @@ async function bootstrap() {
logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error', 'warn', 'log'],
rawBody: true,
});

const swaggerDoc = await generateSwaggerDoc(app, {
title: 'Content Publishing Service API',
description: 'Content Publishing Service API',
version: '1.0',
});

const args = process.argv.slice(2);
if (args.find((v) => v === '--writeOpenApi')) {
writeOpenApiFile(swaggerDoc, './openapi-specs/content-publishing.openapi.json');
process.exit(0);
}

const config = app.get<IContentPublishingApiConfig>(apiConfig.KEY);

// Get event emitter & register a shutdown listener
Expand All @@ -53,7 +66,8 @@ async function bootstrap() {
);
app.useGlobalInterceptors(new TimeoutInterceptor(config.apiTimeoutMs));
app.useBodyParser('json', { limit: config.apiBodyJsonLimit });
await initSwagger(app, '/docs/swagger');

initializeSwaggerUI(app, swaggerDoc);
logger.log(`Listening on port ${config.apiPort}`);
await app.listen(config.apiPort);
} catch (e) {
Expand Down
Loading

0 comments on commit 6dee360

Please sign in to comment.