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

API Docs Ordering and generation command #86

Merged
merged 4 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions apps/api/src/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { CrawlerModule } from '@libs/common/crawler/crawler.module';
import { IPFSProcessorModule } from '@libs/common/ipfs/ipfs.module';
import { PubSubModule } from '@libs/common/pubsub/pubsub.module';
import { ScannerModule } from '@libs/common/scanner/scanner.module';
import { ConfigModule } from '@libs/common/config/config.module'
import { ConfigService } from '@libs/common/config/config.service'
import { ConfigModule } from '@libs/common/config/config.module';
import { ConfigService } from '@libs/common/config/config.service';
import * as QueueConstants from '@libs/common';

@Module({
Expand Down Expand Up @@ -159,7 +159,9 @@ import * as QueueConstants from '@libs/common';
ScheduleModule.forRoot(),
],
providers: [ApiService],
controllers: [HealthController, ScanControllerV1, SearchControllerV1, WebhookControllerV1],
// Controller order determines the order of display for docs
// v[Desc first][ABC Second], Health, and then Dev only last
controllers: [ScanControllerV1, SearchControllerV1, WebhookControllerV1, HealthController],
exports: [],
})
export class ApiModule {}
4 changes: 2 additions & 2 deletions apps/api/src/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export class ApiService {
this.logger = new Logger(this.constructor.name);
}

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

public async setWatchOptions(watchOptions: ChainWatchOptionsDto) {
Expand Down
35 changes: 35 additions & 0 deletions apps/api/src/build-openapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// This is a very hack way to generate the swagger
// without needing any of the service dependencies.
// At some point we should find a better way, but
// there might not be one.

import '@frequency-chain/api-augment';
import * as fs from 'fs';
import { NestFactory } from '@nestjs/core';

// Mock out required env vars before the module loads
process.env.REDIS_URL = 'http://127.0.0.1';
process.env.FREQUENCY_URL = 'http://127.0.0.1';

// eslint-disable-next-line
import { ApiModule } from './api.module';
// eslint-disable-next-line
import { generateSwaggerDoc } from '../../../libs/common/src/config/swagger_config';

async function bootstrap() {
const app = await NestFactory.create(ApiModule, {
logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error'],
});

const document = await generateSwaggerDoc(app);
// write swagger.json to disk
fs.writeFileSync(
'./swagger.json',
JSON.stringify(document, (_, v) => v, 2),
);
console.log('OpenAPI written to ./swagger.json');
// Do NOT call await app.close() as that requires a connection to Redis
process.exit(0);
}

bootstrap();
1 change: 0 additions & 1 deletion apps/api/src/controllers/health.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller()
@ApiTags('health')
export class HealthController {

// Health endpoint
@Get('healthz')
@HttpCode(HttpStatus.OK)
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/controllers/v1/scanner.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Body, Controller, Get, HttpException, HttpStatus, Logger, Post, Query } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
import { ApiService } from '#api/api.service'
import { ApiService } from '#api/api.service';
import { ResetScannerDto } from '@libs/common';
import { ChainWatchOptionsDto } from '@libs/common/dtos/chain.watch.dto';

@Controller('v1/scanner')
@ApiTags('scanner')
@ApiTags('v1/scanner')
export class ScanControllerV1 {
private readonly logger: Logger;

Expand All @@ -14,7 +14,7 @@ export class ScanControllerV1 {
}

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

@Post('options')
@ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs'})
@ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs' })
@ApiBody({
description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds',
type: ChainWatchOptionsDto,
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/controllers/v1/search.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { Body, Controller, HttpStatus, Post } from '@nestjs/common';
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('v1/search')
@ApiTags('search')
@ApiTags('v1/search')
export class SearchControllerV1 {

constructor(private readonly apiService: ApiService) {}

@Post()
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/controllers/v1/webhook.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Body, Controller, Delete, Get, HttpStatus, Logger, Put } from '@nestjs/
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';

@Controller('v1/webhooks')
@ApiTags('webhooks')
@ApiTags('v1/webhooks')
export class WebhookControllerV1 {
private readonly logger: Logger;

Expand Down
140 changes: 133 additions & 7 deletions apps/api/src/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,135 @@
/* eslint-disable */
export default async () => {
const t = {
["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"),
["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"),
["../../../libs/common/src/dtos/chain.watch.dto"]: await import("../../../libs/common/src/dtos/chain.watch.dto")
};
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": {} } }]] } };
};
const t = {
['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'),
['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'),
['../../../libs/common/src/dtos/chain.watch.dto']: await import('../../../libs/common/src/dtos/chain.watch.dto'),
};
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: {} } }],
],
},
};
};
5 changes: 2 additions & 3 deletions libs/common/src/blockchain/chain-event-processor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class ChainEventProcessorService {
public async getMessagesInBlock(blockNumber: number, filter?: ChainWatchOptionsDto): Promise<MessageResponseWithSchemaId[]> {
const blockHash = await this.blockchainService.getBlockHash(blockNumber);
if (blockHash.isEmpty) {
return [];
return [];
}
const apiAt = await this.blockchainService.apiPromise.at(blockHash);
const events = await apiAt.query.system.events();
Expand Down Expand Up @@ -92,7 +92,7 @@ export class ChainEventProcessorService {
messageResponse.schemaId,
message.cid.unwrap().toString(),
message.index.toNumber(),
requestId
requestId,
);

return {
Expand All @@ -107,5 +107,4 @@ export class ChainEventProcessorService {
await queue.addBulk(jobs);
}
}

}
Loading