Compare commits

...
Author SHA1 Message Date
marcos-silva-rodrigues cb98099a91 FIX: update timeout 2025-06-13 15:07:50 -03:00
Marcos Rodrigues Silva b2b7537fee Merge pull request #308 from dadosfera/fix/wordpress-block-list
FIX: comment block list
2025-06-10 15:25:27 -03:00
marcos-silva-rodrigues 01263017e7 FIX: comment block list 2025-06-10 14:24:26 -03:00
Rafael Santana 8aded98a11 Merge pull request #304 from dadosfera/beta
Beta
2025-05-26 10:51:13 -03:00
Rafael Santana 0b5a0d0d7e Merge pull request #303 from dadosfera/adjusting-nestjs-payload
Adjusting nestjs payload
2025-05-26 10:40:43 -03:00
Rafael 5a36762544 UPDATE: route /catalog/register-dataset will have an limit of 10mb 2025-05-26 10:24:48 -03:00
Marcos Rodrigues Silva f579171024 Merge pull request #302 from dadosfera/beta
Default value to header origin
2025-05-15 11:18:17 -03:00
Marcos Rodrigues Silva 885e5f4568 Merge pull request #301 from dadosfera/fix/remove-default-host
Fix/remove default host
2025-05-15 11:00:45 -03:00
marcos-silva-rodrigues 81d8cd8a07 FIX: default value to header origin 2025-05-15 11:00:07 -03:00
Marcos Rodrigues Silva f153f41357 Merge pull request #300 from dadosfera/beta
Beta
2025-05-14 18:30:54 -03:00
marcos-silva-rodrigues 2b9e3ff3bc Merge branch 'main' into beta 2025-05-13 14:32:34 -03:00
Marcos Rodrigues Silva 17cdaac380 Merge pull request #299 from dadosfera/fix/stg-envs
FIX: stg to prd for sbm environment
2025-05-09 11:07:22 -03:00
marcos-silva-rodrigues e4eb388e18 FIX: stg to prd for sbm environment 2025-05-09 11:06:23 -03:00
Marcos Rodrigues Silva 4bea0816b6 Merge pull request #298 from dadosfera/feature/pii-rules
FIX: install chromiun in base_image
2025-04-29 16:22:22 -03:00
marcos-silva-rodrigues fb491328d6 FIX: install chromiun in base_image 2025-04-29 16:20:37 -03:00
Marcos Rodrigues Silva 4e043d93ce Merge pull request #297 from dadosfera/feature/pii-rules
REFACTOR: move template to assets folder
2025-04-29 15:41:16 -03:00
marcos-silva-rodrigues 0b525b835f REFACTOR: move template to assets folder 2025-04-29 15:40:37 -03:00
Marcos Rodrigues Silva 3c8c39ec26 Merge pull request #296 from dadosfera/feature/pii-rules
REFACTOR: create builder for parser reporter and fix pdf
2025-04-29 15:20:46 -03:00
marcos-silva-rodrigues 36997403e1 REFACTOR: create builder for parser reporterand fix pdf 2025-04-29 15:18:46 -03:00
Marcos Rodrigues Silva 80ebd98f6f Merge pull request #294 from dadosfera/feature/logs_dashboard
Feature/logs dashboard
2025-04-23 17:46:18 -03:00
Marcos Rodrigues Silva 7044d6606e Merge pull request #278 from dadosfera/beta
Beta
2025-04-08 18:04:41 -03:00
16 changed files with 149 additions and 91 deletions
+13
View File
@@ -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
-8
View File
@@ -20,14 +20,6 @@
], ],
"type": "string" "type": "string"
} }
},
{
"name": "origin",
"required": true,
"in": "header",
"schema": {
"type": "string"
}
} }
], ],
"requestBody": { "requestBody": {
+1 -1
View File
@@ -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
+1
View File
@@ -4,6 +4,7 @@
"compilerOptions": { "compilerOptions": {
"assets": [ "assets": [
"**/*.proto", "**/*.proto",
"assets/**/*",
{ {
"include": "i18n/**/*", "include": "i18n/**/*",
"watchAssets": true "watchAssets": true
@@ -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>
+3
View File
@@ -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,8 @@ async function bootstrap() {
}, },
}); });
app.use(helmet()); app.use(helmet());
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();
+1 -1
View File
@@ -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?:\/\//, '');
+12 -16
View File
@@ -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);
} }
} }
} }
+22 -26
View File
@@ -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;
} }
} }
@@ -45,11 +45,11 @@ export class OpenDataController {
) { ) {
this.logger.info('createUser for open data' + JSON.stringify(request.headers)); this.logger.info('createUser for open data' + JSON.stringify(request.headers));
const corslist = ["https://devsbm.dadosfera.io", "https://sharingoceandata.com"]; // const corslist = ["https://devsbm.dadosfera.io", "https://sharingoceandata.com"];
if (!corslist.includes(origin)) { // if (!corslist.includes(origin)) {
this.logger.info('block request by cors list: '+ origin); // this.logger.info('block request by cors list: '+ origin);
throw new ForbiddenException(); // throw new ForbiddenException();
} // }
const OPENDATA_CUSTOMER_ID = process.env.OPEN_CUSTOMER_ID; const OPENDATA_CUSTOMER_ID = process.env.OPEN_CUSTOMER_ID;
const OPENDATA_GROUP_ID = process.env.OPEN_GROUP_ID; const OPENDATA_GROUP_ID = process.env.OPEN_GROUP_ID;
+3 -3
View File
@@ -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)
}); });
+3 -2
View File
@@ -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);
+1
View File
@@ -0,0 +1 @@
export type TypeParser = "html" | "csv" | "pdf"
+25
View File
@@ -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);
}
}
+3
View File
@@ -0,0 +1,3 @@
export interface Parser<T> {
parse(data: T[]): Promise<string | Buffer>
}
+19 -10
View File
@@ -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: 90000,
}); });
// 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: 90000,
}); });
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;