diff --git a/docsfera.json b/docsfera.json
index 395a0d2..113884b 100644
--- a/docsfera.json
+++ b/docsfera.json
@@ -4700,6 +4700,65 @@
]
}
},
+ "/customers/{id}/links": {
+ "get": {
+ "operationId": "CustomersController_getCustomerLinks",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Customers"
+ ],
+ "security": [
+ {
+ "access-token": []
+ },
+ {
+ "access-token": []
+ }
+ ]
+ },
+ "put": {
+ "operationId": "CustomersController_setCustomerLinks",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "Customers"
+ ],
+ "security": [
+ {
+ "access-token": []
+ },
+ {
+ "access-token": []
+ }
+ ]
+ }
+ },
"/health": {
"get": {
"operationId": "HealthController_check",
@@ -4716,7 +4775,7 @@
}
},
"info": {
- "title": "Maestro - main",
+ "title": "Maestro - feature/customer-links",
"description": "This is the Maestro API",
"version": "1.0.0",
"contact": {}
@@ -4784,6 +4843,12 @@
},
"scheduleLimit": {
"type": "string"
+ },
+ "links": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
}
},
"required": [
@@ -4791,7 +4856,8 @@
"id",
"name",
"tier",
- "scheduleLimit"
+ "scheduleLimit",
+ "links"
]
},
"AuthUser": {
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/customers/customers.controller.ts b/src/modules/customers/customers.controller.ts
new file mode 100644
index 0000000..e2b06f7
--- /dev/null
+++ b/src/modules/customers/customers.controller.ts
@@ -0,0 +1,54 @@
+import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
+import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
+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';
+
+@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)
+ getCustomerLinks(@Param() params): Promise {
+ const { id } = params;
+ this.logger.info('getCustomerLinks', { id });
+ return this.customersService.getLinks(id);
+ }
+
+ @Put(':id/links')
+ @RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
+ setCustomerLinks(@Body() body, @Param() params) {
+ const { id } = params;
+ 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/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',