DOCS: Adicionado Swagger para documentação de rotas da API
Some checks are pending
Deploy Dev / deploy (push) Waiting to run
Some checks are pending
Deploy Dev / deploy (push) Waiting to run
This commit is contained in:
parent
b2aa1a7712
commit
ee966d3306
94
package-lock.json
generated
94
package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@nestjs/core": "^11.1.19",
|
"@nestjs/core": "^11.1.19",
|
||||||
"@nestjs/platform-express": "^11.1.19",
|
"@nestjs/platform-express": "^11.1.19",
|
||||||
"@nestjs/platform-socket.io": "^11.1.21",
|
"@nestjs/platform-socket.io": "^11.1.21",
|
||||||
|
"@nestjs/swagger": "^11.4.4",
|
||||||
"@nestjs/websockets": "^11.1.21",
|
"@nestjs/websockets": "^11.1.21",
|
||||||
"dotenv": "^16.6.1",
|
"dotenv": "^16.6.1",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
@ -21,6 +22,7 @@
|
|||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
"whatsapp-web.js": "^1.34.7",
|
"whatsapp-web.js": "^1.34.7",
|
||||||
"winston": "^3.19.0",
|
"winston": "^3.19.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
@ -719,6 +721,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@microsoft/tsdoc": {
|
||||||
|
"version": "0.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz",
|
||||||
|
"integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nestjs/cli": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "11.0.21",
|
"version": "11.0.21",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.21.tgz",
|
||||||
@ -852,6 +860,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/mapped-types": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-SCCoMEJ6jdeI5h/N+KCVF1+pmg/hmEkNA5nHTS8Gvww7T/LCl4o1gFLinw2iQ60w7slFkszHcGLKGdazVI4F8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||||
|
"class-transformer": "^0.4.0 || ^0.5.0",
|
||||||
|
"class-validator": "^0.13.0 || ^0.14.0 || ^0.15.0",
|
||||||
|
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"class-transformer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"class-validator": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/platform-express": {
|
"node_modules/@nestjs/platform-express": {
|
||||||
"version": "11.1.19",
|
"version": "11.1.19",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.19.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.19.tgz",
|
||||||
@ -917,6 +945,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/swagger": {
|
||||||
|
"version": "11.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.4.4.tgz",
|
||||||
|
"integrity": "sha512-VaIo1ruV2G7b+f2zPzkBSUNy9a/WQ9sg8TLKhWlrTfg4O6U10M/PA7Xi6XMXadOVhwOqoesijba8jH3i/3adrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/tsdoc": "0.16.0",
|
||||||
|
"@nestjs/mapped-types": "2.1.1",
|
||||||
|
"js-yaml": "4.1.1",
|
||||||
|
"lodash": "4.18.1",
|
||||||
|
"path-to-regexp": "8.4.2",
|
||||||
|
"swagger-ui-dist": "5.32.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fastify/static": "^8.0.0 || ^9.0.0",
|
||||||
|
"@nestjs/common": "^11.0.1",
|
||||||
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"class-transformer": "*",
|
||||||
|
"class-validator": "*",
|
||||||
|
"reflect-metadata": "^0.1.12 || ^0.2.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@fastify/static": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"class-transformer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"class-validator": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/websockets": {
|
"node_modules/@nestjs/websockets": {
|
||||||
"version": "11.1.21",
|
"version": "11.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.21.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.21.tgz",
|
||||||
@ -1046,6 +1107,13 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@scarf/scarf": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@so-ric/colorspace": {
|
"node_modules/@so-ric/colorspace": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
|
||||||
@ -3201,6 +3269,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@ -4174,7 +4243,6 @@
|
|||||||
"version": "4.18.1",
|
"version": "4.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
@ -5990,6 +6058,30 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/swagger-ui-dist": {
|
||||||
|
"version": "5.32.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz",
|
||||||
|
"integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@scarf/scarf": "=1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/swagger-ui-express": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"swagger-ui-dist": ">=5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= v0.10.32"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": ">=4.0.0 || >=5.0.0-beta"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/symbol-observable": {
|
"node_modules/symbol-observable": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
"@nestjs/core": "^11.1.19",
|
"@nestjs/core": "^11.1.19",
|
||||||
"@nestjs/platform-express": "^11.1.19",
|
"@nestjs/platform-express": "^11.1.19",
|
||||||
"@nestjs/platform-socket.io": "^11.1.21",
|
"@nestjs/platform-socket.io": "^11.1.21",
|
||||||
|
"@nestjs/swagger": "^11.4.4",
|
||||||
"@nestjs/websockets": "^11.1.21",
|
"@nestjs/websockets": "^11.1.21",
|
||||||
"dotenv": "^16.6.1",
|
"dotenv": "^16.6.1",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
@ -23,6 +24,7 @@
|
|||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
"whatsapp-web.js": "^1.34.7",
|
"whatsapp-web.js": "^1.34.7",
|
||||||
"winston": "^3.19.0",
|
"winston": "^3.19.0",
|
||||||
"winston-daily-rotate-file": "^5.0.0"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@ApiTags('Saude')
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Verifica se a API esta online',
|
||||||
|
description: 'Endpoint simples usado por deploy, monitoramento e testes manuais para confirmar que o backend esta respondendo.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: 'API online.' })
|
||||||
@Get('health')
|
@Get('health')
|
||||||
health() {
|
health() {
|
||||||
return { status: 'ok' };
|
return { status: 'ok' };
|
||||||
|
|||||||
16
src/main.ts
16
src/main.ts
@ -1,6 +1,7 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import { json, urlencoded } from 'express';
|
import { json, urlencoded } from 'express';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { loadEnv } from './infra/config/load-env';
|
import { loadEnv } from './infra/config/load-env';
|
||||||
@ -30,13 +31,26 @@ async function bootstrap() {
|
|||||||
callback(null, true);
|
callback(null, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback(new Error(`Origem CORS nao permitida: ${origin}`));
|
Logger.warn(`Origem CORS bloqueada: ${origin}`, 'Cors');
|
||||||
|
callback(null, false);
|
||||||
},
|
},
|
||||||
credentials: true,
|
credentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const swaggerConfig = new DocumentBuilder()
|
||||||
|
.setTitle('Omnichannel Backend API')
|
||||||
|
.setDescription('Documentacao das rotas da API do Omnichannel Sothis.')
|
||||||
|
.setVersion('1.0.0')
|
||||||
|
.addBearerAuth()
|
||||||
|
.build();
|
||||||
|
const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
|
||||||
|
SwaggerModule.setup('docs', app, swaggerDocument, {
|
||||||
|
jsonDocumentUrl: 'docs/openapi.json',
|
||||||
|
});
|
||||||
|
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
Logger.log(`Backend ouvindo na porta ${port}`, 'Bootstrap');
|
Logger.log(`Backend ouvindo na porta ${port}`, 'Bootstrap');
|
||||||
|
Logger.log(`Swagger disponivel em /docs`, 'Bootstrap');
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@ -1,40 +1,90 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { AdminAccessService } from './admin-access.service';
|
import { AdminAccessService } from './admin-access.service';
|
||||||
|
|
||||||
|
@ApiTags('Administracao')
|
||||||
@Controller('admin/access')
|
@Controller('admin/access')
|
||||||
export class AdminAccessController {
|
export class AdminAccessController {
|
||||||
constructor(private readonly adminAccessService: AdminAccessService) {}
|
constructor(private readonly adminAccessService: AdminAccessService) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista opcoes administrativas',
|
||||||
|
description: 'Retorna dados auxiliares usados nos formularios administrativos, como perfis, areas e opcoes de acesso.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: 'Opcoes administrativas retornadas.' })
|
||||||
@Get('options')
|
@Get('options')
|
||||||
getOptions() {
|
getOptions() {
|
||||||
return this.adminAccessService.getOptions();
|
return this.adminAccessService.getOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta visao geral operacional',
|
||||||
|
description: 'Retorna indicadores consolidados para o dashboard administrativo.',
|
||||||
|
})
|
||||||
@Get('overview')
|
@Get('overview')
|
||||||
getOverview() {
|
getOverview() {
|
||||||
return this.adminAccessService.getOverview();
|
return this.adminAccessService.getOverview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta ranking de atendentes',
|
||||||
|
description: 'Lista desempenho de atendentes, opcionalmente filtrando por area.',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'areaId', required: false, description: 'ID da area para filtrar o ranking.' })
|
||||||
@Get('ranking')
|
@Get('ranking')
|
||||||
getRanking(@Query('areaId') areaId?: string) {
|
getRanking(@Query('areaId') areaId?: string) {
|
||||||
return this.adminAccessService.getAttendantRanking(areaId ? Number(areaId) : null);
|
return this.adminAccessService.getAttendantRanking(areaId ? Number(areaId) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista auditoria administrativa',
|
||||||
|
description: 'Retorna eventos de auditoria paginados para rastrear alteracoes e acoes relevantes no painel.',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'page', required: false, description: 'Pagina da consulta. Padrao: 1.' })
|
||||||
|
@ApiQuery({ name: 'limit', required: false, description: 'Quantidade por pagina. Padrao: 100.' })
|
||||||
@Get('audit')
|
@Get('audit')
|
||||||
listAuditLogs(@Query('page') page?: string, @Query('limit') limit?: string) {
|
listAuditLogs(@Query('page') page?: string, @Query('limit') limit?: string) {
|
||||||
return this.adminAccessService.listAuditLogs(Number(page || 1), Number(limit || 100));
|
return this.adminAccessService.listAuditLogs(Number(page || 1), Number(limit || 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista conteudos da IA',
|
||||||
|
description: 'Retorna os documentos e conteudos cadastrados para alimentar a base de conhecimento da IA.',
|
||||||
|
})
|
||||||
@Get('ai-contents')
|
@Get('ai-contents')
|
||||||
listAiContents() {
|
listAiContents() {
|
||||||
return this.adminAccessService.listAiContents();
|
return this.adminAccessService.listAiContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Baixa arquivo de conteudo da IA',
|
||||||
|
description: 'Retorna o arquivo original de um conteudo cadastrado na base de conhecimento.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do conteudo da IA.' })
|
||||||
@Get('ai-contents/:id/file')
|
@Get('ai-contents/:id/file')
|
||||||
getAiContentFile(@Param('id') id: string) {
|
getAiContentFile(@Param('id') id: string) {
|
||||||
return this.adminAccessService.getAiContentFile(Number(id));
|
return this.adminAccessService.getAiContentFile(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria conteudo da IA',
|
||||||
|
description: 'Cadastra um documento, anotacao ou arquivo para apoiar respostas e contexto da IA.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
title: { type: 'string', example: 'Politica de ferias' },
|
||||||
|
areaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
filename: { type: 'string', nullable: true, example: 'ferias.pdf' },
|
||||||
|
mimetype: { type: 'string', nullable: true, example: 'application/pdf' },
|
||||||
|
fileSize: { type: 'number', nullable: true, example: 102400 },
|
||||||
|
contentBase64: { type: 'string', nullable: true, description: 'Arquivo em base64.' },
|
||||||
|
notes: { type: 'string', nullable: true, example: 'Regras internas de ferias.' },
|
||||||
|
createdByUserId: { type: 'number', nullable: true, example: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('ai-contents')
|
@Post('ai-contents')
|
||||||
createAiContent(@Body() body: {
|
createAiContent(@Body() body: {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -49,21 +99,50 @@ export class AdminAccessController {
|
|||||||
return this.adminAccessService.createAiContent(body);
|
return this.adminAccessService.createAiContent(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove conteudo da IA',
|
||||||
|
description: 'Exclui um conteudo cadastrado na base de conhecimento.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do conteudo da IA.' })
|
||||||
@Delete('ai-contents/:id')
|
@Delete('ai-contents/:id')
|
||||||
deleteAiContent(@Param('id') id: string) {
|
deleteAiContent(@Param('id') id: string) {
|
||||||
return this.adminAccessService.deleteAiContent(Number(id));
|
return this.adminAccessService.deleteAiContent(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista areas de atendimento',
|
||||||
|
description: 'Retorna as areas usadas para roteamento, permissoes, especialidades e indicadores.',
|
||||||
|
})
|
||||||
@Get('areas')
|
@Get('areas')
|
||||||
listAreas() {
|
listAreas() {
|
||||||
return this.adminAccessService.listAreas();
|
return this.adminAccessService.listAreas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria area de atendimento',
|
||||||
|
description: 'Cadastra uma nova area para roteamento de conversas e organizacao de usuarios.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['nome'],
|
||||||
|
properties: {
|
||||||
|
nome: { type: 'string', example: 'Recursos Humanos' },
|
||||||
|
descricao: { type: 'string', nullable: true, example: 'Atendimentos de RH.' },
|
||||||
|
responsavelUsuarioId: { type: 'number', nullable: true, example: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('areas')
|
@Post('areas')
|
||||||
createArea(@Body() body: { nome: string; descricao?: string | null; responsavelUsuarioId?: number | null }) {
|
createArea(@Body() body: { nome: string; descricao?: string | null; responsavelUsuarioId?: number | null }) {
|
||||||
return this.adminAccessService.createArea(body);
|
return this.adminAccessService.createArea(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza area de atendimento',
|
||||||
|
description: 'Altera dados cadastrais, responsavel ou status de uma area.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID da area.' })
|
||||||
@Put('areas/:id')
|
@Put('areas/:id')
|
||||||
updateArea(
|
updateArea(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@ -72,16 +151,53 @@ export class AdminAccessController {
|
|||||||
return this.adminAccessService.updateArea(Number(id), body);
|
return this.adminAccessService.updateArea(Number(id), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove area de atendimento',
|
||||||
|
description: 'Exclui ou desativa uma area, conforme regra de negocio aplicada pelo service.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID da area.' })
|
||||||
@Delete('areas/:id')
|
@Delete('areas/:id')
|
||||||
deleteArea(@Param('id') id: string) {
|
deleteArea(@Param('id') id: string) {
|
||||||
return this.adminAccessService.deleteArea(Number(id));
|
return this.adminAccessService.deleteArea(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista usuarios',
|
||||||
|
description: 'Retorna usuarios conhecidos pela administracao para ajuste de perfil, area e especialidades.',
|
||||||
|
})
|
||||||
@Get('users')
|
@Get('users')
|
||||||
listUsers() {
|
listUsers() {
|
||||||
return this.adminAccessService.listUsers();
|
return this.adminAccessService.listUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza acessos de usuario',
|
||||||
|
description: 'Altera perfil, perfis adicionais, area principal e especialidades de atendimento de um usuario.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do usuario.' })
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
perfilId: { type: 'number', nullable: true, example: 1 },
|
||||||
|
perfilIds: { type: 'array', items: { type: 'number' }, example: [1, 2] },
|
||||||
|
areaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
especialidades: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['areaId'],
|
||||||
|
properties: {
|
||||||
|
areaId: { type: 'number', example: 2 },
|
||||||
|
funcao: { type: 'string', nullable: true, example: 'atendente' },
|
||||||
|
principal: { type: 'boolean', example: true },
|
||||||
|
ativo: { type: 'boolean', example: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Put('users/:id')
|
@Put('users/:id')
|
||||||
updateUserAccess(
|
updateUserAccess(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
|
|||||||
@ -1,20 +1,47 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Post, Query } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { AgentNotesService } from './agent-notes.service';
|
import { AgentNotesService } from './agent-notes.service';
|
||||||
|
|
||||||
|
@ApiTags('Notas do agente')
|
||||||
@Controller('agent/notes')
|
@Controller('agent/notes')
|
||||||
export class AgentNotesController {
|
export class AgentNotesController {
|
||||||
constructor(private readonly agentNotesService: AgentNotesService) {}
|
constructor(private readonly agentNotesService: AgentNotesService) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista notas do agente',
|
||||||
|
description: 'Retorna lembretes e anotacoes pessoais cadastradas por um agente no painel.',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'userId', required: true, description: 'ID do usuario dono das notas.' })
|
||||||
@Get()
|
@Get()
|
||||||
listNotes(@Query('userId') userId: string) {
|
listNotes(@Query('userId') userId: string) {
|
||||||
return this.agentNotesService.listNotes(Number(userId));
|
return this.agentNotesService.listNotes(Number(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria nota do agente',
|
||||||
|
description: 'Adiciona uma anotacao pessoal para organizacao do atendente.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['userId', 'text'],
|
||||||
|
properties: {
|
||||||
|
userId: { type: 'number', example: 10 },
|
||||||
|
text: { type: 'string', example: 'Retornar contato no fim do dia.' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post()
|
@Post()
|
||||||
createNote(@Body() body: { userId: number; text: string }) {
|
createNote(@Body() body: { userId: number; text: string }) {
|
||||||
return this.agentNotesService.createNote(Number(body.userId), body.text);
|
return this.agentNotesService.createNote(Number(body.userId), body.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove nota do agente',
|
||||||
|
description: 'Exclui uma nota pessoal do agente validando o usuario dono da anotacao.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID da nota.' })
|
||||||
|
@ApiQuery({ name: 'userId', required: true, description: 'ID do usuario dono da nota.' })
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
deleteNote(@Param('id') id: string, @Query('userId') userId: string) {
|
deleteNote(@Param('id') id: string, @Query('userId') userId: string) {
|
||||||
return this.agentNotesService.deleteNote(Number(userId), Number(id));
|
return this.agentNotesService.deleteNote(Number(userId), Number(id));
|
||||||
|
|||||||
@ -1,30 +1,56 @@
|
|||||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { AgentPresenceService } from './agent-presence.service';
|
import { AgentPresenceService } from './agent-presence.service';
|
||||||
|
|
||||||
|
@ApiTags('Presenca do agente')
|
||||||
@Controller('agent/presence')
|
@Controller('agent/presence')
|
||||||
export class AgentPresenceController {
|
export class AgentPresenceController {
|
||||||
constructor(private readonly agentPresenceService: AgentPresenceService) {}
|
constructor(private readonly agentPresenceService: AgentPresenceService) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista presenca dos agentes',
|
||||||
|
description: 'Retorna o estado atual dos agentes para acompanhamento operacional, incluindo disponibilidade e pausas.',
|
||||||
|
})
|
||||||
@Get()
|
@Get()
|
||||||
listPresence() {
|
listPresence() {
|
||||||
return this.agentPresenceService.listPresence();
|
return this.agentPresenceService.listPresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta presenca do agente logado',
|
||||||
|
description: 'Retorna o status de presenca de um usuario especifico.',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'userId', required: true, description: 'ID do usuario.' })
|
||||||
@Get('me')
|
@Get('me')
|
||||||
getPresence(@Query('userId') userId: string) {
|
getPresence(@Query('userId') userId: string) {
|
||||||
return this.agentPresenceService.getPresence(Number(userId));
|
return this.agentPresenceService.getPresence(Number(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Coloca agente em pausa',
|
||||||
|
description: 'Marca o agente como pausado para que ele nao receba novos atendimentos enquanto estiver indisponivel.',
|
||||||
|
})
|
||||||
|
@ApiBody({ schema: { type: 'object', required: ['userId'], properties: { userId: { type: 'number', example: 10 } } } })
|
||||||
@Post('pause')
|
@Post('pause')
|
||||||
pause(@Body() body: { userId: number }) {
|
pause(@Body() body: { userId: number }) {
|
||||||
return this.agentPresenceService.pause(Number(body.userId));
|
return this.agentPresenceService.pause(Number(body.userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Retoma disponibilidade do agente',
|
||||||
|
description: 'Remove a pausa do agente e marca o usuario como disponivel novamente.',
|
||||||
|
})
|
||||||
|
@ApiBody({ schema: { type: 'object', required: ['userId'], properties: { userId: { type: 'number', example: 10 } } } })
|
||||||
@Post('resume')
|
@Post('resume')
|
||||||
resume(@Body() body: { userId: number }) {
|
resume(@Body() body: { userId: number }) {
|
||||||
return this.agentPresenceService.resume(Number(body.userId));
|
return this.agentPresenceService.resume(Number(body.userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Marca agente como offline',
|
||||||
|
description: 'Atualiza a presenca do agente para offline, usado em saida do painel ou encerramento de expediente.',
|
||||||
|
})
|
||||||
|
@ApiBody({ schema: { type: 'object', required: ['userId'], properties: { userId: { type: 'number', example: 10 } } } })
|
||||||
@Post('offline')
|
@Post('offline')
|
||||||
offline(@Body() body: { userId: number }) {
|
offline(@Body() body: { userId: number }) {
|
||||||
return this.agentPresenceService.offline(Number(body.userId));
|
return this.agentPresenceService.offline(Number(body.userId));
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Body, Controller, Get, Param, Put } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Put } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { CustomerContactsService } from './customer-contacts.service';
|
import { CustomerContactsService } from './customer-contacts.service';
|
||||||
|
|
||||||
interface SaveContactBody {
|
interface SaveContactBody {
|
||||||
@ -12,20 +13,51 @@ interface SaveContactBody {
|
|||||||
userId?: number | null;
|
userId?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiTags('Contatos')
|
||||||
@Controller('contacts')
|
@Controller('contacts')
|
||||||
export class CustomerContactsController {
|
export class CustomerContactsController {
|
||||||
constructor(private readonly customerContactsService: CustomerContactsService) {}
|
constructor(private readonly customerContactsService: CustomerContactsService) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista contatos',
|
||||||
|
description: 'Retorna a agenda de contatos usada para atendimento, abertura ativa e consulta rapida no chat.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: 'Lista de contatos retornada.' })
|
||||||
@Get()
|
@Get()
|
||||||
listContacts() {
|
listContacts() {
|
||||||
return this.customerContactsService.listContacts();
|
return this.customerContactsService.listContacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta contato por chat',
|
||||||
|
description: 'Busca os dados salvos de um contato usando o identificador da conversa.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'chatId', description: 'Identificador codificado do chat.' })
|
||||||
@Get(':chatId')
|
@Get(':chatId')
|
||||||
getContact(@Param('chatId') chatId: string) {
|
getContact(@Param('chatId') chatId: string) {
|
||||||
return this.customerContactsService.getContact(decodeURIComponent(chatId));
|
return this.customerContactsService.getContact(decodeURIComponent(chatId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Salva contato',
|
||||||
|
description: 'Cria ou atualiza dados do contato associados ao chat, incluindo telefone, email, etiqueta de identificacao e observacao.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'chatId', description: 'Identificador codificado do chat.' })
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
phone: { type: 'string', nullable: true, example: '5511999999999' },
|
||||||
|
whatsappPhone: { type: 'string', nullable: true, example: '5511999999999' },
|
||||||
|
callSmsPhone: { type: 'string', nullable: true, example: '5511988888888' },
|
||||||
|
email: { type: 'string', nullable: true, example: 'cliente@empresa.com' },
|
||||||
|
name: { type: 'string', nullable: true, example: 'Tony Silva' },
|
||||||
|
company: { type: 'string', nullable: true, example: 'Departamento de RH' },
|
||||||
|
note: { type: 'string', nullable: true, example: 'Contato prefere WhatsApp.' },
|
||||||
|
userId: { type: 'number', nullable: true, example: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Put(':chatId')
|
@Put(':chatId')
|
||||||
saveContact(
|
saveContact(
|
||||||
@Param('chatId') chatId: string,
|
@Param('chatId') chatId: string,
|
||||||
|
|||||||
@ -1,20 +1,51 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { KnowledgeBaseService } from './knowledge-base.service';
|
import { KnowledgeBaseService } from './knowledge-base.service';
|
||||||
|
|
||||||
|
@ApiTags('Base de conhecimento')
|
||||||
@Controller('admin/knowledge')
|
@Controller('admin/knowledge')
|
||||||
export class KnowledgeBaseController {
|
export class KnowledgeBaseController {
|
||||||
constructor(private readonly knowledgeBaseService: KnowledgeBaseService) {}
|
constructor(private readonly knowledgeBaseService: KnowledgeBaseService) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta fluxo do bot',
|
||||||
|
description: 'Retorna a arvore atual do agente virtual usada para perguntas, opcoes, encerramento e transferencia.',
|
||||||
|
})
|
||||||
@Get('bot-flow')
|
@Get('bot-flow')
|
||||||
getBotFlow() {
|
getBotFlow() {
|
||||||
return this.knowledgeBaseService.getBotFlow();
|
return this.knowledgeBaseService.getBotFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista versoes publicadas do fluxo do bot',
|
||||||
|
description: 'Retorna historico de publicacoes do fluxo para auditoria e acompanhamento de mudancas.',
|
||||||
|
})
|
||||||
@Get('bot-flow/versions')
|
@Get('bot-flow/versions')
|
||||||
listBotFlowVersions() {
|
listBotFlowVersions() {
|
||||||
return this.knowledgeBaseService.listBotFlowVersions();
|
return this.knowledgeBaseService.listBotFlowVersions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria no no fluxo do bot',
|
||||||
|
description: 'Adiciona uma pergunta, acao de transferencia para agente ou encerramento dentro da arvore do agente virtual.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['parentId', 'nodeType'],
|
||||||
|
properties: {
|
||||||
|
parentId: { type: 'number', example: 1 },
|
||||||
|
nodeType: { type: 'string', enum: ['question', 'agent', 'close'], example: 'question' },
|
||||||
|
title: { type: 'string', example: 'Ferias' },
|
||||||
|
messageText: { type: 'string', example: 'Voce quer saber sobre saldo ou solicitacao de ferias?' },
|
||||||
|
keywords: { type: 'string', example: 'ferias, descanso, recesso' },
|
||||||
|
fallbackMessage: { type: 'string', example: 'Nao entendi. Pode reformular?' },
|
||||||
|
fallbackAttempts: { type: 'number', example: 2 },
|
||||||
|
fallbackAreaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
areaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('bot-flow/nodes')
|
@Post('bot-flow/nodes')
|
||||||
createBotFlowNode(@Body() body: {
|
createBotFlowNode(@Body() body: {
|
||||||
parentId: number;
|
parentId: number;
|
||||||
@ -30,6 +61,11 @@ export class KnowledgeBaseController {
|
|||||||
return this.knowledgeBaseService.createBotFlowNode(body);
|
return this.knowledgeBaseService.createBotFlowNode(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza no do fluxo do bot',
|
||||||
|
description: 'Edita textos, palavras-chave, fallback, area e ordenacao de um no existente no agente virtual.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do no do fluxo.' })
|
||||||
@Put('bot-flow/nodes/:id')
|
@Put('bot-flow/nodes/:id')
|
||||||
updateBotFlowNode(@Param('id') id: string, @Body() body: {
|
updateBotFlowNode(@Param('id') id: string, @Body() body: {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -44,21 +80,52 @@ export class KnowledgeBaseController {
|
|||||||
return this.knowledgeBaseService.updateBotFlowNode(Number(id), body);
|
return this.knowledgeBaseService.updateBotFlowNode(Number(id), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove no do fluxo do bot',
|
||||||
|
description: 'Exclui um no da arvore do agente virtual.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do no do fluxo.' })
|
||||||
@Delete('bot-flow/nodes/:id')
|
@Delete('bot-flow/nodes/:id')
|
||||||
deleteBotFlowNode(@Param('id') id: string) {
|
deleteBotFlowNode(@Param('id') id: string) {
|
||||||
return this.knowledgeBaseService.deleteBotFlowNode(Number(id));
|
return this.knowledgeBaseService.deleteBotFlowNode(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Publica fluxo do bot',
|
||||||
|
description: 'Marca a versao atual do fluxo como publicada para uso pelo agente virtual.',
|
||||||
|
})
|
||||||
@Post('bot-flow/publish')
|
@Post('bot-flow/publish')
|
||||||
publishBotFlow() {
|
publishBotFlow() {
|
||||||
return this.knowledgeBaseService.publishBotFlow();
|
return this.knowledgeBaseService.publishBotFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta fluxo de triagem',
|
||||||
|
description: 'Retorna mensagens, publicos, intencoes e regras usadas pelo agente virtual para classificar e rotear o atendimento.',
|
||||||
|
})
|
||||||
@Get('triage-flow')
|
@Get('triage-flow')
|
||||||
getTriageFlow() {
|
getTriageFlow() {
|
||||||
return this.knowledgeBaseService.getTriageFlow();
|
return this.knowledgeBaseService.getTriageFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza configuracao do fluxo de triagem',
|
||||||
|
description: 'Altera mensagens gerais, limite de tentativas e area de fallback do roteamento automatico.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
greetingMessage: { type: 'string', example: 'Ola, sou o Agente Virtual Sothis.' },
|
||||||
|
audienceQuestion: { type: 'string', example: 'Voce e colaborador, candidato ou cliente?' },
|
||||||
|
intentQuestionTemplate: { type: 'string', example: 'Sobre qual assunto voce precisa falar?' },
|
||||||
|
resolutionQuestion: { type: 'string', example: 'Consegui te ajudar?' },
|
||||||
|
fallbackMessage: { type: 'string', example: 'Vou encaminhar seu atendimento para o time responsavel.' },
|
||||||
|
fallbackAreaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
maxAttempts: { type: 'number', example: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Put('triage-flow')
|
@Put('triage-flow')
|
||||||
updateTriageFlow(@Body() body: {
|
updateTriageFlow(@Body() body: {
|
||||||
greetingMessage?: string;
|
greetingMessage?: string;
|
||||||
@ -72,16 +139,29 @@ export class KnowledgeBaseController {
|
|||||||
return this.knowledgeBaseService.updateTriageFlow(body);
|
return this.knowledgeBaseService.updateTriageFlow(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria publico de triagem',
|
||||||
|
description: 'Cadastra um publico identificado pelo bot, como colaborador, candidato ou cliente.',
|
||||||
|
})
|
||||||
@Post('triage-flow/audiences')
|
@Post('triage-flow/audiences')
|
||||||
createAudience(@Body() body: { label: string; keywords?: string; sortOrder?: number }) {
|
createAudience(@Body() body: { label: string; keywords?: string; sortOrder?: number }) {
|
||||||
return this.knowledgeBaseService.createAudience(body);
|
return this.knowledgeBaseService.createAudience(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza publico de triagem',
|
||||||
|
description: 'Edita nome, palavras-chave, ordenacao e status de um publico usado pelo bot.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do publico.' })
|
||||||
@Put('triage-flow/audiences/:id')
|
@Put('triage-flow/audiences/:id')
|
||||||
updateAudience(@Param('id') id: string, @Body() body: { label?: string; keywords?: string; sortOrder?: number; active?: boolean }) {
|
updateAudience(@Param('id') id: string, @Body() body: { label?: string; keywords?: string; sortOrder?: number; active?: boolean }) {
|
||||||
return this.knowledgeBaseService.updateAudience(Number(id), body);
|
return this.knowledgeBaseService.updateAudience(Number(id), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria intencao de triagem',
|
||||||
|
description: 'Cadastra um assunto reconhecido pelo bot e associa a intencao a uma area de atendimento.',
|
||||||
|
})
|
||||||
@Post('triage-flow/intents')
|
@Post('triage-flow/intents')
|
||||||
createIntent(@Body() body: {
|
createIntent(@Body() body: {
|
||||||
audienceId: number;
|
audienceId: number;
|
||||||
@ -96,6 +176,11 @@ export class KnowledgeBaseController {
|
|||||||
return this.knowledgeBaseService.createIntent(body);
|
return this.knowledgeBaseService.createIntent(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza intencao de triagem',
|
||||||
|
description: 'Edita assunto, area, palavras-chave, mensagens e status de uma intencao reconhecida pelo bot.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID da intencao.' })
|
||||||
@Put('triage-flow/intents/:id')
|
@Put('triage-flow/intents/:id')
|
||||||
updateIntent(@Param('id') id: string, @Body() body: {
|
updateIntent(@Param('id') id: string, @Body() body: {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -110,21 +195,50 @@ export class KnowledgeBaseController {
|
|||||||
return this.knowledgeBaseService.updateIntent(Number(id), body);
|
return this.knowledgeBaseService.updateIntent(Number(id), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista palavras-chave de roteamento',
|
||||||
|
description: 'Retorna palavras-chave usadas para identificar area responsavel quando o bot precisa transferir o atendimento.',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'areaId', required: false, description: 'ID da area para filtrar as palavras-chave.' })
|
||||||
@Get('routing-keywords')
|
@Get('routing-keywords')
|
||||||
listRoutingKeywords(@Query('areaId') areaId?: string) {
|
listRoutingKeywords(@Query('areaId') areaId?: string) {
|
||||||
return this.knowledgeBaseService.listRoutingKeywords(areaId ? Number(areaId) : null);
|
return this.knowledgeBaseService.listRoutingKeywords(areaId ? Number(areaId) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria palavra-chave de roteamento',
|
||||||
|
description: 'Associa uma palavra-chave a uma area para melhorar o roteamento automatico do agente virtual.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['areaId', 'keyword'],
|
||||||
|
properties: {
|
||||||
|
areaId: { type: 'number', example: 2 },
|
||||||
|
keyword: { type: 'string', example: 'ferias' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('routing-keywords')
|
@Post('routing-keywords')
|
||||||
createRoutingKeyword(@Body() body: { areaId: number; keyword: string }) {
|
createRoutingKeyword(@Body() body: { areaId: number; keyword: string }) {
|
||||||
return this.knowledgeBaseService.createRoutingKeyword(body);
|
return this.knowledgeBaseService.createRoutingKeyword(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza palavra-chave de roteamento',
|
||||||
|
description: 'Altera texto ou status de uma palavra-chave usada pelo roteamento automatico.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID da palavra-chave.' })
|
||||||
@Put('routing-keywords/:id')
|
@Put('routing-keywords/:id')
|
||||||
updateRoutingKeyword(@Param('id') id: string, @Body() body: { keyword?: string; active?: boolean }) {
|
updateRoutingKeyword(@Param('id') id: string, @Body() body: { keyword?: string; active?: boolean }) {
|
||||||
return this.knowledgeBaseService.updateRoutingKeyword(Number(id), body);
|
return this.knowledgeBaseService.updateRoutingKeyword(Number(id), body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove palavra-chave de roteamento',
|
||||||
|
description: 'Exclui uma palavra-chave do mecanismo de roteamento automatico.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID da palavra-chave.' })
|
||||||
@Delete('routing-keywords/:id')
|
@Delete('routing-keywords/:id')
|
||||||
deleteRoutingKeyword(@Param('id') id: string) {
|
deleteRoutingKeyword(@Param('id') id: string) {
|
||||||
return this.knowledgeBaseService.deleteRoutingKeyword(Number(id));
|
return this.knowledgeBaseService.deleteRoutingKeyword(Number(id));
|
||||||
|
|||||||
@ -1,26 +1,61 @@
|
|||||||
import { Body, Controller, Get, Post, Query, Res } from '@nestjs/common';
|
import { Body, Controller, Get, Post, Query, Res } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { LoginData } from './auth.types';
|
import { LoginData } from './auth.types';
|
||||||
|
|
||||||
|
@ApiTags('Autenticacao')
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta configuracao publica de login',
|
||||||
|
description: 'Informa ao frontend quais metodos de autenticacao estao disponiveis para exibir a tela de login corretamente.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: 'Configuracao publica de autenticacao retornada.' })
|
||||||
@Get('config')
|
@Get('config')
|
||||||
getConfig() {
|
getConfig() {
|
||||||
return this.authService.getPublicConfig();
|
return this.authService.getPublicConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Realiza login via LDAP/AD',
|
||||||
|
description: 'Valida as credenciais corporativas no provedor LDAP/Active Directory e retorna os dados do usuario autenticado com token JWT.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['username', 'password'],
|
||||||
|
properties: {
|
||||||
|
username: { type: 'string', example: 'rafael.lopes' },
|
||||||
|
password: { type: 'string', example: 'senha-do-usuario' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 201, description: 'Login realizado com sucesso.' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Credenciais invalidas ou usuario sem acesso.' })
|
||||||
@Post('login')
|
@Post('login')
|
||||||
login(@Body() body: LoginData) {
|
login(@Body() body: LoginData) {
|
||||||
return this.authService.loginWithLdap(body);
|
return this.authService.loginWithLdap(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Inicia login Microsoft OAuth',
|
||||||
|
description: 'Redireciona o navegador para a URL de autorizacao da Microsoft quando o login OAuth estiver configurado.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 302, description: 'Redirecionamento para Microsoft OAuth.' })
|
||||||
@Get('oauth/microsoft/start')
|
@Get('oauth/microsoft/start')
|
||||||
startMicrosoftLogin(@Res() response: any) {
|
startMicrosoftLogin(@Res() response: any) {
|
||||||
return response.redirect(this.authService.getMicrosoftAuthorizeUrl());
|
return response.redirect(this.authService.getMicrosoftAuthorizeUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Recebe callback Microsoft OAuth',
|
||||||
|
description: 'Processa o retorno da Microsoft, troca o codigo por usuario autenticado e redireciona o frontend com token e dados do usuario.',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'code', required: false, description: 'Codigo de autorizacao retornado pela Microsoft.' })
|
||||||
|
@ApiQuery({ name: 'state', required: false, description: 'Estado usado para validar a tentativa de login OAuth.' })
|
||||||
|
@ApiResponse({ status: 302, description: 'Redirecionamento para o frontend com dados de autenticacao.' })
|
||||||
@Get('oauth/microsoft/callback')
|
@Get('oauth/microsoft/callback')
|
||||||
async microsoftCallback(@Query() query: { code?: string; state?: string }, @Res() response: any) {
|
async microsoftCallback(@Query() query: { code?: string; state?: string }, @Res() response: any) {
|
||||||
const authResult = await this.authService.loginWithMicrosoftCallback(query);
|
const authResult = await this.authService.loginWithMicrosoftCallback(query);
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { Controller, Get, Post, Body, Delete, Param } from '@nestjs/common';
|
import { Controller, Get, Post, Body, Delete, Param } from '@nestjs/common';
|
||||||
|
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
import { WhatsappService } from './whatsapp.service';
|
import { WhatsappService } from './whatsapp.service';
|
||||||
import { WhatsappAssignmentService } from './whatsapp-assignment.service';
|
import { WhatsappAssignmentService } from './whatsapp-assignment.service';
|
||||||
|
|
||||||
|
@ApiTags('WhatsApp')
|
||||||
@Controller('whatsapp')
|
@Controller('whatsapp')
|
||||||
export class WhatsappController {
|
export class WhatsappController {
|
||||||
constructor(
|
constructor(
|
||||||
@ -9,86 +11,248 @@ export class WhatsappController {
|
|||||||
private readonly assignmentService: WhatsappAssignmentService
|
private readonly assignmentService: WhatsappAssignmentService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta status da conexao WhatsApp',
|
||||||
|
description: 'Retorna o estado atual da sessao WhatsApp usada pelo backend, como conectado, aguardando QR Code ou desconectado.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: 'Status atual da conexao retornado.' })
|
||||||
@Get('status')
|
@Get('status')
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return { status: this.whatsappService.getStatus() };
|
return { status: this.whatsappService.getStatus() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista conversas do WhatsApp',
|
||||||
|
description: 'Retorna os chats conhecidos pelo backend para alimentar o painel de atendimento em tempo real.',
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, description: 'Lista de conversas retornada.' })
|
||||||
@Get('chats')
|
@Get('chats')
|
||||||
async getChats() {
|
async getChats() {
|
||||||
return this.whatsappService.getChats();
|
return this.whatsappService.getChats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista mensagens de uma conversa',
|
||||||
|
description: 'Busca o historico de mensagens de um chat especifico para abrir a conversa no painel do atendente.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Mensagens do chat retornadas.' })
|
||||||
@Get('messages/:chatId')
|
@Get('messages/:chatId')
|
||||||
async getChatMessages(@Param('chatId') chatId: string) {
|
async getChatMessages(@Param('chatId') chatId: string) {
|
||||||
return this.whatsappService.getChatMessages(chatId);
|
return this.whatsappService.getChatMessages(chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Baixa midia de uma mensagem',
|
||||||
|
description: 'Retorna a midia associada a uma mensagem do WhatsApp quando a mensagem possui anexo.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
||||||
|
@ApiParam({ name: 'messageId', description: 'Identificador da mensagem com midia.' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Midia da mensagem retornada.' })
|
||||||
@Get('media/:chatId/:messageId')
|
@Get('media/:chatId/:messageId')
|
||||||
async getMessageMedia(@Param('chatId') chatId: string, @Param('messageId') messageId: string) {
|
async getMessageMedia(@Param('chatId') chatId: string, @Param('messageId') messageId: string) {
|
||||||
return this.whatsappService.getMessageMedia(chatId, messageId);
|
return this.whatsappService.getMessageMedia(chatId, messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Envia mensagem no WhatsApp',
|
||||||
|
description: 'Envia uma mensagem ativa para um contato ou responde uma conversa existente, opcionalmente com midia em base64.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['to', 'message'],
|
||||||
|
properties: {
|
||||||
|
to: { type: 'string', example: '5511999999999@c.us' },
|
||||||
|
message: { type: 'string', example: 'Ola, como posso ajudar?' },
|
||||||
|
senderName: { type: 'string', example: 'Rafael Lopes' },
|
||||||
|
media: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: true,
|
||||||
|
properties: {
|
||||||
|
data: { type: 'string', description: 'Arquivo em base64.' },
|
||||||
|
mimetype: { type: 'string', example: 'application/pdf' },
|
||||||
|
filename: { type: 'string', example: 'documento.pdf' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 201, description: 'Mensagem enviada.' })
|
||||||
@Post('send')
|
@Post('send')
|
||||||
async sendMessage(@Body() body: { to: string; message: string; senderName?: string; media?: { data: string; mimetype: string; filename?: string } }) {
|
async sendMessage(@Body() body: { to: string; message: string; senderName?: string; media?: { data: string; mimetype: string; filename?: string } }) {
|
||||||
return this.whatsappService.sendMessage(body.to, body.message, body.media, body.senderName);
|
return this.whatsappService.sendMessage(body.to, body.message, body.media, body.senderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Inicia atendimento ativo',
|
||||||
|
description: 'Abre uma conversa ativa usando um template aprovado e registra o atendimento para acompanhamento no painel.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['to', 'templateId', 'userId'],
|
||||||
|
properties: {
|
||||||
|
to: { type: 'string', example: '5511999999999' },
|
||||||
|
templateId: { type: 'number', example: 1 },
|
||||||
|
userId: { type: 'number', example: 10 },
|
||||||
|
areaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
variables: { type: 'object', additionalProperties: { type: 'string', nullable: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 201, description: 'Atendimento ativo iniciado.' })
|
||||||
@Post('start-attendance')
|
@Post('start-attendance')
|
||||||
async startAttendance(@Body() body: { to: string; templateId: number; userId: number; areaId?: number | null; variables?: Record<string, string | null | undefined> }) {
|
async startAttendance(@Body() body: { to: string; templateId: number; userId: number; areaId?: number | null; variables?: Record<string, string | null | undefined> }) {
|
||||||
return this.whatsappService.startAttendance(body.to, body.templateId, body.userId, body.areaId, body.variables);
|
return this.whatsappService.startAttendance(body.to, body.templateId, body.userId, body.areaId, body.variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Assume uma conversa',
|
||||||
|
description: 'Vincula um chat a um atendente e, opcionalmente, a uma area de atendimento.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['chatId', 'userId'],
|
||||||
|
properties: {
|
||||||
|
chatId: { type: 'string', example: '5511999999999@c.us' },
|
||||||
|
userId: { type: 'string', example: '10' },
|
||||||
|
areaId: { type: 'string', example: '2' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('assign')
|
@Post('assign')
|
||||||
async assignChat(@Body() body: { chatId: string; userId: string; areaId?: string }) {
|
async assignChat(@Body() body: { chatId: string; userId: string; areaId?: string }) {
|
||||||
return this.assignmentService.assignChat(body.chatId, body.userId, body.areaId);
|
return this.assignmentService.assignChat(body.chatId, body.userId, body.areaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Transfere uma conversa',
|
||||||
|
description: 'Encaminha o chat para outra area ou responsavel e registra observacao para contextualizar o proximo atendimento.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['chatId', 'areaId'],
|
||||||
|
properties: {
|
||||||
|
chatId: { type: 'string', example: '5511999999999@c.us' },
|
||||||
|
areaId: { type: 'number', example: 2 },
|
||||||
|
userId: { type: 'number', nullable: true, example: 10 },
|
||||||
|
note: { type: 'string', nullable: true, example: 'Tony e um colaborador e quer saber sobre ferias.' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('transfer')
|
@Post('transfer')
|
||||||
async transferChat(@Body() body: { chatId: string; areaId: number; userId?: number | null; note?: string | null }) {
|
async transferChat(@Body() body: { chatId: string; areaId: number; userId?: number | null; note?: string | null }) {
|
||||||
return this.assignmentService.transferChat(body);
|
return this.assignmentService.transferChat(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Libera conversa assumida',
|
||||||
|
description: 'Remove o vinculo de atendimento do chat, permitindo que outro atendente assuma a conversa.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
||||||
@Delete('release/:chatId')
|
@Delete('release/:chatId')
|
||||||
async releaseChat(@Param('chatId') chatId: string) {
|
async releaseChat(@Param('chatId') chatId: string) {
|
||||||
return this.assignmentService.releaseChat(chatId);
|
return this.assignmentService.releaseChat(chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Fecha atendimento',
|
||||||
|
description: 'Finaliza o atendimento de um chat e remove o vinculo ativo com o atendente.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['chatId'],
|
||||||
|
properties: {
|
||||||
|
chatId: { type: 'string', example: '5511999999999@c.us' },
|
||||||
|
userId: { oneOf: [{ type: 'string' }, { type: 'number' }], nullable: true, example: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('close')
|
@Post('close')
|
||||||
async closeChat(@Body() body: { chatId: string; userId?: string | number | null }) {
|
async closeChat(@Body() body: { chatId: string; userId?: string | number | null }) {
|
||||||
return this.assignmentService.closeChat(body.chatId, body.userId);
|
return this.assignmentService.closeChat(body.chatId, body.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Consulta atribuicao do chat',
|
||||||
|
description: 'Retorna qual atendente ou area esta responsavel pela conversa no momento.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'chatId', description: 'Identificador do chat no WhatsApp.' })
|
||||||
@Get('assignment/:chatId')
|
@Get('assignment/:chatId')
|
||||||
async getAssignment(@Param('chatId') chatId: string) {
|
async getAssignment(@Param('chatId') chatId: string) {
|
||||||
return this.assignmentService.getAssignment(chatId);
|
return this.assignmentService.getAssignment(chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Lista templates de atendimento',
|
||||||
|
description: 'Retorna os templates cadastrados para abertura ativa e mensagens padronizadas.',
|
||||||
|
})
|
||||||
@Get('templates')
|
@Get('templates')
|
||||||
async getTemplates() {
|
async getTemplates() {
|
||||||
return this.whatsappService.getTemplates();
|
return this.whatsappService.getTemplates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Cria template de mensagem',
|
||||||
|
description: 'Cadastra um template para abertura ativa ou resposta padronizada. Pode depender de aprovacao conforme o perfil solicitante.',
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'content'],
|
||||||
|
properties: {
|
||||||
|
name: { type: 'string', example: 'Boas vindas' },
|
||||||
|
content: { type: 'string', example: 'Ola {{nome}}, tudo bem?' },
|
||||||
|
areaId: { type: 'number', nullable: true, example: 2 },
|
||||||
|
requestedByRole: { type: 'string', example: 'admin' },
|
||||||
|
category: { type: 'string', example: 'atendimento' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@Post('templates')
|
@Post('templates')
|
||||||
async saveTemplate(@Body() body: { name: string; content: string; areaId?: number | null; requestedByRole?: string; category?: string }) {
|
async saveTemplate(@Body() body: { name: string; content: string; areaId?: number | null; requestedByRole?: string; category?: string }) {
|
||||||
return this.whatsappService.saveTemplate(body.name, body.content, body.areaId, body.requestedByRole, body.category);
|
return this.whatsappService.saveTemplate(body.name, body.content, body.areaId, body.requestedByRole, body.category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Atualiza template de mensagem',
|
||||||
|
description: 'Altera nome, conteudo, area ou categoria de um template existente.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
||||||
@Post('templates/update/:id')
|
@Post('templates/update/:id')
|
||||||
async updateTemplate(@Param('id') id: string, @Body() body: { name: string; content: string; areaId?: number | null; category?: string }) {
|
async updateTemplate(@Param('id') id: string, @Body() body: { name: string; content: string; areaId?: number | null; category?: string }) {
|
||||||
return this.whatsappService.updateTemplate(Number(id), body.name, body.content, body.areaId, body.category);
|
return this.whatsappService.updateTemplate(Number(id), body.name, body.content, body.areaId, body.category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Aprova template pelo admin',
|
||||||
|
description: 'Marca um template como aprovado para uso nos fluxos de atendimento.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
||||||
@Post('templates/approve-admin/:id')
|
@Post('templates/approve-admin/:id')
|
||||||
async approveTemplateByAdmin(@Param('id') id: string) {
|
async approveTemplateByAdmin(@Param('id') id: string) {
|
||||||
return this.whatsappService.approveTemplateByAdmin(Number(id));
|
return this.whatsappService.approveTemplateByAdmin(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Rejeita template pelo admin',
|
||||||
|
description: 'Marca um template como rejeitado, impedindo seu uso nos fluxos de atendimento.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
||||||
@Post('templates/reject-admin/:id')
|
@Post('templates/reject-admin/:id')
|
||||||
async rejectTemplateByAdmin(@Param('id') id: string) {
|
async rejectTemplateByAdmin(@Param('id') id: string) {
|
||||||
return this.whatsappService.rejectTemplateByAdmin(Number(id));
|
return this.whatsappService.rejectTemplateByAdmin(Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Remove template',
|
||||||
|
description: 'Exclui um template cadastrado.',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'id', description: 'ID do template.' })
|
||||||
@Delete('templates/:id')
|
@Delete('templates/:id')
|
||||||
async deleteTemplate(@Param('id') id: string) {
|
async deleteTemplate(@Param('id') id: string) {
|
||||||
return this.whatsappService.deleteTemplate(Number(id));
|
return this.whatsappService.deleteTemplate(Number(id));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user