REFACTOR: create builder for parser reporterand fix pdf

This commit is contained in:
marcos-silva-rodrigues
2025-04-29 15:18:46 -03:00
parent 04accb6689
commit 36997403e1
9 changed files with 129 additions and 76 deletions
+12 -16
View File
@@ -61,6 +61,7 @@ import {
RegisterDatasetWithMetatadaRequest,
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
import { Response } from 'express';
import { TypeParser } from 'src/utils/FileParser/parser-types';
@ApiTags('Catalog')
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
@@ -864,29 +865,24 @@ export class CatalogController {
@RequireModule(
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');
const metadata = PackTheMetadata(user);
try {
const reporter = await this.catalogService.getPiiReporter(metadata, type);
if (type === "html") {
res.setHeader('Content-Type', 'text/html');
} else if (type === "csv") {
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}`;
const {
file,
filename,
type
} = await this.catalogService.getPiiReporter(metadata, contentType);
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) {
this.logger.error(error);
console.error(error)
this.logger.error(error.message);
}
}
}
+23 -27
View File
@@ -33,6 +33,7 @@ import {
AddRlsRuleRequest,
GetNimbusDashboardsRequest,
GetRlsRulesRequest,
PiiMetadata,
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
import { writeFileSync } from 'fs';
import path from 'path';
@@ -40,6 +41,8 @@ import { HtmlParser } from 'src/utils/FileParser/html-parser';
import { PiiDto } from './dtos/pii.dto';
import { CsvParser } from 'src/utils/FileParser/csv-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 {
catalogReadService: ReadService.CatalogReadServices;
@@ -86,7 +89,8 @@ class CatalogService implements OnModuleInit {
)}.dadosfera.ai`;
}
async getPiiReporter(metadata: Metadata, type: "html"| "csv") {
async getPiiReporter(metadata: Metadata, type: TypeParser) {
this.logger.info('getPiiReporter: ' + type)
try {
const {
data
@@ -94,37 +98,29 @@ class CatalogService implements OnModuleInit {
this.catalogWriteService.GetPiiReporter({}, metadata)
)
this.logger.info("Finish grpc call")
// 3. Ler o template HTML
const templatePath = path.join(
__dirname,
'../../../src/template/pii-reporter.html',
);
this.logger.info("templatePath: " + templatePath);
const parser = ParserBuilder.build<PiiMetadata>(type);
if (type === "html") {
const htmlParser = new HtmlParser<PiiDto>(templatePath);
return htmlParser.parse(data);
} else if (type === "csv") {
const csvParser = new CsvParser<PiiDto>();
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
this.logger.info('parser file to: ' + type)
const file = await parser.parse(data)
this.logger.info('finish parser')
const mimeTypes: Record<TypeParser, string> = {
'csv': 'text/csv',
'html': 'text/html',
'pdf': 'application/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) {
this.logger.error(error);
this.logger.error(error.message);
throw error;
}
}
+32 -14
View File
@@ -7,51 +7,69 @@
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet">
<style>
@page {
size: A4;
margin: 20mm 15mm 20mm 15mm;
size: A4 landscape; /* Alterado para paisagem (landscape) */
margin: 15mm 10mm; /* Reduzido para proporcionar mais espaço */
}
body {
font-family: 'Quicksand', sans-serif;
color: #5c5c5c;
margin: 0;
padding: 20px;
padding: 10px;
font-size: 12px; /* Reduzindo o tamanho da fonte */
}
.container {
margin: 0 24px;
margin: 0;
width: 100%;
}
.header {
display: flex;
align-items: center;
margin-bottom: 30px;
margin-bottom: 20px;
}
.logo {
max-width: 200px;
max-width: 150px; /* Reduzida para economizar espaço */
height: auto;
}
h1 {
color: #0d003b;
font-weight: 700;
margin-left: 20px;
font-size: 24px; /* Tamanho ajustado */
}
table {
width: 100%;
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);
border: 1px solid #d0d0d0; /* Borda externa da tabela */
border: 1px solid #d0d0d0;
}
th {
background-color: #1700a2;
color: white;
font-weight: bold;
text-align: left;
padding: 12px 15px;
border: 1px solid #3a26b8; /* Borda um pouco mais escura para o header */
padding: 8px 10px;
border: 1px solid #3a26b8;
font-size: 11px; /* Tamanho ajustado */
word-wrap: break-word; /* Permite quebra de palavras */
overflow-wrap: break-word;
}
td {
padding: 10px 15px;
border: 1px solid #d0d0d0; /* Borda clara da cor do texto */
padding: 6px 10px;
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) {
background-color: #f9f9f9;
}
@@ -59,13 +77,13 @@
background-color: white;
}
.info-section {
margin-top: 30px;
margin-top: 20px;
color: #5c5c5c;
}
.timestamp {
font-style: italic;
text-align: right;
margin-top: 20px;
margin-top: 15px;
font-size: 0.9em;
}
</style>
+3 -3
View File
@@ -1,11 +1,11 @@
import { stringify } from 'csv'
import { Parser } from './parser.interface';
export class CsvParser<T> {
export class CsvParser<T> implements Parser<T>{
async parse(data: T[]) {
return new Promise((resolve, reject) => {
return new Promise<string>((resolve, reject) => {
stringify(data, { header: true }, function (err, output) {
if (err) reject(err);
console.log(output)
resolve(output)
});
+3 -2
View File
@@ -1,9 +1,10 @@
import { readFileSync } from 'fs';
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) {}
parse(data: T[]) {
async parse(data: T[]) {
const templateSource = readFileSync(this.templatePath, 'utf8');
const template = Handlebars.compile(templateSource);
+1
View File
@@ -0,0 +1 @@
export type TypeParser = "html" | "csv" | "pdf"
+29
View File
@@ -0,0 +1,29 @@
import path from 'path';
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 = path.join(
__dirname,
'../../../src/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>
}
+23 -14
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> {
async parse(data: T) {
export class PDFParser<T> implements Parser<T> {
constructor(private htmlParser: HtmlParser<T>) {}
async parse(data: T[]) {
try {
const browser = await puppeteer.launch({
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();
page.setDefaultNavigationTimeout(0);
await page.setContent(data, {
const html = await this.htmlParser.parse(data);
await page.setContent(html, {
waitUntil: 'networkidle0',
timeout: 30000
timeout: 30000,
});
// Configurações adicionais para garantir um PDF válido
const pdfBuffer = await page.pdf({
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm'
left: '15mm',
},
preferCSSPageSize: true,
displayHeaderFooter: false,
@@ -34,12 +43,12 @@ export class PDFParser<T extends string> {
pageRanges: '',
tagged: true,
outline: false,
timeout: 30000
timeout: 30000,
});
await browser.close();
return pdfBuffer;
return Buffer.from(pdf);
} catch (error) {
console.error('Error generating PDF:', error);
throw error;