mirror of
https://github.com/dadosfera/maestro.git
synced 2026-11-12 07:49:09 +00:00
Compare commits
20
Commits
+13
@@ -50,4 +50,17 @@ WORKDIR /app
|
|||||||
COPY --from=prod_build /app/dist ./dist
|
COPY --from=prod_build /app/dist ./dist
|
||||||
COPY --from=prod_build /app/node_modules ./node_modules
|
COPY --from=prod_build /app/node_modules ./node_modules
|
||||||
COPY --from=prod_build /app/package*.json ./
|
COPY --from=prod_build /app/package*.json ./
|
||||||
|
RUN apk update
|
||||||
|
# needed packages to build dependencies from source
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
chromium \
|
||||||
|
nss \
|
||||||
|
freetype \
|
||||||
|
harfbuzz \
|
||||||
|
ca-certificates \
|
||||||
|
ttf-freefont
|
||||||
|
|
||||||
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||||
|
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||||
|
|
||||||
ENTRYPOINT npm run start:prod
|
ENTRYPOINT npm run start:prod
|
||||||
|
|||||||
+244
-8
@@ -20,14 +20,6 @@
|
|||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "origin",
|
|
||||||
"required": true,
|
|
||||||
"in": "header",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
@@ -5976,6 +5968,167 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/api-key": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "ApiKeyController_create",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "dadosfera-lang",
|
||||||
|
"in": "header",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"enum": [
|
||||||
|
"pt-br",
|
||||||
|
"en-us"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreateApiKeyDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreateApiKeyResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreateApiKeyResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ApiKey"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"access-token": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access-token": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"operationId": "ApiKeyController_findAll",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "dadosfera-lang",
|
||||||
|
"in": "header",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"enum": [
|
||||||
|
"pt-br",
|
||||||
|
"en-us"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ApiKeyBaseResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ApiKeyBaseResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ApiKey"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"access-token": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access-token": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api-key/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"operationId": "ApiKeyController_remove",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "dadosfera-lang",
|
||||||
|
"in": "header",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"enum": [
|
||||||
|
"pt-br",
|
||||||
|
"en-us"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ApiKey"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"access-token": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"access-token": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
@@ -8856,6 +9009,89 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"policies"
|
"policies"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"CreateApiKeyDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"permissions": {
|
||||||
|
"description": "Array of permission IDs",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"permissions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"CreateApiKeyResponseDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"key_mask": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/PermissionDto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"key_mask",
|
||||||
|
"permissions",
|
||||||
|
"created_at",
|
||||||
|
"created_by",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ApiKeyBaseResponseDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
},
|
||||||
|
"key_mask": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/PermissionDto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"key_mask",
|
||||||
|
"permissions",
|
||||||
|
"created_at",
|
||||||
|
"created_by"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ charts:
|
|||||||
- name: hostname
|
- name: hostname
|
||||||
value: maestro.stg.dadosfera.ai
|
value: maestro.stg.dadosfera.ai
|
||||||
- name: maestro.pi_factory_url
|
- name: maestro.pi_factory_url
|
||||||
value: pi-factory.stg.dadosfera.ai
|
value: pi-factory.dadosfera.ai
|
||||||
- name: maestro.in_factory_url
|
- name: maestro.in_factory_url
|
||||||
value: in-factory.stg.dadosfera.ai
|
value: in-factory.stg.dadosfera.ai
|
||||||
- name: maestro.tr_factory_url
|
- name: maestro.tr_factory_url
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"assets": [
|
"assets": [
|
||||||
"**/*.proto",
|
"**/*.proto",
|
||||||
|
"assets/**/*",
|
||||||
{
|
{
|
||||||
"include": "i18n/**/*",
|
"include": "i18n/**/*",
|
||||||
"watchAssets": true
|
"watchAssets": true
|
||||||
|
|||||||
Generated
+4
-4
@@ -12,7 +12,7 @@
|
|||||||
"@aws-sdk/client-secrets-manager": "^3.414.0",
|
"@aws-sdk/client-secrets-manager": "^3.414.0",
|
||||||
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
|
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
|
||||||
"@dadosfera/protospack": "2.5.3",
|
"@dadosfera/protospack": "2.5.3",
|
||||||
"@dadosfera/protospack-v2": "3.37.0-beta.23",
|
"@dadosfera/protospack-v2": "3.38.0-beta.1",
|
||||||
"@grpc/grpc-js": "^1.9.3",
|
"@grpc/grpc-js": "^1.9.3",
|
||||||
"@grpc/proto-loader": "^0.7.9",
|
"@grpc/proto-loader": "^0.7.9",
|
||||||
"@nestjs/cli": "^9.5.0",
|
"@nestjs/cli": "^9.5.0",
|
||||||
@@ -1400,9 +1400,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@dadosfera/protospack-v2": {
|
"node_modules/@dadosfera/protospack-v2": {
|
||||||
"version": "3.37.0-beta.23",
|
"version": "3.38.0-beta.1",
|
||||||
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.37.0-beta.23.tgz",
|
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.1.tgz",
|
||||||
"integrity": "sha512-Dv3rODwbiubHB4u8sI5uJOSm1ETOXzlpaxHNEmzTNG2fLTIJxsaF1GlOBST3x9HTkhJC6hUJ8r77/qQu1FKPpQ==",
|
"integrity": "sha512-4+yNZMlhvEkHlxJcaeta+jtJ9owskZcg0yjKdZzBfT8PCclctgertLHmrG0+kDb3Y/na/rpPPHYULRp7AO4zDQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "^1.9.3",
|
"@grpc/grpc-js": "^1.9.3",
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@
|
|||||||
"@aws-sdk/client-secrets-manager": "^3.414.0",
|
"@aws-sdk/client-secrets-manager": "^3.414.0",
|
||||||
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
|
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
|
||||||
"@dadosfera/protospack": "2.5.3",
|
"@dadosfera/protospack": "2.5.3",
|
||||||
"@dadosfera/protospack-v2": "3.37.0-beta.23",
|
"@dadosfera/protospack-v2": "3.38.0-beta.1",
|
||||||
"@grpc/grpc-js": "^1.9.3",
|
"@grpc/grpc-js": "^1.9.3",
|
||||||
"@grpc/proto-loader": "^0.7.9",
|
"@grpc/proto-loader": "^0.7.9",
|
||||||
"@nestjs/cli": "^9.5.0",
|
"@nestjs/cli": "^9.5.0",
|
||||||
|
|||||||
+3
-1
@@ -29,6 +29,7 @@ import { CustomersModule } from './modules/customers/customers.module';
|
|||||||
import { OpenDataModule } from './modules/open-data/open-data.module';
|
import { OpenDataModule } from './modules/open-data/open-data.module';
|
||||||
import { ThemeModule } from './modules/theme/theme.module';
|
import { ThemeModule } from './modules/theme/theme.module';
|
||||||
import { NetworkPolicyModule } from './modules/network-policy/network-policy.module';
|
import { NetworkPolicyModule } from './modules/network-policy/network-policy.module';
|
||||||
|
import { ApiKeyModule } from './modules/api-key/api-key.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -64,7 +65,8 @@ import { NetworkPolicyModule } from './modules/network-policy/network-policy.mod
|
|||||||
ThemeModule,
|
ThemeModule,
|
||||||
//Always leave HealthModule last, so it is on the bottom of swagger
|
//Always leave HealthModule last, so it is on the bottom of swagger
|
||||||
HealthModule,
|
HealthModule,
|
||||||
NetworkPolicyModule
|
NetworkPolicyModule,
|
||||||
|
ApiKeyModule
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -7,51 +7,69 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
@page {
|
@page {
|
||||||
size: A4;
|
size: A4 landscape; /* Alterado para paisagem (landscape) */
|
||||||
margin: 20mm 15mm 20mm 15mm;
|
margin: 15mm 10mm; /* Reduzido para proporcionar mais espaço */
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: 'Quicksand', sans-serif;
|
font-family: 'Quicksand', sans-serif;
|
||||||
color: #5c5c5c;
|
color: #5c5c5c;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 10px;
|
||||||
|
font-size: 12px; /* Reduzindo o tamanho da fonte */
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
margin: 0 24px;
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.logo {
|
.logo {
|
||||||
max-width: 200px;
|
max-width: 150px; /* Reduzida para economizar espaço */
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
color: #0d003b;
|
color: #0d003b;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
|
font-size: 24px; /* Tamanho ajustado */
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-top: 20px;
|
margin-top: 15px;
|
||||||
|
table-layout: fixed; /* Importante: define larguras fixas */
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
border: 1px solid #d0d0d0; /* Borda externa da tabela */
|
border: 1px solid #d0d0d0;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
background-color: #1700a2;
|
background-color: #1700a2;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 12px 15px;
|
padding: 8px 10px;
|
||||||
border: 1px solid #3a26b8; /* Borda um pouco mais escura para o header */
|
border: 1px solid #3a26b8;
|
||||||
|
font-size: 11px; /* Tamanho ajustado */
|
||||||
|
word-wrap: break-word; /* Permite quebra de palavras */
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
padding: 10px 15px;
|
padding: 6px 10px;
|
||||||
border: 1px solid #d0d0d0; /* Borda clara da cor do texto */
|
border: 1px solid #d0d0d0;
|
||||||
|
font-size: 11px; /* Tamanho ajustado */
|
||||||
|
word-wrap: break-word; /* Permite quebra de palavras */
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
/* Definindo larguras específicas para cada coluna */
|
||||||
|
th:nth-child(1), td:nth-child(1) { width: 14%; } /* Database */
|
||||||
|
th:nth-child(2), td:nth-child(2) { width: 14%; } /* Schema */
|
||||||
|
th:nth-child(3), td:nth-child(3) { width: 17%; } /* Tabela */
|
||||||
|
th:nth-child(4), td:nth-child(4) { width: 17%; } /* Coluna */
|
||||||
|
th:nth-child(5), td:nth-child(5) { width: 13%; } /* Tipo de Dado */
|
||||||
|
th:nth-child(6), td:nth-child(6) { width: 25%; } /* Regras PII */
|
||||||
|
|
||||||
tr:nth-child(even) {
|
tr:nth-child(even) {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
@@ -59,13 +77,13 @@
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
.info-section {
|
.info-section {
|
||||||
margin-top: 30px;
|
margin-top: 20px;
|
||||||
color: #5c5c5c;
|
color: #5c5c5c;
|
||||||
}
|
}
|
||||||
.timestamp {
|
.timestamp {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-top: 20px;
|
margin-top: 15px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import { RequestUser } from '../decorators/user.decorator';
|
import { RequestUser } from '../decorators/user.decorator';
|
||||||
import ErrorBuilder from '../utils/ErrorBuilder';
|
import ErrorBuilder from '../utils/ErrorBuilder';
|
||||||
import ErrorCodes from '../utils/errorCodes';
|
import ErrorCodes from '../utils/errorCodes';
|
||||||
|
import { ApiKeyService } from 'src/modules/api-key/api-key.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthenticationGuard
|
export class AuthenticationGuard
|
||||||
@@ -31,6 +32,7 @@ export class AuthenticationGuard
|
|||||||
dadosferaLogger: DadosferaLogger,
|
dadosferaLogger: DadosferaLogger,
|
||||||
private reflector: Reflector,
|
private reflector: Reflector,
|
||||||
private authClient: AuthClientService,
|
private authClient: AuthClientService,
|
||||||
|
private apiKeyService: ApiKeyService
|
||||||
) {
|
) {
|
||||||
this.pems = new Map();
|
this.pems = new Map();
|
||||||
this.logger = dadosferaLogger.logger;
|
this.logger = dadosferaLogger.logger;
|
||||||
@@ -48,20 +50,40 @@ export class AuthenticationGuard
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivate(ctx: ExecutionContext): boolean {
|
async canActivate(ctx: ExecutionContext): Promise<boolean> {
|
||||||
const authFunctions = this.reflector.getAllAndMerge<
|
const authFunctions = this.reflector.getAllAndMerge<
|
||||||
AuthenticationFunction[]
|
AuthenticationFunction[]
|
||||||
>(AUTH_FUNCTION_KEY, [ctx.getClass(), ctx.getHandler()]);
|
>(AUTH_FUNCTION_KEY, [ctx.getClass(), ctx.getHandler()]);
|
||||||
const mustBeAuthenticated = authFunctions.length > 0;
|
const mustBeAuthenticated = authFunctions.length > 0;
|
||||||
|
|
||||||
const request = ctx.switchToHttp().getRequest();
|
|
||||||
const accessToken = this.validateToken(request, mustBeAuthenticated);
|
|
||||||
|
|
||||||
if (!mustBeAuthenticated) {
|
if (!mustBeAuthenticated) {
|
||||||
// no need to be authenticated
|
// no need to be authenticated
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const request = ctx.switchToHttp().getRequest();
|
||||||
|
const apiKey = request.get('X-api-key');
|
||||||
|
if (apiKey) {
|
||||||
|
const {
|
||||||
|
api_key
|
||||||
|
} = await this.apiKeyService.get(apiKey);
|
||||||
|
|
||||||
|
request.user = {
|
||||||
|
user_id: api_key.user_id,
|
||||||
|
username: api_key.username,
|
||||||
|
permissions: api_key.permissions,
|
||||||
|
customer_id: api_key.customer_id,
|
||||||
|
customer_name: api_key.customer_name,
|
||||||
|
customer_tier: api_key.customer_tier,
|
||||||
|
customer_modules: api_key.customer_modules,
|
||||||
|
access_token: apiKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = this.validateToken(request, mustBeAuthenticated);
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
// couldn't load valid token
|
// couldn't load valid token
|
||||||
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
|
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
|
||||||
|
|||||||
@@ -603,6 +603,22 @@ export const PERMISSIONS_GROUPS = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PUBLIC_PERMISSIONS_SEQID = [
|
||||||
|
46, // AUTH.GENERATE_TOKEN
|
||||||
|
23, 13, 29, 5, // PIPELINE
|
||||||
|
37, 38, // CONNECTION
|
||||||
|
35, // NETWORK_CONFIG
|
||||||
|
7, 19, 25, 1, // CATALOG
|
||||||
|
14, // SNOWFLAKE
|
||||||
|
11, // DATAVIZ
|
||||||
|
31, // INTELLIGENCE
|
||||||
|
34, // USERS
|
||||||
|
43, // PROCESS
|
||||||
|
47 // CUSTOMER
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
export interface DadosferaModule {
|
export interface DadosferaModule {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { NestFactory } from '@nestjs/core';
|
|||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
|
import { json, urlencoded } from 'express';
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
@@ -25,6 +26,11 @@ async function bootstrap() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
if (process.env.ENV === 'prd') {
|
||||||
|
app.use('/catalog/register-dataset', json({ limit: '10mb' }));
|
||||||
|
app.use('/catalog/register-dataset', urlencoded({ extended: true, limit: '10mb' }));
|
||||||
|
}
|
||||||
|
|
||||||
configureSwagger(app);
|
configureSwagger(app);
|
||||||
await app.listen(3333);
|
await app.listen(3333);
|
||||||
if (process.env.KILL_AFTER_START) await app.close();
|
if (process.env.KILL_AFTER_START) await app.close();
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Controller, Get, Post, Body, Param, Delete, UseFilters, Inject } from '@nestjs/common';
|
||||||
|
import { ApiKeyService } from './api-key.service';
|
||||||
|
import { CreateApiKeyDto, CreateApiKeyResponseDto, ApiKeyBaseResponseDto } from './dto/api-key.dto';
|
||||||
|
import { Authenticated, RequireAllPermissions } from 'src/decorators/authentication.decorator';
|
||||||
|
import { ApiHeaders, ApiTags, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
|
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
|
||||||
|
import { RequestUser, User } from 'src/decorators/user.decorator';
|
||||||
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
||||||
|
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
|
||||||
|
|
||||||
|
@Controller('api-key')
|
||||||
|
@Authenticated()
|
||||||
|
@ApiTags('ApiKey')
|
||||||
|
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||||
|
@UseFilters(new GrpcToHttpExceptionFilter())
|
||||||
|
export class ApiKeyController {
|
||||||
|
logger: DadosferaLogger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DadosferaLogger)
|
||||||
|
dadosferaLogger: DadosferaLogger,
|
||||||
|
private readonly apiKeyService: ApiKeyService,
|
||||||
|
) {
|
||||||
|
this.logger = dadosferaLogger.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@ApiResponse({ type: CreateApiKeyResponseDto })
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||||
|
async create(@Body() createApiKeyDto: CreateApiKeyDto, @User() user: RequestUser): Promise<CreateApiKeyResponseDto> {
|
||||||
|
this.logger.info('POST /api-key', {
|
||||||
|
permissions: createApiKeyDto.permissions,
|
||||||
|
method: 'create'
|
||||||
|
});
|
||||||
|
const result = await this.apiKeyService.create(createApiKeyDto, user);
|
||||||
|
this.logger.info('POST /api-key success', {
|
||||||
|
id: result.id,
|
||||||
|
method: 'create'
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiResponse({ type: [ApiKeyBaseResponseDto] })
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||||
|
async findAll(@User() user: RequestUser): Promise<ApiKeyBaseResponseDto[]> {
|
||||||
|
this.logger.info('GET /api-key', {
|
||||||
|
method: 'findAll'
|
||||||
|
});
|
||||||
|
const result = await this.apiKeyService.findAll(user);
|
||||||
|
this.logger.info('GET /api-key success', {
|
||||||
|
count: result.length,
|
||||||
|
method: 'findAll'
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
|
||||||
|
async remove(@Param('id') id: string, @User() user: RequestUser): Promise<void> {
|
||||||
|
this.logger.info('DELETE /api-key/:id', {
|
||||||
|
id,
|
||||||
|
method: 'remove'
|
||||||
|
});
|
||||||
|
await this.apiKeyService.remove(id, user);
|
||||||
|
this.logger.info('DELETE /api-key/:id success', {
|
||||||
|
id,
|
||||||
|
method: 'remove'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ApiKeyService } from './api-key.service';
|
||||||
|
import { ApiKeyController } from './api-key.controller';
|
||||||
|
import { ClientsModule } from '@nestjs/microservices';
|
||||||
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import DadosferaLogger from '@dadosfera/dadosfera-logs';
|
||||||
|
|
||||||
|
const ducClient = new DucClient();
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ClientsModule.register([ducClient.providerOptions])
|
||||||
|
],
|
||||||
|
controllers: [ApiKeyController],
|
||||||
|
providers: [ApiKeyService, DadosferaLogger],
|
||||||
|
exports: [ApiKeyService]
|
||||||
|
})
|
||||||
|
export class ApiKeyModule {}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
Inject,
|
||||||
|
OnModuleInit,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ClientGrpc } from '@nestjs/microservices';
|
||||||
|
import {
|
||||||
|
CreateApiKeyDto,
|
||||||
|
CreateApiKeyResponseDto,
|
||||||
|
ApiKeyBaseResponseDto,
|
||||||
|
} from './dto/api-key.dto';
|
||||||
|
import { RequestUser } from 'src/decorators/user.decorator';
|
||||||
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { PackTheMetadata } from '../../utils/ PackTheMetadata';
|
||||||
|
import { ApiKeyWriteProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
|
||||||
|
import { PUBLIC_PERMISSIONS_SEQID } from 'src/authentication/permissions.enum';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ApiKeyService implements OnModuleInit {
|
||||||
|
private apiKeyService: ApiKeyWriteProtoService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DucClient.name) private readonly client: ClientGrpc
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
this.apiKeyService = this.client.getService<ApiKeyWriteProtoService>(
|
||||||
|
ProtoServices.ApiKeyWriteProtoService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
create(
|
||||||
|
createApiKeyDto: CreateApiKeyDto,
|
||||||
|
user: RequestUser,
|
||||||
|
): Promise<CreateApiKeyResponseDto> {
|
||||||
|
const metadata = PackTheMetadata(user);
|
||||||
|
|
||||||
|
const invalidPermissions = [];
|
||||||
|
for (const permission of createApiKeyDto.permissions) {
|
||||||
|
if (!PUBLIC_PERMISSIONS_SEQID.includes(permission)) {
|
||||||
|
invalidPermissions.push(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidPermissions.length > 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Invalid permissions: ${invalidPermissions.join(', ')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastValueFrom(
|
||||||
|
this.apiKeyService.CreateApiKey(
|
||||||
|
{
|
||||||
|
permissions: createApiKeyDto.permissions,
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(user: RequestUser): Promise<ApiKeyBaseResponseDto[]> {
|
||||||
|
const metadata = PackTheMetadata(user);
|
||||||
|
console.log(metadata);
|
||||||
|
|
||||||
|
const data = await lastValueFrom(
|
||||||
|
this.apiKeyService.ListApiKeys({}, metadata),
|
||||||
|
);
|
||||||
|
return data.api_keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string, user: RequestUser) {
|
||||||
|
const metadata = PackTheMetadata(user);
|
||||||
|
|
||||||
|
await lastValueFrom(this.apiKeyService.DeleteApiKey({ id }, metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string) {
|
||||||
|
const metadata = PackTheMetadata({});
|
||||||
|
|
||||||
|
return await lastValueFrom(this.apiKeyService.GetApiKey({ key }, metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsArray, IsNumber } from 'class-validator';
|
||||||
|
|
||||||
|
export class PermissionDto {
|
||||||
|
@ApiProperty({ type: Number })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ type: String })
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiKeyBaseResponseDto {
|
||||||
|
@ApiProperty({ type: String, format: 'uuid' })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: String })
|
||||||
|
key_mask: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [PermissionDto] })
|
||||||
|
permissions: PermissionDto[];
|
||||||
|
|
||||||
|
@ApiProperty({ type: String, format: 'date-time' })
|
||||||
|
created_at: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: String })
|
||||||
|
created_by: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateApiKeyResponseDto extends ApiKeyBaseResponseDto {
|
||||||
|
@ApiProperty({ type: String })
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateApiKeyDto {
|
||||||
|
@ApiProperty({ type: [Number], description: 'Array of permission IDs' })
|
||||||
|
@IsArray()
|
||||||
|
@IsNumber({}, { each: true })
|
||||||
|
permissions: number[];
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@ export class AuthController {
|
|||||||
async signIn(
|
async signIn(
|
||||||
@Body() { username, password, totp }: AuthSignInReq,
|
@Body() { username, password, totp }: AuthSignInReq,
|
||||||
@Language() language: LanguageEnum,
|
@Language() language: LanguageEnum,
|
||||||
@Headers('origin') origin: string,
|
@Headers('origin') origin = '',
|
||||||
): Promise<AuthSignInRes> {
|
): Promise<AuthSignInRes> {
|
||||||
this.logger.info('/auth - SignIn');
|
this.logger.info('/auth - SignIn');
|
||||||
const frontHost = origin.replace(/^https?:\/\//, '');
|
const frontHost = origin.replace(/^https?:\/\//, '');
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import {
|
|||||||
RegisterDatasetWithMetatadaRequest,
|
RegisterDatasetWithMetatadaRequest,
|
||||||
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
|
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
import { TypeParser } from 'src/utils/FileParser/parser-types';
|
||||||
|
|
||||||
@ApiTags('Catalog')
|
@ApiTags('Catalog')
|
||||||
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
|
||||||
@@ -864,29 +865,24 @@ export class CatalogController {
|
|||||||
@RequireModule(
|
@RequireModule(
|
||||||
DADOSFERA_MODULES_KEYS.PII
|
DADOSFERA_MODULES_KEYS.PII
|
||||||
)
|
)
|
||||||
async getPiiReporter(@User() user: RequestUser, @Res() res: Response, @Query('type') type: "html" | "csv") {
|
async getPiiReporter(@User() user: RequestUser, @Res() res: Response, @Query('type') contentType: TypeParser = "pdf") {
|
||||||
this.logger.info('GET pii-reporter');
|
this.logger.info('GET pii-reporter');
|
||||||
const metadata = PackTheMetadata(user);
|
const metadata = PackTheMetadata(user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const reporter = await this.catalogService.getPiiReporter(metadata, type);
|
const {
|
||||||
|
file,
|
||||||
if (type === "html") {
|
filename,
|
||||||
res.setHeader('Content-Type', 'text/html');
|
type
|
||||||
} else if (type === "csv") {
|
} = await this.catalogService.getPiiReporter(metadata, contentType);
|
||||||
res.setHeader('Content-Type', 'text/csv');
|
|
||||||
} else if (type === "pdf") {
|
|
||||||
res.setHeader('Content-Type', 'application/pdf');
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
||||||
const filename = `relatorio-pii-${timestamp}.${type}`;
|
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
||||||
|
res.setHeader('Content-Type', type);
|
||||||
|
|
||||||
return res.send(reporter);
|
// use res.end to send buffer
|
||||||
|
return res.end(file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
console.error(error)
|
||||||
|
this.logger.error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
AddRlsRuleRequest,
|
AddRlsRuleRequest,
|
||||||
GetNimbusDashboardsRequest,
|
GetNimbusDashboardsRequest,
|
||||||
GetRlsRulesRequest,
|
GetRlsRulesRequest,
|
||||||
|
PiiMetadata,
|
||||||
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
|
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -40,6 +41,8 @@ import { HtmlParser } from 'src/utils/FileParser/html-parser';
|
|||||||
import { PiiDto } from './dtos/pii.dto';
|
import { PiiDto } from './dtos/pii.dto';
|
||||||
import { CsvParser } from 'src/utils/FileParser/csv-parser';
|
import { CsvParser } from 'src/utils/FileParser/csv-parser';
|
||||||
import { PDFParser } from 'src/utils/FileParser/pdf-parser';
|
import { PDFParser } from 'src/utils/FileParser/pdf-parser';
|
||||||
|
import { TypeParser } from 'src/utils/FileParser/parser-types';
|
||||||
|
import { ParserBuilder } from 'src/utils/FileParser/parser.builder';
|
||||||
|
|
||||||
class CatalogService implements OnModuleInit {
|
class CatalogService implements OnModuleInit {
|
||||||
catalogReadService: ReadService.CatalogReadServices;
|
catalogReadService: ReadService.CatalogReadServices;
|
||||||
@@ -86,7 +89,8 @@ class CatalogService implements OnModuleInit {
|
|||||||
)}.dadosfera.ai`;
|
)}.dadosfera.ai`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPiiReporter(metadata: Metadata, type: "html"| "csv") {
|
async getPiiReporter(metadata: Metadata, type: TypeParser) {
|
||||||
|
this.logger.info('getPiiReporter: ' + type)
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data
|
data
|
||||||
@@ -95,36 +99,28 @@ class CatalogService implements OnModuleInit {
|
|||||||
)
|
)
|
||||||
this.logger.info("Finish grpc call")
|
this.logger.info("Finish grpc call")
|
||||||
|
|
||||||
// 3. Ler o template HTML
|
const parser = ParserBuilder.build<PiiMetadata>(type);
|
||||||
const templatePath = path.join(
|
|
||||||
__dirname,
|
|
||||||
'../../../src/template/pii-reporter.html',
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.info("templatePath: " + templatePath);
|
this.logger.info('parser file to: ' + type)
|
||||||
|
const file = await parser.parse(data)
|
||||||
if (type === "html") {
|
this.logger.info('finish parser')
|
||||||
const htmlParser = new HtmlParser<PiiDto>(templatePath);
|
const mimeTypes: Record<TypeParser, string> = {
|
||||||
return htmlParser.parse(data);
|
'csv': 'text/csv',
|
||||||
} else if (type === "csv") {
|
'html': 'text/html',
|
||||||
const csvParser = new CsvParser<PiiDto>();
|
'pdf': 'application/pdf'
|
||||||
return await csvParser.parse(data)
|
|
||||||
} else if (type === "pdf") {
|
|
||||||
const htmlParser = new HtmlParser<PiiDto>(templatePath);
|
|
||||||
const pdfParser = new PDFParser();
|
|
||||||
const pdf = await pdfParser.parse(htmlParser.parse(data));
|
|
||||||
const pdfPath = path.join(
|
|
||||||
__dirname,
|
|
||||||
'../../../src/template/teste.pdf',
|
|
||||||
);
|
|
||||||
writeFileSync(pdfPath, pdf);
|
|
||||||
return pdf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Unsupported Type: " + type)
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
|
const filename = `relatorio-pii-${timestamp}.${type}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
file,
|
||||||
|
filename: filename,
|
||||||
|
type: mimeTypes[type]
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error);
|
this.logger.error(error.message);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { PermissionsService } from '../permissions/permissions.service';
|
|||||||
import { LanguageEnum } from 'src/utils/languages.enum';
|
import { LanguageEnum } from 'src/utils/languages.enum';
|
||||||
import { RequestUser } from 'src/decorators/user.decorator';
|
import { RequestUser } from 'src/decorators/user.decorator';
|
||||||
import { DucClient } from '../duc/client.config';
|
import { DucClient } from '../duc/client.config';
|
||||||
|
import { PUBLIC_PERMISSIONS_SEQID } from 'src/authentication/permissions.enum';
|
||||||
|
|
||||||
interface GetRolesPermissionsName {
|
interface GetRolesPermissionsName {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -103,6 +104,20 @@ export class RolesService {
|
|||||||
const meta = new Metadata();
|
const meta = new Metadata();
|
||||||
meta.add('access_token', access_token);
|
meta.add('access_token', access_token);
|
||||||
const { description, name, permissionIds, userIds } = data;
|
const { description, name, permissionIds, userIds } = data;
|
||||||
|
|
||||||
|
const invalidPermissions = [];
|
||||||
|
for (const permission of permissionIds) {
|
||||||
|
if (!PUBLIC_PERMISSIONS_SEQID.includes(permission)) {
|
||||||
|
invalidPermissions.push(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidPermissions.length > 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Invalid permissions: ${invalidPermissions.join(', ')}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const role = await lastValueFrom(
|
const role = await lastValueFrom(
|
||||||
this.rolesClientService.RoleCreate(
|
this.rolesClientService.RoleCreate(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { stringify } from 'csv'
|
import { stringify } from 'csv'
|
||||||
|
import { Parser } from './parser.interface';
|
||||||
|
|
||||||
export class CsvParser<T> {
|
export class CsvParser<T> implements Parser<T>{
|
||||||
async parse(data: T[]) {
|
async parse(data: T[]) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
stringify(data, { header: true }, function (err, output) {
|
stringify(data, { header: true }, function (err, output) {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
console.log(output)
|
|
||||||
|
|
||||||
resolve(output)
|
resolve(output)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
|
import { Parser } from './parser.interface';
|
||||||
|
|
||||||
export class HtmlParser<T> {
|
export class HtmlParser<T> implements Parser<T> {
|
||||||
constructor(private readonly templatePath: string) {}
|
constructor(private readonly templatePath: string) {}
|
||||||
parse(data: T[]) {
|
async parse(data: T[]) {
|
||||||
const templateSource = readFileSync(this.templatePath, 'utf8');
|
const templateSource = readFileSync(this.templatePath, 'utf8');
|
||||||
|
|
||||||
const template = Handlebars.compile(templateSource);
|
const template = Handlebars.compile(templateSource);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export type TypeParser = "html" | "csv" | "pdf"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { TypeParser } from './parser-types';
|
||||||
|
import { HtmlParser } from './html-parser';
|
||||||
|
import { CsvParser } from './csv-parser';
|
||||||
|
import { PDFParser } from './pdf-parser';
|
||||||
|
import { Parser } from './parser.interface';
|
||||||
|
|
||||||
|
export class ParserBuilder {
|
||||||
|
static build<T>(type: TypeParser): Parser<T> {
|
||||||
|
const templatePath = '/app/dist/assets/template/pii-reporter.html';
|
||||||
|
|
||||||
|
if (type === 'html') {
|
||||||
|
const htmlParser = new HtmlParser<T>(templatePath);
|
||||||
|
return htmlParser;
|
||||||
|
} else if (type === 'csv') {
|
||||||
|
const csvParser = new CsvParser<T>();
|
||||||
|
return csvParser;
|
||||||
|
} else if (type === 'pdf') {
|
||||||
|
const htmlParser = new HtmlParser<T>(templatePath);
|
||||||
|
const pdfParser = new PDFParser(htmlParser);
|
||||||
|
return pdfParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unsupported Type: ' + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface Parser<T> {
|
||||||
|
parse(data: T[]): Promise<string | Buffer>
|
||||||
|
}
|
||||||
@@ -1,30 +1,39 @@
|
|||||||
import puppeteer from "puppeteer";
|
import puppeteer from 'puppeteer';
|
||||||
|
import { Parser } from './parser.interface';
|
||||||
|
import { HtmlParser } from './html-parser';
|
||||||
|
|
||||||
export class PDFParser<T extends string> {
|
export class PDFParser<T> implements Parser<T> {
|
||||||
async parse(data: T) {
|
constructor(private htmlParser: HtmlParser<T>) {}
|
||||||
|
|
||||||
|
async parse(data: T[]) {
|
||||||
try {
|
try {
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-setuid-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
page.setDefaultNavigationTimeout(0);
|
page.setDefaultNavigationTimeout(0);
|
||||||
|
|
||||||
await page.setContent(data, {
|
const html = await this.htmlParser.parse(data);
|
||||||
|
await page.setContent(html, {
|
||||||
waitUntil: 'networkidle0',
|
waitUntil: 'networkidle0',
|
||||||
timeout: 30000
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configurações adicionais para garantir um PDF válido
|
// Configurações adicionais para garantir um PDF válido
|
||||||
const pdfBuffer = await page.pdf({
|
const pdf = await page.pdf({
|
||||||
format: 'A4',
|
format: 'A4',
|
||||||
printBackground: true,
|
printBackground: true,
|
||||||
margin: {
|
margin: {
|
||||||
top: '20mm',
|
top: '20mm',
|
||||||
right: '15mm',
|
right: '15mm',
|
||||||
bottom: '20mm',
|
bottom: '20mm',
|
||||||
left: '15mm'
|
left: '15mm',
|
||||||
},
|
},
|
||||||
preferCSSPageSize: true,
|
preferCSSPageSize: true,
|
||||||
displayHeaderFooter: false,
|
displayHeaderFooter: false,
|
||||||
@@ -34,12 +43,12 @@ export class PDFParser<T extends string> {
|
|||||||
pageRanges: '',
|
pageRanges: '',
|
||||||
tagged: true,
|
tagged: true,
|
||||||
outline: false,
|
outline: false,
|
||||||
timeout: 30000
|
timeout: 30000,
|
||||||
});
|
});
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
|
|
||||||
return pdfBuffer;
|
return Buffer.from(pdf);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating PDF:', error);
|
console.error('Error generating PDF:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user