Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

Commit a020f68

Browse files
authored
API Docs Ordering and generation command (#86)
- Add the same swagger generation command from the other services - Add v1 prefix to tags - Reorder to the new standard - `npm run format`
1 parent 7f29079 commit a020f68

File tree

17 files changed

+303
-133
lines changed

17 files changed

+303
-133
lines changed

apps/api/src/api.module.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import { CrawlerModule } from '@libs/common/crawler/crawler.module';
1313
import { IPFSProcessorModule } from '@libs/common/ipfs/ipfs.module';
1414
import { PubSubModule } from '@libs/common/pubsub/pubsub.module';
1515
import { ScannerModule } from '@libs/common/scanner/scanner.module';
16-
import { ConfigModule } from '@libs/common/config/config.module'
17-
import { ConfigService } from '@libs/common/config/config.service'
16+
import { ConfigModule } from '@libs/common/config/config.module';
17+
import { ConfigService } from '@libs/common/config/config.service';
1818
import * as QueueConstants from '@libs/common';
1919

2020
@Module({
@@ -159,7 +159,9 @@ import * as QueueConstants from '@libs/common';
159159
ScheduleModule.forRoot(),
160160
],
161161
providers: [ApiService],
162-
controllers: [HealthController, ScanControllerV1, SearchControllerV1, WebhookControllerV1],
162+
// Controller order determines the order of display for docs
163+
// v[Desc first][ABC Second], Health, and then Dev only last
164+
controllers: [ScanControllerV1, SearchControllerV1, WebhookControllerV1, HealthController],
163165
exports: [],
164166
})
165167
export class ApiModule {}

apps/api/src/api.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ export class ApiService {
2323
this.logger = new Logger(this.constructor.name);
2424
}
2525

26-
public async getWatchOptions(): Promise<ChainWatchOptionsDto|null> {
26+
public async getWatchOptions(): Promise<ChainWatchOptionsDto | null> {
2727
const options = await this.redis.get(EVENTS_TO_WATCH_KEY);
28-
return options ? JSON.parse(options) as ChainWatchOptionsDto : null;
28+
return options ? (JSON.parse(options) as ChainWatchOptionsDto) : null;
2929
}
3030

3131
public async setWatchOptions(watchOptions: ChainWatchOptionsDto) {

apps/api/src/build-openapi.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This is a very hack way to generate the swagger
2+
// without needing any of the service dependencies.
3+
// At some point we should find a better way, but
4+
// there might not be one.
5+
6+
import '@frequency-chain/api-augment';
7+
import * as fs from 'fs';
8+
import { NestFactory } from '@nestjs/core';
9+
10+
// Mock out required env vars before the module loads
11+
process.env.REDIS_URL = 'http://127.0.0.1';
12+
process.env.FREQUENCY_URL = 'http://127.0.0.1';
13+
14+
// eslint-disable-next-line
15+
import { ApiModule } from './api.module';
16+
// eslint-disable-next-line
17+
import { generateSwaggerDoc } from '../../../libs/common/src/config/swagger_config';
18+
19+
async function bootstrap() {
20+
const app = await NestFactory.create(ApiModule, {
21+
logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error'],
22+
});
23+
24+
const document = await generateSwaggerDoc(app);
25+
// write swagger.json to disk
26+
fs.writeFileSync(
27+
'./swagger.json',
28+
JSON.stringify(document, (_, v) => v, 2),
29+
);
30+
console.log('OpenAPI written to ./swagger.json');
31+
// Do NOT call await app.close() as that requires a connection to Redis
32+
process.exit(0);
33+
}
34+
35+
bootstrap();

apps/api/src/controllers/health.controller.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
55
@Controller()
66
@ApiTags('health')
77
export class HealthController {
8-
98
// Health endpoint
109
@Get('healthz')
1110
@HttpCode(HttpStatus.OK)

apps/api/src/controllers/v1/scanner.controller.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Body, Controller, Get, HttpException, HttpStatus, Logger, Post, Query } from '@nestjs/common';
22
import { ApiBody, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
3-
import { ApiService } from '#api/api.service'
3+
import { ApiService } from '#api/api.service';
44
import { ResetScannerDto } from '@libs/common';
55
import { ChainWatchOptionsDto } from '@libs/common/dtos/chain.watch.dto';
66

77
@Controller('v1/scanner')
8-
@ApiTags('scanner')
8+
@ApiTags('v1/scanner')
99
export class ScanControllerV1 {
1010
private readonly logger: Logger;
1111

@@ -14,7 +14,7 @@ export class ScanControllerV1 {
1414
}
1515

1616
@Post('reset')
17-
@ApiOperation({ summary: 'Reset blockchain scan to a specific block number or offset from the current position'})
17+
@ApiOperation({ summary: 'Reset blockchain scan to a specific block number or offset from the current position' })
1818
@ApiBody({
1919
description: 'blockNumber',
2020
type: ResetScannerDto,
@@ -35,7 +35,7 @@ export class ScanControllerV1 {
3535
}
3636

3737
@Post('options')
38-
@ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs'})
38+
@ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs' })
3939
@ApiBody({
4040
description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds',
4141
type: ChainWatchOptionsDto,

apps/api/src/controllers/v1/search.controller.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import { Body, Controller, HttpStatus, Post } from '@nestjs/common';
55
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
66

77
@Controller('v1/search')
8-
@ApiTags('search')
8+
@ApiTags('v1/search')
99
export class SearchControllerV1 {
10-
1110
constructor(private readonly apiService: ApiService) {}
1211

1312
@Post()

apps/api/src/controllers/v1/webhook.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Body, Controller, Delete, Get, HttpStatus, Logger, Put } from '@nestjs/
44
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
55

66
@Controller('v1/webhooks')
7-
@ApiTags('webhooks')
7+
@ApiTags('v1/webhooks')
88
export class WebhookControllerV1 {
99
private readonly logger: Logger;
1010

apps/api/src/metadata.ts

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,135 @@
11
/* eslint-disable */
22
export default async () => {
3-
const t = {
4-
["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"),
5-
["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"),
6-
["../../../libs/common/src/dtos/chain.watch.dto"]: await import("../../../libs/common/src/dtos/chain.watch.dto")
7-
};
8-
return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } }, "ResetScannerDto": { blockNumber: { required: false, type: () => Number, minimum: 1 }, rewindOffset: { required: false, type: () => Number }, immediate: { required: false, type: () => Boolean } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/chain.watch.dto"), { "ChainWatchOptionsDto": { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }], [import("../../../libs/common/src/dtos/request-job.dto"), { "ContentSearchRequestDto": { id: { required: true, type: () => String }, startBlock: { required: true, type: () => Number, minimum: 1 }, endBlock: { required: true, type: () => Number, minimum: 1 }, filters: { required: true, type: () => t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto } } }], [import("../../../libs/common/src/dtos/subscription.webhook.dto"), { "WebhookRegistrationDto": { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }], [import("./controllers/v1/scanner.controller"), { "ScanControllerV1": { "resetScanner": {}, "getWatchOptions": { type: t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto }, "setWatchOptions": {}, "pauseScanner": {}, "startScanner": {} } }], [import("./controllers/v1/search.controller"), { "SearchControllerV1": { "search": {} } }], [import("./controllers/v1/webhook.controller"), { "WebhookControllerV1": { "registerWebhook": {}, "clearAllWebHooks": {}, "getRegisteredWebhooks": {} } }]] } };
9-
};
3+
const t = {
4+
['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'),
5+
['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'),
6+
['../../../libs/common/src/dtos/chain.watch.dto']: await import('../../../libs/common/src/dtos/chain.watch.dto'),
7+
};
8+
return {
9+
'@nestjs/swagger': {
10+
models: [
11+
[
12+
import('../../../libs/common/src/dtos/common.dto'),
13+
{
14+
DsnpUserIdParam: { userDsnpId: { required: true, type: () => String } },
15+
AnnouncementResponseDto: { referenceId: { required: true, type: () => String } },
16+
UploadResponseDto: { assetIds: { required: true, type: () => [String] } },
17+
FilesUploadDto: { files: { required: true, type: () => [Object] } },
18+
ResetScannerDto: {
19+
blockNumber: { required: false, type: () => Number, minimum: 1 },
20+
rewindOffset: { required: false, type: () => Number },
21+
immediate: { required: false, type: () => Boolean },
22+
},
23+
},
24+
],
25+
[
26+
import('../../../libs/common/src/dtos/activity.dto'),
27+
{
28+
LocationDto: {
29+
name: { required: true, type: () => String, minLength: 1 },
30+
accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 },
31+
altitude: { required: false, type: () => Number },
32+
latitude: { required: false, type: () => Number },
33+
longitude: { required: false, type: () => Number },
34+
radius: { required: false, type: () => Number, minimum: 0 },
35+
units: { required: false, enum: t['../../../libs/common/src/dtos/activity.dto'].UnitTypeDto },
36+
},
37+
AssetReferenceDto: {
38+
referenceId: { required: true, type: () => String, minLength: 1 },
39+
height: { required: false, type: () => Number, minimum: 1 },
40+
width: { required: false, type: () => Number, minimum: 1 },
41+
duration: { required: false, type: () => String, pattern: 'DURATION_REGEX' },
42+
},
43+
TagDto: {
44+
type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].TagTypeDto },
45+
name: { required: false, type: () => String, minLength: 1 },
46+
mentionedId: { required: false, type: () => String, minLength: 1, pattern: 'DSNP_USER_URI_REGEX' },
47+
},
48+
AssetDto: {
49+
type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].AttachmentTypeDto },
50+
references: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] },
51+
name: { required: false, type: () => String, minLength: 1 },
52+
href: { required: false, type: () => String, minLength: 1 },
53+
},
54+
BaseActivityDto: {
55+
name: { required: false, type: () => String },
56+
tag: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].TagDto] },
57+
location: { required: false, type: () => t['../../../libs/common/src/dtos/activity.dto'].LocationDto },
58+
},
59+
NoteActivityDto: {
60+
content: { required: true, type: () => String, minLength: 1 },
61+
published: { required: true, type: () => String, pattern: 'ISO8601_REGEX' },
62+
assets: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetDto] },
63+
},
64+
ProfileActivityDto: {
65+
icon: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] },
66+
summary: { required: false, type: () => String },
67+
published: { required: false, type: () => String, pattern: 'ISO8601_REGEX' },
68+
},
69+
},
70+
],
71+
[
72+
import('../../../libs/common/src/dtos/announcement.dto'),
73+
{
74+
BroadcastDto: { content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto } },
75+
ReplyDto: {
76+
inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' },
77+
content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto },
78+
},
79+
TombstoneDto: {
80+
targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' },
81+
targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto },
82+
},
83+
UpdateDto: {
84+
targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' },
85+
targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto },
86+
content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto },
87+
},
88+
ReactionDto: {
89+
emoji: { required: true, type: () => String, minLength: 1, pattern: 'DSNP_EMOJI_REGEX' },
90+
apply: { required: true, type: () => Number, minimum: 0, maximum: 255 },
91+
inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' },
92+
},
93+
ProfileDto: { profile: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].ProfileActivityDto } },
94+
},
95+
],
96+
[
97+
import('../../../libs/common/src/dtos/chain.watch.dto'),
98+
{ ChainWatchOptionsDto: { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } },
99+
],
100+
[
101+
import('../../../libs/common/src/dtos/request-job.dto'),
102+
{
103+
ContentSearchRequestDto: {
104+
id: { required: true, type: () => String },
105+
startBlock: { required: true, type: () => Number, minimum: 1 },
106+
endBlock: { required: true, type: () => Number, minimum: 1 },
107+
filters: { required: true, type: () => t['../../../libs/common/src/dtos/chain.watch.dto'].ChainWatchOptionsDto },
108+
},
109+
},
110+
],
111+
[
112+
import('../../../libs/common/src/dtos/subscription.webhook.dto'),
113+
{ WebhookRegistrationDto: { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } },
114+
],
115+
],
116+
controllers: [
117+
[import('./controllers/health.controller'), { HealthController: { healthz: {}, livez: {}, readyz: {} } }],
118+
[
119+
import('./controllers/v1/scanner.controller'),
120+
{
121+
ScanControllerV1: {
122+
resetScanner: {},
123+
getWatchOptions: { type: t['../../../libs/common/src/dtos/chain.watch.dto'].ChainWatchOptionsDto },
124+
setWatchOptions: {},
125+
pauseScanner: {},
126+
startScanner: {},
127+
},
128+
},
129+
],
130+
[import('./controllers/v1/search.controller'), { SearchControllerV1: { search: {} } }],
131+
[import('./controllers/v1/webhook.controller'), { WebhookControllerV1: { registerWebhook: {}, clearAllWebHooks: {}, getRegisteredWebhooks: {} } }],
132+
],
133+
},
134+
};
135+
};

libs/common/src/blockchain/chain-event-processor.service.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class ChainEventProcessorService {
1616
public async getMessagesInBlock(blockNumber: number, filter?: ChainWatchOptionsDto): Promise<MessageResponseWithSchemaId[]> {
1717
const blockHash = await this.blockchainService.getBlockHash(blockNumber);
1818
if (blockHash.isEmpty) {
19-
return [];
19+
return [];
2020
}
2121
const apiAt = await this.blockchainService.apiPromise.at(blockHash);
2222
const events = await apiAt.query.system.events();
@@ -92,7 +92,7 @@ export class ChainEventProcessorService {
9292
messageResponse.schemaId,
9393
message.cid.unwrap().toString(),
9494
message.index.toNumber(),
95-
requestId
95+
requestId,
9696
);
9797

9898
return {
@@ -107,5 +107,4 @@ export class ChainEventProcessorService {
107107
await queue.addBulk(jobs);
108108
}
109109
}
110-
111110
}

0 commit comments

Comments
 (0)