swagger configurado na rota chavenfe
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src"
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -6292,7 +6292,6 @@
|
|||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz",
|
||||||
"integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==",
|
"integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ioredis/commands": "^1.1.1",
|
"@ioredis/commands": "^1.1.1",
|
||||||
"cluster-key-slot": "^1.1.0",
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
|||||||
@@ -10,7 +10,19 @@ import { UsersService } from '../users/users.service';
|
|||||||
import { LoginDto } from '../auth/dto/login.dto';
|
import { LoginDto } from '../auth/dto/login.dto';
|
||||||
import { ResultModel } from 'src/core/models/result.model';
|
import { ResultModel } from 'src/core/models/result.model';
|
||||||
import { ApiBody, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger';
|
import { ApiBody, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { ApiOperation } from '@nestjs/swagger';
|
||||||
|
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { ApiCreatedResponse } from '@nestjs/swagger';
|
||||||
|
import { ApiBadRequestResponse } from '@nestjs/swagger';
|
||||||
|
import { ApiNotFoundResponse } from '@nestjs/swagger';
|
||||||
|
import { ApiInternalServerErrorResponse } from '@nestjs/swagger';
|
||||||
|
import { ApiForbiddenResponse } from '@nestjs/swagger';
|
||||||
|
import { LoginResponseDto } from './dto/LoginResponseDto';
|
||||||
|
|
||||||
|
|
||||||
|
@ApiTags('Auth')
|
||||||
@Controller('api/v1/auth')
|
@Controller('api/v1/auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -19,17 +31,19 @@ export class AuthController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
|
@ApiOperation({ summary: 'Realiza login e retorna um token JWT' })
|
||||||
@ApiBody({ type: LoginDto })
|
@ApiBody({ type: LoginDto })
|
||||||
@ApiOkResponse({ description: 'Login realizado com sucesso' })
|
@ApiOkResponse({
|
||||||
|
description: 'Login realizado com sucesso',
|
||||||
|
type: LoginResponseDto,
|
||||||
|
})
|
||||||
@ApiUnauthorizedResponse({ description: 'Usuário ou senha inválidos' })
|
@ApiUnauthorizedResponse({ description: 'Usuário ou senha inválidos' })
|
||||||
async login(@Body() model: LoginDto): Promise<any> {
|
async login(@Body() model: LoginDto): Promise<LoginResponseDto> {
|
||||||
const result = await this.usersService.authenticate({
|
const result = await this.usersService.authenticate({
|
||||||
userName: model.username,
|
userName: model.username,
|
||||||
password: model.password,
|
password: model.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Resultado da autenticação:', result);
|
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
new ResultModel(false, result.error, null, result.error),
|
new ResultModel(false, result.error, null, result.error),
|
||||||
@@ -37,7 +51,6 @@ if (!result.success) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const user = result.data;
|
const user = result.data;
|
||||||
|
|
||||||
const token = await this.authService.createToken(
|
const token = await this.authService.createToken(
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthController } from './auth.controller';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import { JwtModule, JwtService } from '@nestjs/jwt';
|
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { UsersModule } from '../users/users.module';
|
import { AuthService } from './auth.service';
|
||||||
import { JwtStrategy } from '../strategies/jwt-strategy';
|
import { JwtStrategy } from '../strategies/jwt-strategy';
|
||||||
|
import { RedisModule } from 'src/core/configs/cache/redis.module';
|
||||||
|
import { UsersModule } from '../users/users.module';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
UsersModule,
|
|
||||||
PassportModule.register({
|
|
||||||
defaultStrategy: 'jwt',
|
|
||||||
}),
|
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9',
|
secret: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9',
|
||||||
signOptions: {
|
signOptions: { expiresIn: '8h' },
|
||||||
expiresIn: 3600,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||||
|
RedisModule,
|
||||||
|
UsersModule,
|
||||||
|
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, JwtStrategy],
|
providers: [AuthService, JwtStrategy],
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { JwtService, JwtSignOptions } from '@nestjs/jwt';
|
import { JwtService, JwtSignOptions } from '@nestjs/jwt';
|
||||||
import { UsersService } from '../users/users.service';
|
import { UsersService } from '../users/users.service';
|
||||||
import { JwtPayload } from '../models/jwt-payload.model';
|
import { JwtPayload } from '../models/jwt-payload.model';
|
||||||
|
import Redis from 'ioredis';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
82
src/auth/auth/dist/auth.service.js
vendored
82
src/auth/auth/dist/auth.service.js
vendored
@@ -1,82 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
||||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
||||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
||||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
||||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
||||||
};
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
||||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
||||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
||||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
||||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
||||||
function step(op) {
|
|
||||||
if (f) throw new TypeError("Generator is already executing.");
|
|
||||||
while (_) try {
|
|
||||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
||||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
||||||
switch (op[0]) {
|
|
||||||
case 0: case 1: t = op; break;
|
|
||||||
case 4: _.label++; return { value: op[1], done: false };
|
|
||||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
||||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
||||||
default:
|
|
||||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
||||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
||||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
||||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
||||||
if (t[2]) _.ops.pop();
|
|
||||||
_.trys.pop(); continue;
|
|
||||||
}
|
|
||||||
op = body.call(thisArg, _);
|
|
||||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
||||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
exports.__esModule = true;
|
|
||||||
exports.AuthService = void 0;
|
|
||||||
/* eslint-disable prettier/prettier */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
var common_1 = require("@nestjs/common");
|
|
||||||
var AuthService = /** @class */ (function () {
|
|
||||||
function AuthService(usersService, jwtService) {
|
|
||||||
this.usersService = usersService;
|
|
||||||
this.jwtService = jwtService;
|
|
||||||
}
|
|
||||||
AuthService.prototype.createToken = function (id, sellerId, username, email, storeId) {
|
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
|
||||||
var user, options;
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
user = {
|
|
||||||
id: id,
|
|
||||||
sellerId: sellerId,
|
|
||||||
storeId: storeId,
|
|
||||||
username: username,
|
|
||||||
email: email
|
|
||||||
};
|
|
||||||
options = { expiresIn: '8h' };
|
|
||||||
return [2 /*return*/, this.jwtService.sign(user, options)];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
AuthService.prototype.validateUser = function (payload) {
|
|
||||||
return __awaiter(this, void 0, Promise, function () {
|
|
||||||
return __generator(this, function (_a) {
|
|
||||||
return [2 /*return*/, payload];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
AuthService = __decorate([
|
|
||||||
common_1.Injectable()
|
|
||||||
], AuthService);
|
|
||||||
return AuthService;
|
|
||||||
}());
|
|
||||||
exports.AuthService = AuthService;
|
|
||||||
12
src/auth/auth/dto/LoginResponseDto.ts
Normal file
12
src/auth/auth/dto/LoginResponseDto.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// login-response.dto.ts
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class LoginResponseDto {
|
||||||
|
@ApiProperty() id: number;
|
||||||
|
@ApiProperty() sellerId: number;
|
||||||
|
@ApiProperty() name: string;
|
||||||
|
@ApiProperty() username: string;
|
||||||
|
@ApiProperty() storeId: string;
|
||||||
|
@ApiProperty() email: string;
|
||||||
|
@ApiProperty() token: string;
|
||||||
|
}
|
||||||
118
src/auth/doc/login.fluxo.html
Normal file
118
src/auth/doc/login.fluxo.html
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Documentação - Fluxo de Login</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #007acc;
|
||||||
|
}
|
||||||
|
code, pre {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #007acc;
|
||||||
|
color: white;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🔐 Fluxo de Login - Portal Juru API</h1>
|
||||||
|
|
||||||
|
<h2>📌 Rota de Login</h2>
|
||||||
|
<p><strong>URL:</strong> <code>POST /api/v1/auth/login</code></p>
|
||||||
|
<p><strong>Descrição:</strong> Autentica o usuário, valida regras de negócio e retorna um token JWT.</p>
|
||||||
|
<p><strong>Acesso:</strong> Público</p>
|
||||||
|
|
||||||
|
<h3>📤 Body (JSON)</h3>
|
||||||
|
<pre>{
|
||||||
|
"username": "joelson.r",
|
||||||
|
"password": "1010"
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<h3>✅ Resposta (200 OK)</h3>
|
||||||
|
<pre>{
|
||||||
|
"id": 1498,
|
||||||
|
"sellerId": 2013,
|
||||||
|
"name": "JOELSON DE BRITO RIBEIRO",
|
||||||
|
"username": "JOELSON DE BRITO RIBEIRO",
|
||||||
|
"storeId": "4",
|
||||||
|
"email": "JOELSON.R@JURUNENSE.COM.BR",
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5..."
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<h3>❌ Resposta (401 Unauthorized)</h3>
|
||||||
|
<pre>{
|
||||||
|
"success": false,
|
||||||
|
"message": "Usuário ou senha inválidos.",
|
||||||
|
"data": null,
|
||||||
|
"error": "Usuário ou senha inválidos."
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<h2>🧱 Camadas e Responsabilidades</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>Camada</th><th>Responsabilidade</th></tr>
|
||||||
|
<tr><td>AuthController</td><td>Recebe requisição e coordena autenticação</td></tr>
|
||||||
|
<tr><td>UsersService</td><td>Orquestra os casos de uso (login, reset, troca senha)</td></tr>
|
||||||
|
<tr><td>AuthenticateUserService</td><td>Executa lógica de autenticação e validações</td></tr>
|
||||||
|
<tr><td>UserRepository</td><td>Executa SQL diretamente no Oracle</td></tr>
|
||||||
|
<tr><td>AuthService</td><td>Gera o token JWT com os dados do usuário</td></tr>
|
||||||
|
<tr><td>JwtStrategy</td><td>Valida o token em rotas protegidas, usando Redis como cache</td></tr>
|
||||||
|
<tr><td>RedisClientAdapter</td><td>Wrapper de acesso ao Redis com interface genérica e TTL</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>🧊 Redis na Autenticação</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Chave:</strong> <code>session:{userId}</code></li>
|
||||||
|
<li><strong>Valor:</strong> JSON serializado do usuário autenticado</li>
|
||||||
|
<li><strong>TTL:</strong> 8 horas</li>
|
||||||
|
<li><strong>Fallback:</strong> Se o cache não existir, consulta ao banco</li>
|
||||||
|
<li><strong>Auditoria:</strong> espaço reservado para log de acesso</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>🔐 Proteção de Rotas</h2>
|
||||||
|
<p>Rotas protegidas utilizam <code>@UseGuards(JwtAuthGuard)</code> e <code>@ApiBearerAuth()</code>.</p>
|
||||||
|
<p><strong>Header necessário:</strong></p>
|
||||||
|
<code>Authorization: Bearer <token></code>
|
||||||
|
|
||||||
|
<h2>🚀 Melhorias Futuras</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[ ] Blacklist de tokens para logout</li>
|
||||||
|
<li>[ ] Log de auditoria</li>
|
||||||
|
<li>[ ] Refresh Token</li>
|
||||||
|
<li>[ ] Controle de permissões/roles</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Última atualização:</strong> 28/03/2025</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
// ✅ jwt.strategy.ts
|
||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
import { JwtPayload } from '../models/jwt-payload.model';
|
import { JwtPayload } from '../models/jwt-payload.model';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { UserRepository } from '../../auth/users/UserRepository';
|
||||||
|
import { RedisClientToken } from '../../core/configs/cache/redis-client.adapter.provider';
|
||||||
|
import { IRedisClient } from '../../core/configs/cache/IRedisClient';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
constructor(private readonly authService: AuthService) {
|
constructor(
|
||||||
|
@Inject(RedisClientToken) private readonly redis: IRedisClient,
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
|
) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
secretOrKey: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9',
|
secretOrKey: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9',
|
||||||
@@ -15,10 +20,24 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: JwtPayload) {
|
async validate(payload: JwtPayload) {
|
||||||
const user = await this.authService.validateUser(payload);
|
const sessionKey = `session:${payload.id}`;
|
||||||
if (!user) {
|
const user = await this.redis.get<any>(sessionKey);
|
||||||
throw new UnauthorizedException();
|
|
||||||
}
|
if (user) {
|
||||||
|
// Audit log placeholder
|
||||||
|
// await this.auditAccess(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userDb = await this.userRepository.findById(payload.id);
|
||||||
|
if (!userDb || userDb.situacao === 'I' || userDb.dataDesligamento) {
|
||||||
|
throw new UnauthorizedException('Usuário inválido ou inativo');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.redis.set(sessionKey, userDb, 60 * 60 * 8);
|
||||||
|
// Audit fallback
|
||||||
|
// await this.auditAccess(userDb, 'fallback');
|
||||||
|
|
||||||
|
return userDb;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { InjectDataSource } from '@nestjs/typeorm';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserRepository {
|
export class UserRepository {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectDataSource('oracle') // especifica que queremos o DataSource da conexão 'oracle'
|
@InjectDataSource('oracle')
|
||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -61,4 +61,16 @@ export class UserRepository {
|
|||||||
const result = await this.dataSource.query(sql, [sellerId, passwordHash]);
|
const result = await this.dataSource.query(sql, [sellerId, passwordHash]);
|
||||||
return result[0] || null;
|
return result[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findById(id: number) {
|
||||||
|
const sql = `
|
||||||
|
SELECT MATRICULA AS "id", NOME AS "name", CODUSUR AS "sellerId",
|
||||||
|
CODFILIAL AS "storeId", EMAIL AS "email",
|
||||||
|
DTDEMISSAO as "dataDesligamento", SITUACAO as "situacao"
|
||||||
|
FROM PCEMPR
|
||||||
|
WHERE MATRICULA = :1
|
||||||
|
`;
|
||||||
|
const result = await this.dataSource.query(sql, [id]);
|
||||||
|
return result[0] || null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ import { EmailService } from './email.service';
|
|||||||
ChangePasswordService,
|
ChangePasswordService,
|
||||||
EmailService,
|
EmailService,
|
||||||
],
|
],
|
||||||
exports: [UsersService],
|
exports: [UsersService,UserRepository],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
|||||||
145
src/core/configs/cache/index.html
vendored
Normal file
145
src/core/configs/cache/index.html
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Documentação - Integração Redis</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #007acc;
|
||||||
|
}
|
||||||
|
code, pre {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #007acc;
|
||||||
|
color: white;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>📦 Integração Redis com Abstração - Portal Juru API</h1>
|
||||||
|
|
||||||
|
<h2>🧱 Arquitetura</h2>
|
||||||
|
<p>O projeto utiliza o Redis com uma interface genérica para garantir desacoplamento, facilidade de teste e reaproveitamento em múltiplos módulos.</p>
|
||||||
|
|
||||||
|
<h3>🔌 Interface IRedisClient</h3>
|
||||||
|
<pre><code>export interface IRedisClient {
|
||||||
|
get<T>(key: string): Promise<T | null>;
|
||||||
|
set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
|
||||||
|
del(key: string): Promise<void>;
|
||||||
|
}</code></pre>
|
||||||
|
|
||||||
|
<h3>🧩 Provider REDIS_CLIENT</h3>
|
||||||
|
<p>Faz a conexão direta com o Redis usando a biblioteca <code>ioredis</code> e o <code>ConfigService</code> para pegar host e porta.</p>
|
||||||
|
<pre><code>export const RedisProvider: Provider = {
|
||||||
|
provide: 'REDIS_CLIENT',
|
||||||
|
useFactory: (configService: ConfigService) => {
|
||||||
|
const redis = new Redis({
|
||||||
|
host: configService.get('REDIS_HOST', '10.1.1.109'),
|
||||||
|
port: configService.get('REDIS_PORT', 6379),
|
||||||
|
});
|
||||||
|
|
||||||
|
redis.on('error', (err) => {
|
||||||
|
console.error('Erro ao conectar ao Redis:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return redis;
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
};</code></pre>
|
||||||
|
|
||||||
|
<h3>📦 RedisClientAdapter (Wrapper)</h3>
|
||||||
|
<p>Classe que implementa <code>IRedisClient</code> e encapsula as operações de cache. É injetada em serviços via token.</p>
|
||||||
|
<pre><code>@Injectable()
|
||||||
|
export class RedisClientAdapter implements IRedisClient {
|
||||||
|
constructor(@Inject('REDIS_CLIENT') private readonly redis: Redis) {}
|
||||||
|
|
||||||
|
async get<T>(key: string): Promise<T | null> {
|
||||||
|
const data = await this.redis.get(key);
|
||||||
|
return data ? JSON.parse(data) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async set<T>(key: string, value: T, ttlSeconds = 300): Promise<void> {
|
||||||
|
await this.redis.set(key, JSON.stringify(value), 'EX', ttlSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(key: string): Promise<void> {
|
||||||
|
await this.redis.del(key);
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
|
||||||
|
<h3>🔗 Token e Provider</h3>
|
||||||
|
<p>Token de injeção definido para o adapter:</p>
|
||||||
|
<pre><code>export const RedisClientToken = 'RedisClientInterface';
|
||||||
|
|
||||||
|
export const RedisClientAdapterProvider = {
|
||||||
|
provide: RedisClientToken,
|
||||||
|
useClass: RedisClientAdapter,
|
||||||
|
};</code></pre>
|
||||||
|
|
||||||
|
<h3>📦 Módulo Global RedisModule</h3>
|
||||||
|
<p>Torna o Redis disponível em toda a aplicação.</p>
|
||||||
|
<pre><code>@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
providers: [RedisProvider, RedisClientAdapterProvider],
|
||||||
|
exports: [RedisProvider, RedisClientAdapterProvider],
|
||||||
|
})
|
||||||
|
export class RedisModule {}</code></pre>
|
||||||
|
|
||||||
|
<h2>🧠 Uso em Serviços</h2>
|
||||||
|
<p>Injetando o cache no seu service:</p>
|
||||||
|
<pre><code>constructor(
|
||||||
|
@Inject(RedisClientToken)
|
||||||
|
private readonly redisClient: IRedisClient
|
||||||
|
) {}</code></pre>
|
||||||
|
|
||||||
|
<p>Uso típico:</p>
|
||||||
|
<pre><code>const data = await this.redisClient.get<T>('chave');
|
||||||
|
if (!data) {
|
||||||
|
const result = await fetchFromDb();
|
||||||
|
await this.redisClient.set('chave', result, 3600);
|
||||||
|
}</code></pre>
|
||||||
|
|
||||||
|
<h2>🧰 Boas práticas</h2>
|
||||||
|
<ul>
|
||||||
|
<li>✅ TTL por recurso (ex: produtos: 1h, lojas: 24h)</li>
|
||||||
|
<li>✅ Nomear chaves com prefixos por domínio (ex: <code>data-consult:sellers</code>)</li>
|
||||||
|
<li>✅ Centralizar helpers como <code>getOrSetCache</code> para evitar repetição</li>
|
||||||
|
<li>✅ Usar <code>JSON.stringify</code> e <code>JSON.parse</code> no adapter</li>
|
||||||
|
<li>✅ Marcar módulo como <code>@Global()</code> para acesso em toda a aplicação</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Última atualização:</strong> 29/03/2025</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2
src/core/configs/cache/redis.module.ts
vendored
2
src/core/configs/cache/redis.module.ts
vendored
@@ -9,4 +9,4 @@ import { RedisClientAdapterProvider } from './redis-client.adapter.provider';
|
|||||||
providers: [RedisProvider, RedisClientAdapterProvider],
|
providers: [RedisProvider, RedisClientAdapterProvider],
|
||||||
exports: [RedisProvider, RedisClientAdapterProvider],
|
exports: [RedisProvider, RedisClientAdapterProvider],
|
||||||
})
|
})
|
||||||
export class CacheModule {}
|
export class RedisModule {}
|
||||||
@@ -4,13 +4,15 @@ import { DataConsultController } from './data-consult.controller';
|
|||||||
import { DataConsultRepository } from './data-consult.repository';
|
import { DataConsultRepository } from './data-consult.repository';
|
||||||
import { LoggerModule } from 'src/Log/logger.module';
|
import { LoggerModule } from 'src/Log/logger.module';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { RedisModule } from 'src/core/configs/cache/redis.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [LoggerModule, ConfigModule],
|
imports: [LoggerModule, ConfigModule, RedisModule],
|
||||||
controllers: [DataConsultController],
|
controllers: [DataConsultController],
|
||||||
providers: [
|
providers: [
|
||||||
DataConsultService,
|
DataConsultService,
|
||||||
DataConsultRepository,
|
DataConsultRepository,
|
||||||
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DataConsultModule {}
|
export class DataConsultModule {}
|
||||||
|
|||||||
@@ -47,11 +47,13 @@ export class DataConsultRepository {
|
|||||||
FROM PCUSUARI
|
FROM PCUSUARI
|
||||||
WHERE PCUSUARI.DTTERMINO IS NULL
|
WHERE PCUSUARI.DTTERMINO IS NULL
|
||||||
AND PCUSUARI.TIPOVEND NOT IN ('P')
|
AND PCUSUARI.TIPOVEND NOT IN ('P')
|
||||||
|
AND (PCUSUARI.BLOQUEIO IS NULL OR PCUSUARI.BLOQUEIO = 'N')
|
||||||
ORDER BY PCUSUARI.NOME
|
ORDER BY PCUSUARI.NOME
|
||||||
`;
|
`;
|
||||||
return this.executeQuery<SellerDto[]>(sql);
|
return this.executeQuery<SellerDto[]>(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async findBillings(): Promise<BillingDto[]> {
|
async findBillings(): Promise<BillingDto[]> {
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT PCCOB.CODCOB as "id",
|
SELECT PCCOB.CODCOB as "id",
|
||||||
|
|||||||
@@ -6,13 +6,24 @@ import { BillingDto } from './dto/billing.dto';
|
|||||||
import { CustomerDto } from './dto/customer.dto';
|
import { CustomerDto } from './dto/customer.dto';
|
||||||
import { ProductDto } from './dto/product.dto';
|
import { ProductDto } from './dto/product.dto';
|
||||||
import { ILogger } from '../Log/ILogger';
|
import { ILogger } from '../Log/ILogger';
|
||||||
|
import { RedisClientToken } from '../core/configs/cache/redis-client.adapter.provider';
|
||||||
|
import { IRedisClient } from '../core/configs/cache/IRedisClient';
|
||||||
|
import { getOrSetCache } from '../shared/cache.util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DataConsultService {
|
export class DataConsultService {
|
||||||
|
|
||||||
|
private readonly SELLERS_CACHE_KEY = 'data-consult:sellers';
|
||||||
|
private readonly SELLERS_TTL = 3600; // 1 hora
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly repository: DataConsultRepository,
|
private readonly repository: DataConsultRepository,
|
||||||
|
@Inject(RedisClientToken) private readonly redisClient: IRedisClient,
|
||||||
@Inject('LoggerService')
|
@Inject('LoggerService')
|
||||||
private readonly logger: ILogger
|
private readonly logger: ILogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,9 +40,17 @@ export class DataConsultService {
|
|||||||
* @returns Array de SellerDto
|
* @returns Array de SellerDto
|
||||||
*/
|
*/
|
||||||
async sellers(): Promise<SellerDto[]> {
|
async sellers(): Promise<SellerDto[]> {
|
||||||
this.logger.log('Buscando todos os vendedores');
|
this.logger.log('Buscando vendedores com cache Redis...');
|
||||||
|
return getOrSetCache<SellerDto[]>(
|
||||||
|
this.redisClient,
|
||||||
|
this.SELLERS_CACHE_KEY,
|
||||||
|
this.SELLERS_TTL,
|
||||||
|
async () => {
|
||||||
|
this.logger.log('Cache de vendedores vazio. Buscando no banco...');
|
||||||
return this.repository.findSellers();
|
return this.repository.findSellers();
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obter todos os faturamentos
|
* Obter todos os faturamentos
|
||||||
|
|||||||
114
src/data-consult/doc.html
Normal file
114
src/data-consult/doc.html
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Documentação - DataConsult Module</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #007acc;
|
||||||
|
}
|
||||||
|
code, pre {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: #007acc;
|
||||||
|
color: white;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>📊 Módulo de Consultas - Portal Juru API</h1>
|
||||||
|
|
||||||
|
<h2>📌 Endpoints Disponíveis</h2>
|
||||||
|
|
||||||
|
<h3>🔹 GET /api/v1/data-consult/stores</h3>
|
||||||
|
<p><strong>Descrição:</strong> Lista todas as lojas.</p>
|
||||||
|
<p><strong>Autenticação:</strong> <span class="tag">JWT</span></p>
|
||||||
|
<code>Retorna: StoreDto[]</code>
|
||||||
|
|
||||||
|
<h3>🔹 GET /api/v1/data-consult/sellers</h3>
|
||||||
|
<p><strong>Descrição:</strong> Lista todos os vendedores ativos.</p>
|
||||||
|
<p><strong>Autenticação:</strong> <span class="tag">JWT</span></p>
|
||||||
|
<code>Retorna: SellerDto[]</code>
|
||||||
|
|
||||||
|
<h3>🔹 GET /api/v1/data-consult/billings</h3>
|
||||||
|
<p><strong>Descrição:</strong> Retorna todos os tipos de faturamento válidos.</p>
|
||||||
|
<p><strong>Autenticação:</strong> <span class="tag">JWT</span></p>
|
||||||
|
<code>Retorna: BillingDto[]</code>
|
||||||
|
|
||||||
|
<h3>🔹 GET /api/v1/data-consult/customers/:filter</h3>
|
||||||
|
<p><strong>Descrição:</strong> Busca clientes por nome, código ou CPF/CNPJ.</p>
|
||||||
|
<p><strong>Parâmetro:</strong> <code>filter</code></p>
|
||||||
|
<p><strong>Autenticação:</strong> <span class="tag">JWT</span></p>
|
||||||
|
<code>Retorna: CustomerDto[]</code>
|
||||||
|
|
||||||
|
<h3>🔹 GET /api/v1/data-consult/products/:filter</h3>
|
||||||
|
<p><strong>Descrição:</strong> Busca produtos por descrição, código ou código auxiliar.</p>
|
||||||
|
<p><strong>Parâmetro:</strong> <code>filter</code></p>
|
||||||
|
<p><strong>Autenticação:</strong> Não requer</p>
|
||||||
|
<code>Retorna: ProductDto[]</code>
|
||||||
|
|
||||||
|
<h2>🧱 Camadas e Responsabilidades</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>Camada</th><th>Responsabilidade</th></tr>
|
||||||
|
<tr><td>DataConsultController</td><td>Recebe as requisições HTTP e delega para o service</td></tr>
|
||||||
|
<tr><td>DataConsultService</td><td>Intermediário entre controller e repositório; adiciona logs e trata exceções</td></tr>
|
||||||
|
<tr><td>DataConsultRepository</td><td>Executa queries SQL no Oracle via TypeORM</td></tr>
|
||||||
|
<tr><td>LoggerService</td><td>Registra logs de acesso, sucesso ou erro</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>📦 Detalhes Técnicos</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Utiliza <strong>Oracle</strong> como banco de dados com configuração customizada.</li>
|
||||||
|
<li>As buscas por cliente e produto realizam múltiplas tentativas de match (ex: por código, nome, etc.).</li>
|
||||||
|
<li>Repositório implementa <code>queryRunner</code> com liberação segura de conexão.</li>
|
||||||
|
<li>Camada de serviço registra log com <code>ILogger</code> em todas as operações.</li>
|
||||||
|
<li>Erros no endpoint <code>products</code> são tratados com <code>HttpException</code>.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>🔐 Segurança</h2>
|
||||||
|
<p>Endpoints protegidos utilizam <code>@UseGuards(JwtAuthGuard)</code> e <code>@ApiBearerAuth()</code>.</p>
|
||||||
|
<p><strong>Header necessário:</strong></p>
|
||||||
|
<code>Authorization: Bearer <token></code>
|
||||||
|
|
||||||
|
<h2>🚀 Melhorias Futuras</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[🔹 ] Cache Redis para lojas, vendedores e faturamentos</li>
|
||||||
|
<li>[ ] Auditoria detalhada de acessos no logger</li>
|
||||||
|
<li>[ ] Paginação nas buscas de clientes e produtos</li>
|
||||||
|
<li>[ ] Endpoint para exportação dos dados em CSV</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Última atualização:</strong> 29/03/2025</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
79
src/logistic/dist/logistic.controller.js
vendored
79
src/logistic/dist/logistic.controller.js
vendored
@@ -1,79 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
/* eslint-disable prettier/prettier */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
/*
|
|
||||||
https://docs.nestjs.com/controllers#controllers
|
|
||||||
*/
|
|
||||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
||||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
||||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
||||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
||||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
||||||
};
|
|
||||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
||||||
return function (target, key) { decorator(target, key, paramIndex); }
|
|
||||||
};
|
|
||||||
exports.__esModule = true;
|
|
||||||
exports.LogisticController = void 0;
|
|
||||||
var common_1 = require("@nestjs/common");
|
|
||||||
var jwt_auth_guard_1 = require("src/auth/guards/jwt-auth.guard"); // ajuste o caminho se necessário
|
|
||||||
var swagger_1 = require("@nestjs/swagger");
|
|
||||||
var LogisticController = /** @class */ (function () {
|
|
||||||
function LogisticController(logisticService) {
|
|
||||||
this.logisticService = logisticService;
|
|
||||||
}
|
|
||||||
LogisticController.prototype.getExpedicao = function () {
|
|
||||||
return this.logisticService.getExpedicao();
|
|
||||||
};
|
|
||||||
LogisticController.prototype.getEmployee = function () {
|
|
||||||
return this.logisticService.getEmployee();
|
|
||||||
};
|
|
||||||
LogisticController.prototype.getDelivery = function (placa) {
|
|
||||||
return this.logisticService.getDeliveries(placa);
|
|
||||||
};
|
|
||||||
LogisticController.prototype.getStatusCar = function (placa) {
|
|
||||||
return this.logisticService.getStatusCar(placa);
|
|
||||||
};
|
|
||||||
LogisticController.prototype.createOutCar = function (data) {
|
|
||||||
return this.logisticService.createCarOut(data);
|
|
||||||
};
|
|
||||||
LogisticController.prototype.createinCar = function (data) {
|
|
||||||
return this.logisticService.createCarIn(data);
|
|
||||||
};
|
|
||||||
__decorate([
|
|
||||||
common_1.Get('expedicao'),
|
|
||||||
swagger_1.ApiOperation({ summary: 'Retorna informações de expedição' })
|
|
||||||
], LogisticController.prototype, "getExpedicao");
|
|
||||||
__decorate([
|
|
||||||
common_1.Get('employee'),
|
|
||||||
swagger_1.ApiOperation({ summary: 'Retorna lista de funcionários' })
|
|
||||||
], LogisticController.prototype, "getEmployee");
|
|
||||||
__decorate([
|
|
||||||
common_1.Get('deliveries/:placa'),
|
|
||||||
swagger_1.ApiOperation({ summary: 'Retorna entregas por placa' }),
|
|
||||||
__param(0, common_1.Param('placa'))
|
|
||||||
], LogisticController.prototype, "getDelivery");
|
|
||||||
__decorate([
|
|
||||||
common_1.Get('status-car/:placa'),
|
|
||||||
swagger_1.ApiOperation({ summary: 'Retorna status do veículo por placa' }),
|
|
||||||
__param(0, common_1.Param('placa'))
|
|
||||||
], LogisticController.prototype, "getStatusCar");
|
|
||||||
__decorate([
|
|
||||||
common_1.Post('create'),
|
|
||||||
swagger_1.ApiOperation({ summary: 'Registra saída de veículo' }),
|
|
||||||
__param(0, common_1.Body())
|
|
||||||
], LogisticController.prototype, "createOutCar");
|
|
||||||
__decorate([
|
|
||||||
common_1.Post('return-car'),
|
|
||||||
swagger_1.ApiOperation({ summary: 'Registra retorno de veículo' }),
|
|
||||||
__param(0, common_1.Body())
|
|
||||||
], LogisticController.prototype, "createinCar");
|
|
||||||
LogisticController = __decorate([
|
|
||||||
swagger_1.ApiTags('Logística'),
|
|
||||||
swagger_1.ApiBearerAuth(),
|
|
||||||
common_1.UseGuards(jwt_auth_guard_1.JwtAuthGuard),
|
|
||||||
common_1.Controller('api/v1/logistic')
|
|
||||||
], LogisticController);
|
|
||||||
return LogisticController;
|
|
||||||
}());
|
|
||||||
exports.LogisticController = LogisticController;
|
|
||||||
21
src/orders/application/invoice.service.ts
Normal file
21
src/orders/application/invoice.service.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { InvoiceRepository } from '../repositories/invoice.repository';
|
||||||
|
import { InvoiceDto } from '../dto/invoice.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class InvoiceService {
|
||||||
|
constructor(private readonly invoiceRepo: InvoiceRepository) {}
|
||||||
|
|
||||||
|
async getInvoiceByChave(chavenfe: string): Promise<InvoiceDto> {
|
||||||
|
const invoice = await this.invoiceRepo.findInvoiceByChave(chavenfe);
|
||||||
|
if (!invoice) {
|
||||||
|
throw new NotFoundException('Nota fiscal não localizada.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const itens = await this.invoiceRepo.findInvoiceItems(invoice.transactionId);
|
||||||
|
return {
|
||||||
|
...invoice,
|
||||||
|
itens,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/orders/application/orders.service.ts
Normal file
6
src/orders/application/orders.service.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
///← Orquestra operações de pedido
|
||||||
|
|
||||||
|
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
|
||||||
25
src/orders/dto/invoice.dto.ts
Normal file
25
src/orders/dto/invoice.dto.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export class InvoiceItemDto {
|
||||||
|
productId: number;
|
||||||
|
productName: string;
|
||||||
|
package: string;
|
||||||
|
qt: number;
|
||||||
|
ean: string;
|
||||||
|
multiple: number;
|
||||||
|
productType: string;
|
||||||
|
image: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvoiceDto {
|
||||||
|
storeId: number;
|
||||||
|
invoiceDate: Date;
|
||||||
|
orderId: number;
|
||||||
|
invoiceId: number;
|
||||||
|
transactionId: number;
|
||||||
|
customerId: number;
|
||||||
|
customer: string;
|
||||||
|
sellerId: number;
|
||||||
|
sellerName: string;
|
||||||
|
itensQt: number;
|
||||||
|
itens: InvoiceItemDto[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,20 +7,35 @@ https://docs.nestjs.com/controllers#controllers
|
|||||||
|
|
||||||
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
||||||
import { OrdersService } from './orders.service';
|
import { OrdersService } from './orders.service';
|
||||||
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation } from '@nestjs/swagger';
|
||||||
|
import { InvoiceService } from './application/invoice.service';
|
||||||
|
import { InvoiceDto } from './dto/invoice.dto';
|
||||||
|
|
||||||
@Controller('api/v1/orders')
|
|
||||||
|
|
||||||
|
@ApiTags('Invoice')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@Controller('api/v1/invoice')
|
||||||
export class OrdersController {
|
export class OrdersController {
|
||||||
|
|
||||||
constructor( private readonly ordersService: OrdersService) {}
|
constructor(
|
||||||
|
private readonly ordersService: OrdersService,
|
||||||
|
private readonly invoiceService: InvoiceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
|
||||||
@Get('find')
|
@Get('find')
|
||||||
findOrders(@Query() query) {
|
findOrders(@Query() query) {
|
||||||
return this.ordersService.findOrders(query);
|
return this.ordersService.findOrders(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('invoice/:chavenfe')
|
@Get(':chavenfe')
|
||||||
findInvoice(@Param('chavenfe') chavenfe: string) {
|
async getByChave(@Param('chavenfe') chavenfe: string): Promise<InvoiceDto> {
|
||||||
return this.ordersService.findInvoice(chavenfe);
|
return this.invoiceService.getInvoiceByChave(chavenfe);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('itens/:id')
|
@Get('itens/:id')
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
/* eslint-disable prettier/prettier */
|
/* eslint-disable prettier/prettier */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
|
||||||
import { OrdersService } from './orders.service';
|
|
||||||
import { OrdersController } from './orders.controller';
|
|
||||||
/*
|
|
||||||
https://docs.nestjs.com/modules
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { OrdersService } from './orders.service';
|
||||||
|
import { InvoiceRepository } from './repositories/invoice.repository';
|
||||||
|
import { InvoiceService } from './application/invoice.service';
|
||||||
|
import { OrdersController } from './orders.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [
|
||||||
|
ConfigModule,
|
||||||
|
TypeOrmModule,
|
||||||
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
OrdersController,],
|
OrdersController,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
OrdersService,],
|
OrdersService,
|
||||||
|
InvoiceService,
|
||||||
|
InvoiceRepository,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class OrdersModule {}
|
export class OrdersModule {}
|
||||||
|
|||||||
78
src/orders/repositories/invoice.repository.ts
Normal file
78
src/orders/repositories/invoice.repository.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { InjectDataSource } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class InvoiceRepository {
|
||||||
|
constructor(
|
||||||
|
@InjectDataSource() private readonly dataSource: DataSource,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findInvoiceByChave(chavenfe: string) {
|
||||||
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
await queryRunner.connect();
|
||||||
|
try {
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
pcnfsaid.codfilial AS "storeId",
|
||||||
|
pcnfsaid.dtsaida AS "invoiceDate",
|
||||||
|
pcnfsaid.numped AS "orderId",
|
||||||
|
pcnfsaid.numnota AS "invoiceId",
|
||||||
|
pcnfsaid.numtransvenda AS "transactionId",
|
||||||
|
pcnfsaid.codcli AS "customerId",
|
||||||
|
pcclient.cliente AS "customer",
|
||||||
|
pcnfsaid.codusur AS "sellerId",
|
||||||
|
pcusuari.nome AS "sellerName",
|
||||||
|
(
|
||||||
|
SELECT SUM(pcmov.qt)
|
||||||
|
FROM pcmov
|
||||||
|
WHERE pcmov.numtransvenda = pcnfsaid.numtransvenda
|
||||||
|
) AS "itensQt",
|
||||||
|
NULL AS "itens"
|
||||||
|
FROM pcnfsaid, pcclient, pcusuari
|
||||||
|
WHERE
|
||||||
|
pcnfsaid.codcli = pcclient.codcli
|
||||||
|
AND pcnfsaid.codusur = pcusuari.codusur
|
||||||
|
AND pcnfsaid.chavenfe = :chavenfe
|
||||||
|
`;
|
||||||
|
return await queryRunner.manager.query(sql, [chavenfe]);
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findInvoiceItems(transactionId: number) {
|
||||||
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
await queryRunner.connect();
|
||||||
|
try {
|
||||||
|
const sql = `
|
||||||
|
SELECT
|
||||||
|
pcmov.codprod AS "productId",
|
||||||
|
pcprodut.descricao AS "productName",
|
||||||
|
pcprodut.embalagem AS "package",
|
||||||
|
pcmov.qt AS "qt",
|
||||||
|
pcprodut.codauxiliar AS "ean",
|
||||||
|
pcprodut.multiplo AS "multiple",
|
||||||
|
pcprodut.tipoproduto AS "productType",
|
||||||
|
REPLACE(
|
||||||
|
CASE
|
||||||
|
WHEN INSTR(PCPRODUT.URLIMAGEM, ';') > 0 THEN
|
||||||
|
SUBSTR(PCPRODUT.URLIMAGEM, 1, INSTR(PCPRODUT.URLIMAGEM, ';') - 1)
|
||||||
|
WHEN PCPRODUT.URLIMAGEM IS NOT NULL THEN
|
||||||
|
PCPRODUT.URLIMAGEM
|
||||||
|
ELSE NULL
|
||||||
|
END,
|
||||||
|
'167.249.211.178:8001',
|
||||||
|
'10.1.1.191'
|
||||||
|
) AS "image"
|
||||||
|
FROM pcmov, pcprodut
|
||||||
|
WHERE
|
||||||
|
pcmov.codprod = pcprodut.codprod
|
||||||
|
AND pcmov.numtransvenda = :transactionId
|
||||||
|
`;
|
||||||
|
return await queryRunner.manager.query(sql, [transactionId]);
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/orders/repositories/orders.repository.ts
Normal file
0
src/orders/repositories/orders.repository.ts
Normal file
15
src/shared/cache.util.ts
Normal file
15
src/shared/cache.util.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { IRedisClient } from '../core/configs/cache/IRedisClient';
|
||||||
|
|
||||||
|
export async function getOrSetCache<T>(
|
||||||
|
redisClient: IRedisClient,
|
||||||
|
key: string,
|
||||||
|
ttlSeconds: number,
|
||||||
|
fallback: () => Promise<T>
|
||||||
|
): Promise<T> {
|
||||||
|
const cached = await redisClient.get<T>(key);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const data = await fallback();
|
||||||
|
await redisClient.set<T>(key, data, ttlSeconds);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -10,6 +10,6 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"incremental": true
|
// "incremental": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user