diff --git a/package-lock.json b/package-lock.json index 7daed3d..3b533d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "@nestjs/schedule": "^1.1.0", "@nestjs/swagger": "^5.2.1", "axios": "^0.25.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", "cron-parser": "^4.4.0", "dadosfera-logs": "^1.0.0-alpha.10", "dotenv": "^14.3.2", @@ -36,7 +38,7 @@ "passport-hubspot-oauth2": "^1.0.3", "passport-mailchimp": "^1.1.0", "protospack": "2.5.2", - "protospack-v2": "3.4.0", + "protospack-v2": "3.8.11", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.5.5", @@ -4237,6 +4239,20 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.13.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", + "dependencies": { + "libphonenumber-js": "^1.9.43", + "validator": "^13.7.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "dev": true, @@ -7823,6 +7839,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.9", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/libphonenumber-js/-/libphonenumber-js-1.10.9.tgz", + "integrity": "sha512-BvPlectKyHuA8xVBU4PerKdI1t+l71F/GCh6CQhe4j5bTwibF6Jcle88kDcyvq4aW55CXxWZg7yIYhzmjgrjkQ==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "dev": true, @@ -8997,9 +9018,9 @@ } }, "node_modules/protospack-v2": { - "version": "3.4.0", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/protospack-v2/-/protospack-v2-3.4.0.tgz", - "integrity": "sha512-KJZR0HtzUaBQnsdWzn4J6IqMUtceeO5gV41hdLrS1EbTKwn2A2EsbcOGQRm4/8kPzU2i4pe9o6zKTR3xCvWavw==", + "version": "3.8.11", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/protospack-v2/-/protospack-v2-3.8.11.tgz", + "integrity": "sha512-xInbwBwbRrir7Pmgdt4Qv7cA5gjfzHRLl/LOR24vfeXiGO8kome+iAUvk68pwsg529XL1fIl1rOMTsSakCMY9A==", "dependencies": { "@grpc/grpc-js": "^1.6.7", "rxjs": "^7.5.5", @@ -10571,6 +10592,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -13970,6 +13999,20 @@ "version": "1.2.2", "dev": true }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "class-validator": { + "version": "0.13.2", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/class-validator/-/class-validator-0.13.2.tgz", + "integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==", + "requires": { + "libphonenumber-js": "^1.9.43", + "validator": "^13.7.0" + } + }, "cli-cursor": { "version": "3.1.0", "dev": true, @@ -16408,6 +16451,11 @@ "type-check": "~0.4.0" } }, + "libphonenumber-js": { + "version": "1.10.9", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/libphonenumber-js/-/libphonenumber-js-1.10.9.tgz", + "integrity": "sha512-BvPlectKyHuA8xVBU4PerKdI1t+l71F/GCh6CQhe4j5bTwibF6Jcle88kDcyvq4aW55CXxWZg7yIYhzmjgrjkQ==" + }, "lines-and-columns": { "version": "1.2.4", "dev": true @@ -17199,9 +17247,9 @@ } }, "protospack-v2": { - "version": "3.4.0", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/protospack-v2/-/protospack-v2-3.4.0.tgz", - "integrity": "sha512-KJZR0HtzUaBQnsdWzn4J6IqMUtceeO5gV41hdLrS1EbTKwn2A2EsbcOGQRm4/8kPzU2i4pe9o6zKTR3xCvWavw==", + "version": "3.8.11", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/protospack-v2/-/protospack-v2-3.8.11.tgz", + "integrity": "sha512-xInbwBwbRrir7Pmgdt4Qv7cA5gjfzHRLl/LOR24vfeXiGO8kome+iAUvk68pwsg529XL1fIl1rOMTsSakCMY9A==", "requires": { "@grpc/grpc-js": "^1.6.7", "rxjs": "^7.5.5", @@ -18243,6 +18291,11 @@ "source-map": "^0.7.3" } }, + "validator": { + "version": "13.7.0", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com:443/npm/dadosfera-npm/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + }, "vary": { "version": "1.1.2" }, diff --git a/package.json b/package.json index 3c2a600..2e69580 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "@nestjs/schedule": "^1.1.0", "@nestjs/swagger": "^5.2.1", "axios": "^0.25.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.13.2", "cron-parser": "^4.4.0", "dadosfera-logs": "^1.0.0-alpha.10", "dotenv": "^14.3.2", @@ -51,7 +53,7 @@ "passport-hubspot-oauth2": "^1.0.3", "passport-mailchimp": "^1.1.0", "protospack": "2.5.2", - "protospack-v2": "3.8.3", + "protospack-v2": "3.8.11", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.5.5", diff --git a/src/app.module.ts b/src/app.module.ts index 3eff0d1..575f6d8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -42,6 +42,7 @@ import { PermissionsModule } from './modules/permissions/permissions.module'; import { DadosferaLogger } from 'dadosfera-logs'; import { TermsOfUseModule } from './modules/termsOfUse/termsOfUse.module'; +import { ConnectionModule } from './modules/connection/connection.module'; const inputClient = new InputsClientConfiguration(); const outputClient = new OutputsClientConfiguration(); @@ -90,6 +91,7 @@ const transformationClient = new TransformationsClientConfiguration(); AuthModule, PermissionsModule, TermsOfUseModule, + ConnectionModule, ClientsModule.register([ { diff --git a/src/modules/connection/client.config.ts b/src/modules/connection/client.config.ts new file mode 100644 index 0000000..66bd812 --- /dev/null +++ b/src/modules/connection/client.config.ts @@ -0,0 +1,32 @@ +import { credentials } from '@grpc/grpc-js'; +import { ClientOptions, Transport } from '@nestjs/microservices'; +import { ConnectionManager } from 'protospack-v2'; + +export class ConnectionClientConfiguration { + config(): ClientOptions { + return { + transport: Transport.GRPC, + options: { + url: process.env.INFACTORY_URL, + package: [ + ConnectionManager.ProtoPackages.WritePackage, + ConnectionManager.ProtoPackages.ReadPackage, + ], + // credentials: undefined, + credentials: process.env.LOCAL_ENV + ? undefined + : credentials.createSsl(), + protoPath: [ + ConnectionManager.ProtoPaths.WriteFilePath, + ConnectionManager.ProtoPaths.ReadFilePath, + ], + loader: { + keepCase: true, + enums: String, + objects: true, + arrays: true, + }, + }, + }; + } +} diff --git a/src/modules/connection/client.service.ts b/src/modules/connection/client.service.ts new file mode 100644 index 0000000..0f8a752 --- /dev/null +++ b/src/modules/connection/client.service.ts @@ -0,0 +1,67 @@ +import { Metadata } from '@grpc/grpc-js'; +import { + OnModuleInit, + Inject, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { ClientGrpc } from '@nestjs/microservices'; +import { ConnectionManager } from 'protospack-v2'; +import { lastValueFrom } from 'rxjs'; + +export class ConnectionClientService implements OnModuleInit { + private connectionServiceRead: ConnectionManager.ReadService.ConnectionManagerReadServices; + private connectionServiceWrite: ConnectionManager.WriteService.ConnectionManagerWriteServices; + constructor( + @Inject('CONNECTION_PACKAGE') private readonly grpcClient: ClientGrpc, + ) {} + + onModuleInit() { + this.connectionServiceWrite = + this.grpcClient.getService( + ConnectionManager.ProtoServices.ConnectionManagerWriteServices, + ); + + this.connectionServiceRead = + this.grpcClient.getService( + ConnectionManager.ProtoServices.ConnectionManagerReadServices, + ); + } + + async createConnection({ body, info }) { + const meta = new Metadata(); + meta.add('customer_id', info.customer_id); + meta.add('customer_name', info.customer_name); + meta.add('user_id', info.user_id); + + const { + connector_id, + description, + image_url, + name, + network_config_id, + properties, + type, + } = body; + + return lastValueFrom( + this.connectionServiceWrite.CreateConnection( + { + connector_id, + description, + image_url, + name, + network_config_id, + properties: JSON.stringify(properties), + type, + }, + meta, + ), + ).catch((err) => { + throw new HttpException( + err.details, + err.code === 6 ? HttpStatus.CONFLICT : 400, + ); + }); + } +} diff --git a/src/modules/connection/connection.controller.ts b/src/modules/connection/connection.controller.ts new file mode 100644 index 0000000..f8b93d3 --- /dev/null +++ b/src/modules/connection/connection.controller.ts @@ -0,0 +1,51 @@ +import { Body, Controller, Post, Inject } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ConnectionClientService } from './client.service'; +import { CreateConnectionDto } from './dtos/create-connection'; +import { DadosferaLogger } from 'dadosfera-logs'; +import { + Authenticated, + RequireAllPermissions, +} from 'src/authentication/authentication.decorator'; +import { Permissions } from 'src/authentication/permissions.enum'; +import { User } from 'src/authentication/user.decorator'; +import { ValidationPipe } from '../../pipes/object-validation.pipe'; + +@ApiTags('connections') +@ApiBearerAuth() +@Authenticated() +@Controller('connections') +export class ConnectionController { + logger: any; + constructor( + @Inject(DadosferaLogger) + dadosferaLogger: DadosferaLogger, + private clientService: ConnectionClientService, + ) { + this.logger = dadosferaLogger.logger; + } + + @Post() + // @RequireAllPermissions(Permissions.CONNECTIONS.CREATE) + async createConnection( + @User() user, + @Body(new ValidationPipe()) body: CreateConnectionDto, + ) { + this.logger.info('/connections - Create Connection'); + + const { user_id, customer_id, customer_name } = user; + + const response = await this.clientService.createConnection({ + body, + info: { user_id, customer_id, customer_name }, + }); + + delete response.connection.properties; + delete response.connection.user_id; + delete response.connection.customer_id; + delete response.connection.customer_name; + delete response.connection.updated_at; + + return response; + } +} diff --git a/src/modules/connection/connection.module.ts b/src/modules/connection/connection.module.ts new file mode 100644 index 0000000..dde02b3 --- /dev/null +++ b/src/modules/connection/connection.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { ClientsModule } from '@nestjs/microservices'; +import { ConnectionClientService } from './client.service'; +import { ConnectionClientConfiguration } from './client.config'; +import { ConnectionController } from './connection.controller'; +import { DadosferaLogger } from 'dadosfera-logs'; + +const client = new ConnectionClientConfiguration(); + +@Module({ + controllers: [ConnectionController], + providers: [ConnectionClientService, DadosferaLogger], + imports: [ + ClientsModule.register([ + { + name: 'CONNECTION_PACKAGE', + ...client.config(), + }, + ]), + ], +}) +export class ConnectionModule {} diff --git a/src/modules/connection/dtos/create-connection.ts b/src/modules/connection/dtos/create-connection.ts new file mode 100644 index 0000000..f5db7b2 --- /dev/null +++ b/src/modules/connection/dtos/create-connection.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsObject } from 'class-validator'; + +export class CreateConnectionDto { + @ApiProperty() + @IsString() + connector_id: string; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + description: string; + + @ApiProperty() + @IsString() + network_config_id: string; + + @ApiProperty() + @IsString() + type: string; + + @ApiProperty() + @IsString() + image_url: string; + + @ApiProperty() + @IsObject() + properties: any; +} diff --git a/src/modules/connector/client.config.ts b/src/modules/connector/client.config.ts index c35707c..40ba762 100644 --- a/src/modules/connector/client.config.ts +++ b/src/modules/connector/client.config.ts @@ -12,7 +12,6 @@ export class ConnectorClientConfiguration { ConnectorManager.ProtoPackages.WritePackage, ConnectorManager.ProtoPackages.ReadPackage, ], - // credentials: undefined, credentials: process.env.LOCAL_ENV ? undefined : credentials.createSsl(), @@ -21,6 +20,7 @@ export class ConnectorClientConfiguration { ConnectorManager.ProtoPaths.ReadFilePath, ], loader: { + keepCase: true, enums: String, objects: true, arrays: true, diff --git a/src/pipes/object-validation.pipe.ts b/src/pipes/object-validation.pipe.ts new file mode 100644 index 0000000..1014581 --- /dev/null +++ b/src/pipes/object-validation.pipe.ts @@ -0,0 +1,29 @@ +import { + PipeTransform, + Injectable, + ArgumentMetadata, + BadRequestException, +} from '@nestjs/common'; +import { validate } from 'class-validator'; +import { plainToInstance } from 'class-transformer'; + +@Injectable() +export class ValidationPipe implements PipeTransform { + async transform(value: any, { metatype }: ArgumentMetadata) { + if (!metatype || !this.toValidate(metatype)) { + return value; + } + const object = plainToInstance(metatype, value); + const errors = await validate(object); + if (errors.length > 0) { + const errorMessages = errors.map((err) => err.constraints); + throw new BadRequestException(errorMessages); + } + return value; + } + + private toValidate(metatype): boolean { + const types = [String, Boolean, Number, Array, Object]; + return !types.includes(metatype); + } +}