From 2d847a889a11a63d6579eca182f1168a828bed5a Mon Sep 17 00:00:00 2001 From: "thong.nguyen5" Date: Wed, 17 Apr 2024 22:03:33 +0700 Subject: [PATCH] CRYP-46 Increase quota when sending webhook --- .../src/ethereum/ethereum.module.ts | 2 + .../src/ethereum/ethereum.service.ts | 5 +++ .../src/modules/project/dto/project.dto.ts | 36 ++++++++++++++++ .../src/modules/project/project.controller.ts | 22 +++++++++- .../src/modules/project/project.service.ts | 43 ++++++++++++++++++- .../src/project/project.module.ts | 3 ++ .../repositories/project.quota.repository.ts | 39 ++++++++++++++++- .../src/project/schemas/project.schema.ts | 3 ++ .../project/services/project.quota.service.ts | 22 ++++++++++ .../src/webhook/webhook.service.ts | 2 +- 10 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 app/libs/shared_modules/src/project/services/project.quota.service.ts diff --git a/app/apps/monitor-service/src/ethereum/ethereum.module.ts b/app/apps/monitor-service/src/ethereum/ethereum.module.ts index 438831d..96cb0ef 100644 --- a/app/apps/monitor-service/src/ethereum/ethereum.module.ts +++ b/app/apps/monitor-service/src/ethereum/ethereum.module.ts @@ -4,6 +4,7 @@ import { Module } from '@nestjs/common'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { EthereumController } from './ethereum.controller'; import { EthereumService } from './ethereum.service'; +import { ProjectModule } from '@app/shared_modules/project/project.module'; @Module({ providers: [EthereumService], @@ -29,6 +30,7 @@ import { EthereumService } from './ethereum.service'; ]), WebhookModule, MonitorModule, + ProjectModule, ], }) export class EthereumModule {} diff --git a/app/apps/monitor-service/src/ethereum/ethereum.service.ts b/app/apps/monitor-service/src/ethereum/ethereum.service.ts index b8c40d3..a01cbd6 100644 --- a/app/apps/monitor-service/src/ethereum/ethereum.service.ts +++ b/app/apps/monitor-service/src/ethereum/ethereum.service.ts @@ -6,6 +6,7 @@ import { MonitoringType, WebhookNotification, } from '@app/shared_modules/monitor/schemas/monitor.schema'; +import { ProjectQuotaService } from '@app/shared_modules/project/services/project.quota.service'; import { WebhookService } from '@app/shared_modules/webhook/webhook.service'; import { SupportedChain } from '@app/utils/supportedChain.util'; import { Inject, Injectable, Logger } from '@nestjs/common'; @@ -32,6 +33,9 @@ export class EthereumService { @Inject() private readonly webhookService: WebhookService; + @Inject() + private readonly projectQuotaService: ProjectQuotaService; + async findEthAddress(address: string): Promise { return this.ethMonitorAddressRepository.findByAddress(address); } @@ -320,6 +324,7 @@ export class EthereumService { this.logger.debug( `Dispatch webhook successfully response: ${JSON.stringify(respone)}`, ); + this.projectQuotaService.increaseUsed(monitor.projectId); } catch (error) { this.logger.error( `Error while sending webhook request to: ${webhook.url}`, diff --git a/app/apps/onebox/src/modules/project/dto/project.dto.ts b/app/apps/onebox/src/modules/project/dto/project.dto.ts index aa67d9f..39b3037 100644 --- a/app/apps/onebox/src/modules/project/dto/project.dto.ts +++ b/app/apps/onebox/src/modules/project/dto/project.dto.ts @@ -1,5 +1,6 @@ import { Project, + ProjectQuota, ProjectStatus, } from '@app/shared_modules/project/schemas/project.schema'; import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger'; @@ -46,6 +47,9 @@ export class ProjectResponseDto { @ApiResponseProperty() dateCreated: Date; + @ApiResponseProperty() + currentQuota: number; + static from(project: Project) { return Builder() .projectId(project.projectId) @@ -59,6 +63,38 @@ export class ProjectResponseDto { .maxAddress(project.maxAddress) .addressCount(project.addressCount) .dateCreated(project.dateCreated) + .currentQuota(project.currentQuota) + .build(); + } +} + +export class ProjectQuotaResponseDto { + @ApiResponseProperty() + projectId: string; + + @ApiResponseProperty() + month: string; + + @ApiResponseProperty() + ownerId: string; + + @ApiResponseProperty() + quota: number; + + @ApiResponseProperty() + used: number; + + @ApiResponseProperty() + dateCreated: Date; + + static from(projectQuota: ProjectQuota): ProjectQuotaResponseDto { + return Builder() + .projectId(projectQuota.projectId) + .month(projectQuota.month) + .ownerId(projectQuota.ownerId) + .quota(projectQuota.quota) + .used(projectQuota.used) + .dateCreated(projectQuota.dateCreated) .build(); } } diff --git a/app/apps/onebox/src/modules/project/project.controller.ts b/app/apps/onebox/src/modules/project/project.controller.ts index 332751e..ca98ff2 100644 --- a/app/apps/onebox/src/modules/project/project.controller.ts +++ b/app/apps/onebox/src/modules/project/project.controller.ts @@ -4,6 +4,7 @@ import { Get, Param, Post, + Query, Req, UseGuards, } from '@nestjs/common'; @@ -17,7 +18,11 @@ import { import { Request } from 'express'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { User } from '../users/schemas/user.schema'; -import { CreateProjectDto, ProjectResponseDto } from './dto/project.dto'; +import { + CreateProjectDto, + ProjectQuotaResponseDto, + ProjectResponseDto, +} from './dto/project.dto'; import { ProjectService } from './project.service'; @ApiTags('Project') @@ -57,4 +62,19 @@ export class ProjectController { ): Promise { return await this.projectService.createProject(req.user as User, body); } + + @ApiOperation({ summary: 'Get project current month quota' }) + @ApiBearerAuth('JWT') + @UseGuards(JwtAuthGuard) + @Get('/quota/current') + @ApiOkResponse({ type: ProjectQuotaResponseDto }) + async getProjectQuotaCurrentMonth( + @Req() req: Request, + @Query('projectId') projectId: string, + ): Promise { + return await this.projectService.getProjectCurrentQuora( + req.user as User, + projectId, + ); + } } diff --git a/app/apps/onebox/src/modules/project/project.service.ts b/app/apps/onebox/src/modules/project/project.service.ts index 3ed128c..4c7cf56 100644 --- a/app/apps/onebox/src/modules/project/project.service.ts +++ b/app/apps/onebox/src/modules/project/project.service.ts @@ -1,9 +1,11 @@ import { ErrorCode } from '@app/global/global.error'; import { ProjectMemberRepository } from '@app/shared_modules/project/repositories/project.member.repository'; +import { ProjectQuotaRepository } from '@app/shared_modules/project/repositories/project.quota.repository'; import { ProjectRepository } from '@app/shared_modules/project/repositories/project.repository'; import { Project, ProjectMember, + ProjectQuota, ProjectRole, ProjectStatus, } from '@app/shared_modules/project/schemas/project.schema'; @@ -11,13 +13,18 @@ import { generateProjectId } from '@app/utils/uuidUtils'; import { Injectable } from '@nestjs/common'; import { Builder } from 'builder-pattern'; import { User } from '../users/schemas/user.schema'; -import { CreateProjectDto, ProjectResponseDto } from './dto/project.dto'; +import { + CreateProjectDto, + ProjectQuotaResponseDto, + ProjectResponseDto, +} from './dto/project.dto'; @Injectable() export class ProjectService { constructor( private readonly projectRepository: ProjectRepository, private readonly projectMemberRepository: ProjectMemberRepository, + private readonly projectQuotaRepository: ProjectQuotaRepository, ) {} async checkProjectPermission( @@ -61,6 +68,7 @@ export class ProjectService { .maxAddress(1000) .addressCount(0) .dateCreated(new Date()) + .currentQuota(1000) .build(); const createdProject = await this.projectRepository.saveProject(project); @@ -77,4 +85,37 @@ export class ProjectService { const projects = await this.projectRepository.listUserProjects(user.userId); return projects.map((project) => ProjectResponseDto.from(project)); } + + async getProjectCurrentQuora( + user: User, + projectId: string, + ): Promise { + const project = await this.projectRepository.findById(projectId); + if (!project) { + throw ErrorCode.PROJECT_NOT_FOUND.asException(); + } + + this.checkProjectPermission(user, project.projectId); + return this.projectQuotaRepository + .getCurrentMonthQuota(projectId) + .then((quota) => { + if (quota) { + return ProjectQuotaResponseDto.from(quota); + } else { + const date = new Date(); + const month = + `${date.getMonth() + 1}`.padStart(2, '0') + `${date.getFullYear()}`; + return ProjectQuotaResponseDto.from( + Builder() + .projectId(project.projectId) + .month(month) + .ownerId(project.ownerId) + .quota(project.currentQuota) + .used(0) + .dateCreated(date) + .build(), + ); + } + }); + } } diff --git a/app/libs/shared_modules/src/project/project.module.ts b/app/libs/shared_modules/src/project/project.module.ts index a44bcef..96a50f2 100644 --- a/app/libs/shared_modules/src/project/project.module.ts +++ b/app/libs/shared_modules/src/project/project.module.ts @@ -4,6 +4,7 @@ import { ProjectProviders } from './project.provider'; import { ProjectMemberRepository } from './repositories/project.member.repository'; import { ProjectQuotaRepository } from './repositories/project.quota.repository'; import { ProjectRepository } from './repositories/project.repository'; +import { ProjectQuotaService } from './services/project.quota.service'; @Module({ imports: [DatabaseModule], @@ -12,12 +13,14 @@ import { ProjectRepository } from './repositories/project.repository'; ProjectRepository, ProjectQuotaRepository, ProjectMemberRepository, + ProjectQuotaService, ], exports: [ ...ProjectProviders, ProjectRepository, ProjectQuotaRepository, ProjectMemberRepository, + ProjectQuotaService, ], }) export class ProjectModule {} diff --git a/app/libs/shared_modules/src/project/repositories/project.quota.repository.ts b/app/libs/shared_modules/src/project/repositories/project.quota.repository.ts index 90feaee..9f983b2 100644 --- a/app/libs/shared_modules/src/project/repositories/project.quota.repository.ts +++ b/app/libs/shared_modules/src/project/repositories/project.quota.repository.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Model } from 'mongoose'; -import { ProjectQuota } from '../schemas/project.schema'; +import { Project, ProjectQuota } from '../schemas/project.schema'; @Injectable() export class ProjectQuotaRepository { @@ -8,4 +8,41 @@ export class ProjectQuotaRepository { @Inject('PROJECT_QUOTA_MODEL') private readonly projectQuotaModel: Model, ) {} + + async increaseUsed(project: Project, used = 1): Promise { + const date = new Date(); + const month = + `${date.getMonth() + 1}`.padStart(2, '0') + `${date.getFullYear()}`; + return this.projectQuotaModel + .updateOne( + { + projectId: project.projectId, + month: month, + }, + { + $inc: { used: used }, + $setOnInsert: { + ownerId: project.ownerId, + quota: project.currentQuota || 0, + dateCreated: date, + }, + }, + { + upsert: true, + }, + ) + .then((result) => result.modifiedCount > 0 || result.upsertedCount > 0); + } + + async getCurrentMonthQuota(projectId: string): Promise { + const date = new Date(); + const month = + `${date.getMonth() + 1}`.padStart(2, '0') + `${date.getFullYear()}`; + return this.projectQuotaModel + .findOne({ + projectId: projectId, + month: month, + }) + .exec(); + } } diff --git a/app/libs/shared_modules/src/project/schemas/project.schema.ts b/app/libs/shared_modules/src/project/schemas/project.schema.ts index d7d1820..46baa47 100644 --- a/app/libs/shared_modules/src/project/schemas/project.schema.ts +++ b/app/libs/shared_modules/src/project/schemas/project.schema.ts @@ -40,6 +40,9 @@ export class Project { @Prop() dateCreated: Date; + + @Prop() + currentQuota: number; } export type ProjectDocument = HydratedDocument; export const ProjectSchema = SchemaFactory.createForClass(Project); diff --git a/app/libs/shared_modules/src/project/services/project.quota.service.ts b/app/libs/shared_modules/src/project/services/project.quota.service.ts new file mode 100644 index 0000000..f64d9d6 --- /dev/null +++ b/app/libs/shared_modules/src/project/services/project.quota.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { ProjectQuotaRepository } from '../repositories/project.quota.repository'; +import { ProjectRepository } from '../repositories/project.repository'; +import { ProjectQuota } from '../schemas/project.schema'; + +@Injectable() +export class ProjectQuotaService { + constructor( + private readonly projectQuotaRepository: ProjectQuotaRepository, + private readonly projectRepository: ProjectRepository, + ) {} + + async increaseUsed(projectId: string, used = 1): Promise { + const project = await this.projectRepository.findById(projectId); + if (!project) return false; + return this.projectQuotaRepository.increaseUsed(project, used); + } + + async getCurrentMonthQuota(projectId: string): Promise { + return this.projectQuotaRepository.getCurrentMonthQuota(projectId); + } +} diff --git a/app/libs/shared_modules/src/webhook/webhook.service.ts b/app/libs/shared_modules/src/webhook/webhook.service.ts index 19b6e29..0f0c45a 100644 --- a/app/libs/shared_modules/src/webhook/webhook.service.ts +++ b/app/libs/shared_modules/src/webhook/webhook.service.ts @@ -316,7 +316,7 @@ export class WebhookServiceResponseDto { valid_status_codes: []; } -interface DispatchWebhookResponse { +export class DispatchWebhookResponse { id: string; webhook_id: string; payload: string;