FEAT: pii

This commit is contained in:
marcos-silva-rodrigues
2025-04-29 14:00:23 -03:00
parent 7a6db36554
commit 58e3c7be94
12 changed files with 883 additions and 59 deletions
+18 -5
View File
@@ -346,7 +346,7 @@ export class CatalogController {
@Language() language: LanguageEnum,
@Param('id') id: string,
): Promise<IPreviewResponse> {
const { customer_name, customer_id, user_id, username } = user;
const { customer_name, customer_id, user_id, username, customer_modules } = user;
this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, {
user_id,
@@ -359,6 +359,7 @@ export class CatalogController {
user_id,
username,
language,
is_mask: customer_modules.some(mod => mod === 'pii')
});
const preview = await this.catalogService.getDatasetPreview(id, metadata);
@@ -863,15 +864,27 @@ export class CatalogController {
@RequireModule(
DADOSFERA_MODULES_KEYS.PII
)
async getPiiReporter(@User() user: RequestUser, @Res() res: Response) {
async getPiiReporter(@User() user: RequestUser, @Res() res: Response, @Query('type') type: "html" | "csv") {
this.logger.info('GET pii-reporter');
const metadata = PackTheMetadata(user);
try {
const reporterHtml = await this.catalogService.getPiiReporter(metadata);
const reporter = await this.catalogService.getPiiReporter(metadata, type);
res.setHeader('Content-Type', 'text/html');
return res.send(reporterHtml);
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}`;
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
return res.send(reporter);
} catch (error) {
this.logger.error(error);
}
+43 -41
View File
@@ -34,8 +34,12 @@ import {
GetNimbusDashboardsRequest,
GetRlsRulesRequest,
} from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
import { readFileSync } from 'fs';
import { writeFileSync } from 'fs';
import path from 'path';
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';
class CatalogService implements OnModuleInit {
catalogReadService: ReadService.CatalogReadServices;
@@ -82,49 +86,47 @@ class CatalogService implements OnModuleInit {
)}.dadosfera.ai`;
}
async getPiiReporter(metadata: Metadata) {
const {
data
} = await lastValueFrom(
this.catalogWriteService.GetPiiReporter({}, metadata)
)
async getPiiReporter(metadata: Metadata, type: "html"| "csv") {
try {
const {
data
} = await lastValueFrom(
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',
);
const result = data.map((props) => ({
...props,
pii_rules: this.formatPiiRules(JSON.parse(props.pii_rules)),
}));
this.logger.info("templatePath: " + templatePath);
// 3. Ler o template HTML
const templatePath = path.join(
__dirname,
'../../../src/template/pii-reporter.html',
);
const templateSource = readFileSync(templatePath, 'utf8');
// 4. Compilar o template
const template = Handlebars.compile(templateSource);
// 5. Aplicar os dados ao template
return template({
dados: result,
dataGeracao: new Date().toLocaleString('pt-BR'),
});
}
private formatPiiRules(
data: Record<
string,
{
avg_score: number;
max_score: number;
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
}
>,
) {
return Object.keys(data)
.filter((key) => {
return data[key].max_score > 0.7;
})
.join(', ');
throw new Error("Unsupported Type: " + type)
} catch (error) {
this.logger.error(error);
}
}
async createDataAsset(data: Messages.CreateDataAssetRequest, metadata) {
+8
View File
@@ -0,0 +1,8 @@
export class PiiDto {
database_name: string;
table_schema: string;
table_name: string;
column_name: string;
data_type: string;
pii_rules: string;
}
+5 -2
View File
@@ -6,6 +6,10 @@
<title>Dadosfera Relatório de PII</title>
<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;
}
body {
font-family: 'Quicksand', sans-serif;
color: #5c5c5c;
@@ -13,8 +17,7 @@
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
margin: 0 24px;
}
.header {
display: flex;
+1
View File
@@ -14,6 +14,7 @@ interface IMetadata {
host?: string;
origin?: string;
custom_host?: string;
is_mask?: boolean;
}
export function PackTheMetadata(info: IMetadata): Metadata {
+14
View File
@@ -0,0 +1,14 @@
import { stringify } from 'csv'
export class CsvParser<T> {
async parse(data: T[]) {
return new Promise((resolve, reject) => {
stringify(data, { header: true }, function (err, output) {
if (err) reject(err);
console.log(output)
resolve(output)
});
})
}
}
+16
View File
@@ -0,0 +1,16 @@
import { readFileSync } from 'fs';
import Handlebars from 'handlebars';
export class HtmlParser<T> {
constructor(private readonly templatePath: string) {}
parse(data: T[]) {
const templateSource = readFileSync(this.templatePath, 'utf8');
const template = Handlebars.compile(templateSource);
return template({
dados: data,
dataGeracao: new Date().toLocaleString('pt-BR'),
});
}
}
+48
View File
@@ -0,0 +1,48 @@
import puppeteer from "puppeteer";
export class PDFParser<T extends string> {
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']
});
const page = await browser.newPage();
page.setDefaultNavigationTimeout(0);
await page.setContent(data, {
waitUntil: 'networkidle0',
timeout: 30000
});
// Configurações adicionais para garantir um PDF válido
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
top: '20mm',
right: '15mm',
bottom: '20mm',
left: '15mm'
},
preferCSSPageSize: true,
displayHeaderFooter: false,
headerTemplate: '',
footerTemplate: '',
omitBackground: false,
pageRanges: '',
tagged: true,
outline: false,
timeout: 30000
});
await browser.close();
return pdfBuffer;
} catch (error) {
console.error('Error generating PDF:', error);
throw error;
}
}
}