diff --git a/docsfera.external.json b/docsfera.external.json index ee94532..9af7d01 100644 --- a/docsfera.external.json +++ b/docsfera.external.json @@ -1752,7 +1752,7 @@ } }, "info": { - "title": "Maestro - feat/trigger-catalog-task", + "title": "Maestro - feature/customer-links", "description": "This is the Maestro API", "version": "1.0.0", "contact": {} @@ -1812,6 +1812,12 @@ }, "scheduleLimit": { "type": "string" + }, + "links": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -1819,7 +1825,8 @@ "id", "name", "tier", - "scheduleLimit" + "scheduleLimit", + "links" ] }, "AuthUser": { diff --git a/docsfera.json b/docsfera.json index 47f0d6f..49b938a 100644 --- a/docsfera.json +++ b/docsfera.json @@ -4959,6 +4959,72 @@ ] } }, + "/customers/{id}/links": { + "get": { + "operationId": "CustomersController_getCustomerLinks", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerLinksResponse" + } + } + } + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + }, + "put": { + "operationId": "CustomersController_setCustomerLinks", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Customers" + ], + "security": [ + { + "access-token": [] + }, + { + "access-token": [] + } + ] + } + }, "/health": { "get": { "operationId": "HealthController_check", @@ -4975,7 +5041,7 @@ } }, "info": { - "title": "Maestro - feat/trigger-catalog-task", + "title": "Maestro - feature/customer-links", "description": "This is the Maestro API", "version": "1.0.0", "contact": {} @@ -5030,6 +5096,12 @@ }, "scheduleLimit": { "type": "string" + }, + "links": { + "type": "array", + "items": { + "type": "string" + } } }, "required": [ @@ -5037,7 +5109,8 @@ "id", "name", "tier", - "scheduleLimit" + "scheduleLimit", + "links" ] }, "AuthUser": { @@ -7702,6 +7775,34 @@ "content", "tags" ] + }, + "CustomerLinksResponse": { + "type": "object", + "properties": { + "links": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "links" + ] + }, + "CustomerDto": { + "type": "object", + "properties": { + "links": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "links" + ] } } } diff --git a/package.json b/package.json index cbae592..3aa3413 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.112.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", - "@dadosfera/protospack-v2": "3.30.0", + "@dadosfera/protospack-v2": "3.31.0", "@grpc/grpc-js": "^1.6.7", "@grpc/proto-loader": "^0.6.13", "@nestjs/common": "^8.4.7", diff --git a/src/app.module.ts b/src/app.module.ts index 22ea09a..fa831fd 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -25,6 +25,7 @@ import { ConfigModule } from '@nestjs/config'; import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module'; import { ProductboardModule } from './modules/productboard/productboard.module'; import { MixpanelModule } from './modules/mixpanel/mixpanel.module'; +import { CustomersModule } from './modules/customers/customers.module'; @Module({ controllers: [], @@ -56,6 +57,7 @@ import { MixpanelModule } from './modules/mixpanel/mixpanel.module'; OauthModule, ProductboardModule, MixpanelModule, + CustomersModule, //Always leave HealthModule last, so it is on the bottom of swagger HealthModule, ], diff --git a/src/modules/auth/dtos/login.ts b/src/modules/auth/dtos/login.ts index eae2b64..33a779b 100644 --- a/src/modules/auth/dtos/login.ts +++ b/src/modules/auth/dtos/login.ts @@ -1,3 +1,4 @@ +import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities'; import { AuthSignInRequest, AuthSignInResponse, @@ -74,6 +75,8 @@ export class AuthCustomer { tier: string; @ApiProperty() scheduleLimit: string; + @ApiProperty() + links: Link[]; } export class AuthSignInReq implements AuthSignInRequest { diff --git a/src/modules/customers/customers.controller.ts b/src/modules/customers/customers.controller.ts new file mode 100644 index 0000000..e69d331 --- /dev/null +++ b/src/modules/customers/customers.controller.ts @@ -0,0 +1,57 @@ +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { IdResponse } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; +import { + Body, + Controller, + Get, + Inject, + Param, + Put, + UseFilters, +} from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum'; +import { + Authenticated, + RequireAllPermissions, +} from 'src/decorators/authentication.decorator'; +import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator'; +import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; +import { CustomersService } from './customers.service'; +import { CustomerDto, CustomerLinksResponse } from './dtos/customers'; + +@ApiInternalOnlyController() +@ApiTags('Customers') +@Controller('customers') +@Authenticated() +@UseFilters(GrpcToHttpExceptionFilter) +export class CustomersController { + logger: DadosferaLogger; + + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private customersService: CustomersService, + ) { + this.logger = dadosferaLogger.logger; + } + + @Get(':id/links') + @RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) + async getCustomerLinks(@Param('id') id): Promise { + this.logger.info('getCustomerLinks', { id }); + const links = await this.customersService.getLinks(id); + return { links }; + } + + @Put(':id/links') + @RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) + setCustomerLinks( + @Body() body: CustomerDto, + @Param('id') id, + ): Promise { + const { links } = body; + this.logger.info('setCustomerLinks', { id, links }); + return this.customersService.setLinks(id, links); + } +} diff --git a/src/modules/customers/customers.module.ts b/src/modules/customers/customers.module.ts new file mode 100644 index 0000000..56a285f --- /dev/null +++ b/src/modules/customers/customers.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ClientsModule } from '@nestjs/microservices'; +import { DucClient } from '../duc/client.config'; +import { CustomersController } from './customers.controller'; +import { CustomersService } from './customers.service'; + +const client = new DucClient(); + +@Module({ + imports: [ClientsModule.register([client.providerOptions])], + controllers: [CustomersController], + providers: [CustomersService, DadosferaLogger], + exports: [CustomersService], +}) +export class CustomersModule {} diff --git a/src/modules/customers/customers.service.ts b/src/modules/customers/customers.service.ts new file mode 100644 index 0000000..3b32007 --- /dev/null +++ b/src/modules/customers/customers.service.ts @@ -0,0 +1,63 @@ +import { + OnModuleInit, + Inject, + Injectable, + HttpException, + HttpStatus, +} from '@nestjs/common'; + +import { firstValueFrom, lastValueFrom } from 'rxjs'; +import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities'; +import { DucClient } from '../duc/client.config'; +import { ClientGrpc } from '@nestjs/microservices'; +import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; +import { CustomerUpdateRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; +import { CustomersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; +import ErrorCodes from 'src/utils/errorCodes'; + +@Injectable() +export class CustomersService implements OnModuleInit { + private customerService: CustomersProtoService; + + constructor( + @Inject(DucClient.name) private readonly grpcClient: ClientGrpc, + ) {} + + onModuleInit() { + this.customerService = this.grpcClient.getService( + ProtoServices.CustomersProtoService, + ); + } + + async getLinks(customerId: string) { + try { + const result = await lastValueFrom( + this.customerService.CustomerFindOneById({ id: customerId }), + ); + return result.customer?.links || []; + } catch (err) { + if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) + throw new HttpException(err.details, HttpStatus.NOT_FOUND); + throw err; + } + } + + async setLinks(customerId: string, links: Link[]) { + if (!customerId || !links) { + throw new HttpException(null, HttpStatus.BAD_REQUEST); + } + + try { + return await firstValueFrom( + this.customerService.CustomerUpdate({ + id: customerId, + links, + } as CustomerUpdateRequest), + ); + } catch (err) { + if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) + throw new HttpException(err.details, HttpStatus.NOT_FOUND); + else throw err; + } + } +} diff --git a/src/modules/customers/dtos/customers.ts b/src/modules/customers/dtos/customers.ts new file mode 100644 index 0000000..01337ad --- /dev/null +++ b/src/modules/customers/dtos/customers.ts @@ -0,0 +1,12 @@ +import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities'; +import { ApiProperty } from '@nestjs/swagger'; + +export class CustomerDto { + @ApiProperty() + links: Link[]; +} + +export class CustomerLinksResponse { + @ApiProperty() + links: Link[]; +} diff --git a/src/utils/errorCodes.ts b/src/utils/errorCodes.ts index 91b83d8..7c5ce09 100644 --- a/src/utils/errorCodes.ts +++ b/src/utils/errorCodes.ts @@ -39,6 +39,7 @@ export const CUSTOMER = { INVALID_ID: 'CUSTOMER.INVALID_ID', NOT_FOUND: 'CUSTOMER.NOT_FOUND', ALREADY_EXISTS: 'CUSTOMER.ALREADY_EXISTS', + BAD_REQUEST: 'CUSTOMER.BAD_REQUEST', }; const TERMS_OF_USE = { UP_TO_DATE: 'TERMS_OF_USE.UP_TO_DATE',