swagger configurado na rota chavenfe
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz",
|
||||
"integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "^1.1.1",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
|
||||
@@ -10,7 +10,19 @@ import { UsersService } from '../users/users.service';
|
||||
import { LoginDto } from '../auth/dto/login.dto';
|
||||
import { ResultModel } from 'src/core/models/result.model';
|
||||
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')
|
||||
export class AuthController {
|
||||
constructor(
|
||||
@@ -19,24 +31,25 @@ export class AuthController {
|
||||
) {}
|
||||
|
||||
@Post('login')
|
||||
@ApiOperation({ summary: 'Realiza login e retorna um token JWT' })
|
||||
@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' })
|
||||
async login(@Body() model: LoginDto): Promise<any> {
|
||||
async login(@Body() model: LoginDto): Promise<LoginResponseDto> {
|
||||
const result = await this.usersService.authenticate({
|
||||
userName: model.username,
|
||||
password: model.password,
|
||||
});
|
||||
|
||||
console.log('Resultado da autenticação:', result);
|
||||
|
||||
if (!result.success) {
|
||||
throw new HttpException(
|
||||
new ResultModel(false, result.error, null, result.error),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
throw new HttpException(
|
||||
new ResultModel(false, result.error, null, result.error),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const user = result.data;
|
||||
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtModule, JwtService } from '@nestjs/jwt';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { AuthService } from './auth.service';
|
||||
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({
|
||||
imports: [
|
||||
UsersModule,
|
||||
PassportModule.register({
|
||||
defaultStrategy: 'jwt',
|
||||
}),
|
||||
JwtModule.register({
|
||||
secret: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9',
|
||||
signOptions: {
|
||||
expiresIn: 3600,
|
||||
},
|
||||
signOptions: { expiresIn: '8h' },
|
||||
}),
|
||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||
RedisModule,
|
||||
UsersModule,
|
||||
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService,JwtStrategy],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
exports: [AuthService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import { JwtService, JwtSignOptions } from '@nestjs/jwt';
|
||||
import { UsersService } from '../users/users.service';
|
||||
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 */
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
// ✅ jwt.strategy.ts
|
||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
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()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private readonly authService: AuthService) {
|
||||
constructor(
|
||||
@Inject(RedisClientToken) private readonly redis: IRedisClient,
|
||||
private readonly userRepository: UserRepository,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: '4557C0D7-DFB0-40DA-BF83-91A75103F7A9',
|
||||
@@ -15,10 +20,24 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
const user = await this.authService.validateUser(payload);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
const sessionKey = `session:${payload.id}`;
|
||||
const user = await this.redis.get<any>(sessionKey);
|
||||
|
||||
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()
|
||||
export class UserRepository {
|
||||
constructor(
|
||||
@InjectDataSource('oracle') // especifica que queremos o DataSource da conexão 'oracle'
|
||||
@InjectDataSource('oracle')
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
@@ -61,4 +61,16 @@ export class UserRepository {
|
||||
const result = await this.dataSource.query(sql, [sellerId, passwordHash]);
|
||||
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,
|
||||
EmailService,
|
||||
],
|
||||
exports: [UsersService],
|
||||
exports: [UsersService,UserRepository],
|
||||
})
|
||||
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],
|
||||
exports: [RedisProvider, RedisClientAdapterProvider],
|
||||
})
|
||||
export class CacheModule {}
|
||||
export class RedisModule {}
|
||||
36
src/core/configs/cache/redis.provider.ts
vendored
36
src/core/configs/cache/redis.provider.ts
vendored
@@ -1,21 +1,21 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Provider } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
export const RedisProvider: Provider = {
|
||||
provide: 'REDIS_CLIENT',
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const redis = new Redis({
|
||||
host: configService.get<string>('REDIS_HOST', '10.1.1.109'),
|
||||
port: configService.get<number>('REDIS_PORT', 6379),
|
||||
// password: configService.get<string>('REDIS_PASSWORD', ''),
|
||||
});
|
||||
export const RedisProvider: Provider = {
|
||||
provide: 'REDIS_CLIENT',
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const redis = new Redis({
|
||||
host: configService.get<string>('REDIS_HOST', '10.1.1.109'),
|
||||
port: configService.get<number>('REDIS_PORT', 6379),
|
||||
// password: configService.get<string>('REDIS_PASSWORD', ''),
|
||||
});
|
||||
|
||||
redis.on('error', (err) => {
|
||||
console.error('Erro ao conectar ao Redis:', err);
|
||||
});
|
||||
redis.on('error', (err) => {
|
||||
console.error('Erro ao conectar ao Redis:', err);
|
||||
});
|
||||
|
||||
return redis;
|
||||
},
|
||||
inject: [ConfigService],
|
||||
};
|
||||
return redis;
|
||||
},
|
||||
inject: [ConfigService],
|
||||
};
|
||||
|
||||
@@ -4,13 +4,15 @@ import { DataConsultController } from './data-consult.controller';
|
||||
import { DataConsultRepository } from './data-consult.repository';
|
||||
import { LoggerModule } from 'src/Log/logger.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { RedisModule } from 'src/core/configs/cache/redis.module';
|
||||
|
||||
@Module({
|
||||
imports: [LoggerModule, ConfigModule],
|
||||
imports: [LoggerModule, ConfigModule, RedisModule],
|
||||
controllers: [DataConsultController],
|
||||
providers: [
|
||||
DataConsultService,
|
||||
DataConsultRepository,
|
||||
|
||||
],
|
||||
})
|
||||
export class DataConsultModule {}
|
||||
|
||||
@@ -47,11 +47,13 @@ export class DataConsultRepository {
|
||||
FROM PCUSUARI
|
||||
WHERE PCUSUARI.DTTERMINO IS NULL
|
||||
AND PCUSUARI.TIPOVEND NOT IN ('P')
|
||||
AND (PCUSUARI.BLOQUEIO IS NULL OR PCUSUARI.BLOQUEIO = 'N')
|
||||
ORDER BY PCUSUARI.NOME
|
||||
`;
|
||||
return this.executeQuery<SellerDto[]>(sql);
|
||||
}
|
||||
|
||||
|
||||
async findBillings(): Promise<BillingDto[]> {
|
||||
const sql = `
|
||||
SELECT PCCOB.CODCOB as "id",
|
||||
|
||||
@@ -6,13 +6,24 @@ import { BillingDto } from './dto/billing.dto';
|
||||
import { CustomerDto } from './dto/customer.dto';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
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()
|
||||
export class DataConsultService {
|
||||
|
||||
private readonly SELLERS_CACHE_KEY = 'data-consult:sellers';
|
||||
private readonly SELLERS_TTL = 3600; // 1 hora
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly repository: DataConsultRepository,
|
||||
@Inject(RedisClientToken) private readonly redisClient: IRedisClient,
|
||||
@Inject('LoggerService')
|
||||
private readonly logger: ILogger
|
||||
private readonly logger: ILogger,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -29,8 +40,16 @@ export class DataConsultService {
|
||||
* @returns Array de SellerDto
|
||||
*/
|
||||
async sellers(): Promise<SellerDto[]> {
|
||||
this.logger.log('Buscando todos os vendedores');
|
||||
return this.repository.findSellers();
|
||||
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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
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 { 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 {
|
||||
|
||||
constructor( private readonly ordersService: OrdersService) {}
|
||||
constructor(
|
||||
private readonly ordersService: OrdersService,
|
||||
private readonly invoiceService: InvoiceService,
|
||||
) {}
|
||||
|
||||
|
||||
@Get('find')
|
||||
findOrders(@Query() query) {
|
||||
return this.ordersService.findOrders(query);
|
||||
}
|
||||
|
||||
@Get('invoice/:chavenfe')
|
||||
findInvoice(@Param('chavenfe') chavenfe: string) {
|
||||
return this.ordersService.findInvoice(chavenfe);
|
||||
@Get(':chavenfe')
|
||||
async getByChave(@Param('chavenfe') chavenfe: string): Promise<InvoiceDto> {
|
||||
return this.invoiceService.getInvoiceByChave(chavenfe);
|
||||
}
|
||||
|
||||
@Get('itens/:id')
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* 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 { 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({
|
||||
imports: [],
|
||||
controllers: [
|
||||
OrdersController,],
|
||||
providers: [
|
||||
OrdersService,],
|
||||
imports: [
|
||||
ConfigModule,
|
||||
TypeOrmModule,
|
||||
],
|
||||
controllers: [
|
||||
OrdersController,
|
||||
],
|
||||
providers: [
|
||||
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,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true
|
||||
// "incremental": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user