rl-core-api 0.16.8 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/core/export/export.constants.d.ts +1 -0
  2. package/dist/core/export/export.constants.js +4 -0
  3. package/dist/core/export/export.controller.js +8 -7
  4. package/dist/core/export/export.module.js +13 -2
  5. package/dist/core/export/export.processor.d.ts +5 -2
  6. package/dist/core/export/export.processor.js +30 -8
  7. package/dist/core/export/export.service.d.ts +2 -0
  8. package/dist/core/export/export.service.js +29 -9
  9. package/dist/core/export/exportDocument.interface.d.ts +19 -0
  10. package/dist/core/export/exportDocument.interface.js +7 -0
  11. package/dist/core/export/exportExtension.enum.d.ts +5 -0
  12. package/dist/core/export/exportExtension.enum.js +12 -0
  13. package/dist/core/export/exportHandler.interface.d.ts +3 -1
  14. package/dist/core/export/exportHandler.registry.d.ts +12 -2
  15. package/dist/core/export/exportHandler.registry.js +26 -10
  16. package/dist/core/export/exportKind.enum.d.ts +4 -0
  17. package/dist/core/export/exportKind.enum.js +8 -0
  18. package/dist/core/export/exportStorage.service.d.ts +5 -4
  19. package/dist/core/export/exportStorage.service.js +9 -9
  20. package/dist/core/export/pdf/pdf.constants.d.ts +32 -0
  21. package/dist/core/export/pdf/pdf.constants.js +32 -0
  22. package/dist/core/export/pdf/pdfColor.util.d.ts +1 -0
  23. package/dist/core/export/pdf/pdfColor.util.js +15 -0
  24. package/dist/core/export/pdf/pdfDocument.service.d.ts +28 -0
  25. package/dist/core/export/pdf/pdfDocument.service.js +243 -0
  26. package/dist/core/export/pdf/pdfFonts.util.d.ts +3 -0
  27. package/dist/core/export/pdf/pdfFonts.util.js +14 -0
  28. package/dist/core/export/pdf/pdfTheme.interface.d.ts +12 -0
  29. package/dist/core/export/pdf/pdfTheme.interface.js +2 -0
  30. package/dist/core/export/requestExport.command.d.ts +1 -0
  31. package/dist/core/export/requestExport.command.js +9 -0
  32. package/dist/index.d.ts +9 -1
  33. package/dist/index.js +17 -2
  34. package/package.json +3 -1
@@ -0,0 +1 @@
1
+ export declare const EXPORT_PARAMS_MAX_LENGTH = 4000;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXPORT_PARAMS_MAX_LENGTH = void 0;
4
+ exports.EXPORT_PARAMS_MAX_LENGTH = 4000;
@@ -20,11 +20,11 @@ const currentUser_decorator_1 = require("../auth/decorators/currentUser.decorato
20
20
  const permissions_guard_1 = require("../auth/guards/permissions.guard");
21
21
  const export_service_1 = require("./export.service");
22
22
  const exportAccepted_response_1 = require("./exportAccepted.response");
23
+ const exportExtension_enum_1 = require("./exportExtension.enum");
23
24
  const exportStatus_response_1 = require("./exportStatus.response");
24
25
  const exportStorage_service_1 = require("./exportStorage.service");
25
26
  const requestExport_command_1 = require("./requestExport.command");
26
- const XLSX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
27
- const sanitizeFileName = (name) => name.replace(/["\\/\r\n]+/g, "_").slice(0, 120) || "relatorio.xlsx";
27
+ const sanitizeFileName = (name, extension) => name.replace(/["\\/\r\n]+/g, "_").slice(0, 120) || `relatorio.${extension}`;
28
28
  let ExportController = class ExportController {
29
29
  constructor(exports, storage) {
30
30
  this.exports = exports;
@@ -52,13 +52,14 @@ let ExportController = class ExportController {
52
52
  if (status.state !== "completed" || !status.summary) {
53
53
  throw new common_1.ConflictException("O relatório ainda está sendo gerado");
54
54
  }
55
- if (!(await this.storage.exists(user.id, jobId))) {
55
+ const extension = status.summary.extension ?? exportExtension_enum_1.ExportExtension.Xlsx;
56
+ if (!(await this.storage.exists(user.id, jobId, extension))) {
56
57
  throw new common_1.GoneException("Este relatório expirou. Gere novamente para baixar.");
57
58
  }
58
- const fileName = sanitizeFileName(status.summary.fileName);
59
- response.setHeader("Content-Type", XLSX_CONTENT_TYPE);
59
+ const fileName = sanitizeFileName(status.summary.fileName, extension);
60
+ response.setHeader("Content-Type", exportExtension_enum_1.EXPORT_CONTENT_TYPES[extension]);
60
61
  response.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
61
- return new common_1.StreamableFile((0, fs_1.createReadStream)(this.storage.pathFor(user.id, jobId)));
62
+ return new common_1.StreamableFile((0, fs_1.createReadStream)(this.storage.pathFor(user.id, jobId, extension)));
62
63
  }
63
64
  };
64
65
  exports.ExportController = ExportController;
@@ -87,7 +88,7 @@ __decorate([
87
88
  __decorate([
88
89
  (0, common_1.Get)(":jobId"),
89
90
  (0, swagger_1.ApiParam)({ name: "jobId", example: "184" }),
90
- (0, swagger_1.ApiOkResponse)({ description: "A planilha", type: common_1.StreamableFile }),
91
+ (0, swagger_1.ApiOkResponse)({ description: "O arquivo gerado", type: common_1.StreamableFile }),
91
92
  (0, swagger_1.ApiResponse)({ status: 409, description: "Ainda em geração" }),
92
93
  (0, swagger_1.ApiResponse)({ status: 410, description: "O arquivo expirou" }),
93
94
  __param(0, (0, currentUser_decorator_1.CurrentUser)()),
@@ -12,13 +12,24 @@ const core_1 = require("@nestjs/core");
12
12
  const exportEngine_service_1 = require("./exportEngine.service");
13
13
  const exportHandler_registry_1 = require("./exportHandler.registry");
14
14
  const exportStorage_service_1 = require("./exportStorage.service");
15
+ const pdfDocument_service_1 = require("./pdf/pdfDocument.service");
15
16
  let ExportModule = class ExportModule {
16
17
  };
17
18
  exports.ExportModule = ExportModule;
18
19
  exports.ExportModule = ExportModule = __decorate([
19
20
  (0, common_1.Module)({
20
21
  imports: [core_1.DiscoveryModule],
21
- providers: [exportHandler_registry_1.ExportHandlerRegistry, exportEngine_service_1.ExportEngineService, exportStorage_service_1.ExportStorageService],
22
- exports: [exportHandler_registry_1.ExportHandlerRegistry, exportEngine_service_1.ExportEngineService, exportStorage_service_1.ExportStorageService],
22
+ providers: [
23
+ exportHandler_registry_1.ExportHandlerRegistry,
24
+ exportEngine_service_1.ExportEngineService,
25
+ exportStorage_service_1.ExportStorageService,
26
+ pdfDocument_service_1.PdfDocumentService,
27
+ ],
28
+ exports: [
29
+ exportHandler_registry_1.ExportHandlerRegistry,
30
+ exportEngine_service_1.ExportEngineService,
31
+ exportStorage_service_1.ExportStorageService,
32
+ pdfDocument_service_1.PdfDocumentService,
33
+ ],
23
34
  })
24
35
  ], ExportModule);
@@ -1,4 +1,5 @@
1
1
  import { Logger } from "@nestjs/common";
2
+ import { DocumentJobPayload } from "./exportDocument.interface";
2
3
  import { ExportEngineService } from "./exportEngine.service";
3
4
  import { ExportJobPayload, ExportJobSummary } from "./exportHandler.interface";
4
5
  import { ExportHandlerRegistry } from "./exportHandler.registry";
@@ -6,11 +7,13 @@ import { ExportStorageService } from "./exportStorage.service";
6
7
  import { JobContext } from "../queue/job.interface";
7
8
  import { JobEventsService } from "../queue/jobEvents.service";
8
9
  import { JobWorkerHost } from "../queue/jobWorkerHost";
9
- export declare class ExportProcessor extends JobWorkerHost<ExportJobPayload, ExportJobSummary> {
10
+ export declare class ExportProcessor extends JobWorkerHost<ExportJobPayload | DocumentJobPayload, ExportJobSummary> {
10
11
  private readonly registry;
11
12
  private readonly engine;
12
13
  private readonly storage;
13
14
  protected readonly logger: Logger;
14
15
  constructor(jobEvents: JobEventsService, registry: ExportHandlerRegistry, engine: ExportEngineService, storage: ExportStorageService);
15
- handle(job: JobContext<ExportJobPayload>): Promise<ExportJobSummary>;
16
+ handle(job: JobContext<ExportJobPayload | DocumentJobPayload>): Promise<ExportJobSummary>;
17
+ private sheet;
18
+ private document;
16
19
  }
@@ -15,11 +15,14 @@ const bullmq_1 = require("@nestjs/bullmq");
15
15
  const common_1 = require("@nestjs/common");
16
16
  const bullmq_2 = require("bullmq");
17
17
  const exportEngine_service_1 = require("./exportEngine.service");
18
+ const exportExtension_enum_1 = require("./exportExtension.enum");
18
19
  const exportHandler_registry_1 = require("./exportHandler.registry");
20
+ const exportKind_enum_1 = require("./exportKind.enum");
19
21
  const exportStorage_service_1 = require("./exportStorage.service");
20
22
  const jobEvents_service_1 = require("../queue/jobEvents.service");
21
23
  const jobWorkerHost_1 = require("../queue/jobWorkerHost");
22
24
  const queue_constants_1 = require("../queue/queue.constants");
25
+ const PERCENT_TOTAL = 100;
23
26
  let ExportProcessor = ExportProcessor_1 = class ExportProcessor extends jobWorkerHost_1.JobWorkerHost {
24
27
  constructor(jobEvents, registry, engine, storage) {
25
28
  super(jobEvents);
@@ -30,23 +33,42 @@ let ExportProcessor = ExportProcessor_1 = class ExportProcessor extends jobWorke
30
33
  }
31
34
  async handle(job) {
32
35
  const { jobId, ownerId, payload } = job;
33
- const handler = this.registry.find(payload.handler);
34
- if (!handler) {
36
+ const entry = this.registry.find(payload.handler);
37
+ if (!entry) {
35
38
  throw new bullmq_2.UnrecoverableError(`Relatório "${payload.handler}" não registrado neste consumer`);
36
39
  }
37
- const sink = await this.storage.openWriteStream(ownerId, jobId);
40
+ const extension = entry.kind === exportKind_enum_1.ExportKind.Document
41
+ ? entry.handler.extension
42
+ : exportExtension_enum_1.ExportExtension.Xlsx;
43
+ const sink = await this.storage.openWriteStream(ownerId, jobId, extension);
38
44
  try {
39
- const { totalRows } = await this.engine.run(handler, payload, ownerId, sink, async (processed) => {
40
- await job.report(processed, 0, `${processed} linha(s)`);
41
- });
42
- return { fileName: payload.fileName, totalRows };
45
+ const totalRows = entry.kind === exportKind_enum_1.ExportKind.Document
46
+ ? await this.document(entry.handler, payload, job, sink)
47
+ : await this.sheet(entry.handler, payload, job, sink);
48
+ return { fileName: payload.fileName, totalRows, extension };
43
49
  }
44
50
  catch (error) {
45
51
  sink.destroy();
46
- await this.storage.remove(ownerId, jobId).catch(() => undefined);
52
+ await this.storage
53
+ .remove(ownerId, jobId, extension)
54
+ .catch(() => undefined);
47
55
  throw error;
48
56
  }
49
57
  }
58
+ async sheet(handler, payload, job, sink) {
59
+ const { totalRows } = await this.engine.run(handler, payload, job.ownerId, sink, async (processed) => {
60
+ await job.report(processed, 0, `${processed} linha(s)`);
61
+ });
62
+ return totalRows;
63
+ }
64
+ async document(handler, payload, job, sink) {
65
+ const params = handler.parseParams(payload.params);
66
+ await handler.render({ ownerId: job.ownerId }, params, sink, async (percent, message) => {
67
+ await job.report(percent, PERCENT_TOTAL, message);
68
+ });
69
+ this.logger.log(`Documento "${handler.name}" gerado`);
70
+ return null;
71
+ }
50
72
  };
51
73
  exports.ExportProcessor = ExportProcessor;
52
74
  exports.ExportProcessor = ExportProcessor = ExportProcessor_1 = __decorate([
@@ -10,4 +10,6 @@ export declare class ExportService {
10
10
  constructor(registry: ExportHandlerRegistry, producer: JobProducer);
11
11
  request(user: AuthenticatedUser, name: string, command: RequestExportCommand): Promise<JobRef>;
12
12
  status(jobId: string, ownerId: string): Promise<JobStatus<ExportJobSummary>>;
13
+ private sheetPayload;
14
+ private documentPayload;
13
15
  }
@@ -11,7 +11,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ExportService = void 0;
13
13
  const common_1 = require("@nestjs/common");
14
+ const export_constants_1 = require("./export.constants");
14
15
  const exportHandler_registry_1 = require("./exportHandler.registry");
16
+ const exportKind_enum_1 = require("./exportKind.enum");
15
17
  const parseFilter_util_1 = require("../query/parseFilter.util");
16
18
  const job_producer_1 = require("../queue/job.producer");
17
19
  const queue_constants_1 = require("../queue/queue.constants");
@@ -21,15 +23,28 @@ let ExportService = class ExportService {
21
23
  this.producer = producer;
22
24
  }
23
25
  async request(user, name, command) {
24
- const handler = this.registry.require(name);
25
- if (handler.permission && !user.permissions.includes(handler.permission)) {
26
+ const entry = this.registry.require(name);
27
+ const { permission } = entry.handler;
28
+ if (permission && !user.permissions.includes(permission)) {
26
29
  throw new common_1.ForbiddenException("Você não tem permissão para exportar este relatório");
27
30
  }
28
31
  if (!this.producer.isAvailable(queue_constants_1.EXPORTS_QUEUE)) {
29
32
  throw new common_1.ServiceUnavailableException("Geração de relatórios indisponível neste ambiente");
30
33
  }
34
+ const payload = entry.kind === exportKind_enum_1.ExportKind.Document
35
+ ? this.documentPayload(entry.handler, command)
36
+ : this.sheetPayload(entry.handler, command, user);
37
+ return await this.producer.enqueue(entry.handler.name, payload, {
38
+ ownerId: user.id,
39
+ queue: queue_constants_1.EXPORTS_QUEUE,
40
+ });
41
+ }
42
+ async status(jobId, ownerId) {
43
+ return await this.producer.status(jobId, ownerId, queue_constants_1.EXPORTS_QUEUE);
44
+ }
45
+ sheetPayload(handler, command, user) {
31
46
  const filter = (0, parseFilter_util_1.parseFilter)(command.filter, handler.catalog, user.permissions);
32
- const payload = {
47
+ return {
33
48
  handler: handler.name,
34
49
  filter,
35
50
  search: command.search,
@@ -37,13 +52,18 @@ let ExportService = class ExportService {
37
52
  sortDir: command.sortDir,
38
53
  fileName: handler.buildFileName(),
39
54
  };
40
- return await this.producer.enqueue(handler.name, payload, {
41
- ownerId: user.id,
42
- queue: queue_constants_1.EXPORTS_QUEUE,
43
- });
44
55
  }
45
- async status(jobId, ownerId) {
46
- return await this.producer.status(jobId, ownerId, queue_constants_1.EXPORTS_QUEUE);
56
+ documentPayload(handler, command) {
57
+ const raw = command.params ?? {};
58
+ if (JSON.stringify(raw).length > export_constants_1.EXPORT_PARAMS_MAX_LENGTH) {
59
+ throw new common_1.BadRequestException("Parâmetros do relatório grandes demais");
60
+ }
61
+ const params = handler.parseParams(raw);
62
+ return {
63
+ handler: handler.name,
64
+ params: raw,
65
+ fileName: handler.buildFileName(params),
66
+ };
47
67
  }
48
68
  };
49
69
  exports.ExportService = ExportService;
@@ -0,0 +1,19 @@
1
+ import { Writable } from "stream";
2
+ import { ExportExtension } from "./exportExtension.enum";
3
+ import { ExportContext } from "./exportHandler.interface";
4
+ export type ExportProgress = (percent: number, message: string) => Promise<void>;
5
+ export interface DocumentHandler<TParams = unknown> {
6
+ readonly name: string;
7
+ readonly permission?: string;
8
+ readonly extension: ExportExtension;
9
+ parseParams(raw: unknown): TParams;
10
+ buildFileName(params: TParams): string;
11
+ render(context: ExportContext, params: TParams, sink: Writable, progress: ExportProgress): Promise<void>;
12
+ }
13
+ export declare const EXPORT_DOCUMENT = "rl-core:export-document";
14
+ export declare const ExportDocument: () => ClassDecorator;
15
+ export interface DocumentJobPayload {
16
+ handler: string;
17
+ params: unknown;
18
+ fileName: string;
19
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExportDocument = exports.EXPORT_DOCUMENT = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.EXPORT_DOCUMENT = "rl-core:export-document";
6
+ const ExportDocument = () => (0, common_1.SetMetadata)(exports.EXPORT_DOCUMENT, true);
7
+ exports.ExportDocument = ExportDocument;
@@ -0,0 +1,5 @@
1
+ export declare enum ExportExtension {
2
+ Xlsx = "xlsx",
3
+ Pdf = "pdf"
4
+ }
5
+ export declare const EXPORT_CONTENT_TYPES: Record<ExportExtension, string>;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXPORT_CONTENT_TYPES = exports.ExportExtension = void 0;
4
+ var ExportExtension;
5
+ (function (ExportExtension) {
6
+ ExportExtension["Xlsx"] = "xlsx";
7
+ ExportExtension["Pdf"] = "pdf";
8
+ })(ExportExtension || (exports.ExportExtension = ExportExtension = {}));
9
+ exports.EXPORT_CONTENT_TYPES = {
10
+ [ExportExtension.Xlsx]: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
11
+ [ExportExtension.Pdf]: "application/pdf",
12
+ };
@@ -1,5 +1,6 @@
1
1
  import { ObjectLiteral, SelectQueryBuilder } from "typeorm";
2
2
  import { ExportColumn } from "./exportColumn.interface";
3
+ import { ExportExtension } from "./exportExtension.enum";
3
4
  import { FilterCatalog, FilterNode } from "../query/filterField.interface";
4
5
  import { SortDir } from "../utils/pagination.util";
5
6
  export interface ExportContext {
@@ -28,5 +29,6 @@ export interface ExportJobPayload {
28
29
  }
29
30
  export interface ExportJobSummary {
30
31
  fileName: string;
31
- totalRows: number;
32
+ totalRows: number | null;
33
+ extension?: ExportExtension;
32
34
  }
@@ -1,13 +1,23 @@
1
1
  import { OnModuleInit } from "@nestjs/common";
2
2
  import { DiscoveryService } from "@nestjs/core";
3
+ import { DocumentHandler } from "./exportDocument.interface";
3
4
  import { ExportHandler } from "./exportHandler.interface";
5
+ import { ExportKind } from "./exportKind.enum";
6
+ export type RegisteredExport = {
7
+ kind: ExportKind.Sheet;
8
+ handler: ExportHandler;
9
+ } | {
10
+ kind: ExportKind.Document;
11
+ handler: DocumentHandler;
12
+ };
4
13
  export declare class ExportHandlerRegistry implements OnModuleInit {
5
14
  private readonly discovery;
6
15
  private readonly logger;
7
16
  private readonly handlers;
8
17
  constructor(discovery: DiscoveryService);
9
18
  onModuleInit(): void;
10
- find(name: string): ExportHandler | undefined;
11
- require(name: string): ExportHandler;
19
+ find(name: string): RegisteredExport | undefined;
20
+ require(name: string): RegisteredExport;
12
21
  names(): string[];
22
+ private classify;
13
23
  }
@@ -13,7 +13,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.ExportHandlerRegistry = void 0;
14
14
  const common_1 = require("@nestjs/common");
15
15
  const core_1 = require("@nestjs/core");
16
+ const exportDocument_interface_1 = require("./exportDocument.interface");
16
17
  const exportHandler_interface_1 = require("./exportHandler.interface");
18
+ const exportKind_enum_1 = require("./exportKind.enum");
17
19
  let ExportHandlerRegistry = ExportHandlerRegistry_1 = class ExportHandlerRegistry {
18
20
  constructor(discovery) {
19
21
  this.discovery = discovery;
@@ -21,17 +23,16 @@ let ExportHandlerRegistry = ExportHandlerRegistry_1 = class ExportHandlerRegistr
21
23
  this.handlers = new Map();
22
24
  }
23
25
  onModuleInit() {
24
- const found = this.discovery
25
- .getProviders()
26
- .filter((wrapper) => wrapper.metatype &&
27
- Reflect.getMetadata(exportHandler_interface_1.EXPORT_HANDLER, wrapper.metatype) === true)
28
- .map((wrapper) => wrapper.instance)
29
- .filter(Boolean);
30
- for (const handler of found) {
31
- if (this.handlers.has(handler.name)) {
32
- throw new Error(`Dois relatórios registrados com o nome "${handler.name}"`);
26
+ for (const wrapper of this.discovery.getProviders()) {
27
+ const entry = this.classify(wrapper.metatype, wrapper.instance);
28
+ if (!entry) {
29
+ continue;
33
30
  }
34
- this.handlers.set(handler.name, handler);
31
+ const { name } = entry.handler;
32
+ if (this.handlers.has(name)) {
33
+ throw new Error(`Dois relatórios registrados com o nome "${name}"`);
34
+ }
35
+ this.handlers.set(name, entry);
35
36
  }
36
37
  if (this.handlers.size > 0) {
37
38
  this.logger.log(`Relatórios disponíveis: ${this.names().join(", ")}`);
@@ -50,6 +51,21 @@ let ExportHandlerRegistry = ExportHandlerRegistry_1 = class ExportHandlerRegistr
50
51
  names() {
51
52
  return [...this.handlers.keys()];
52
53
  }
54
+ classify(metatype, instance) {
55
+ if (typeof metatype !== "function" || !instance) {
56
+ return null;
57
+ }
58
+ if (Reflect.getMetadata(exportHandler_interface_1.EXPORT_HANDLER, metatype) === true) {
59
+ return { kind: exportKind_enum_1.ExportKind.Sheet, handler: instance };
60
+ }
61
+ if (Reflect.getMetadata(exportDocument_interface_1.EXPORT_DOCUMENT, metatype) === true) {
62
+ return {
63
+ kind: exportKind_enum_1.ExportKind.Document,
64
+ handler: instance,
65
+ };
66
+ }
67
+ return null;
68
+ }
53
69
  };
54
70
  exports.ExportHandlerRegistry = ExportHandlerRegistry;
55
71
  exports.ExportHandlerRegistry = ExportHandlerRegistry = ExportHandlerRegistry_1 = __decorate([
@@ -0,0 +1,4 @@
1
+ export declare enum ExportKind {
2
+ Sheet = "SHEET",
3
+ Document = "DOCUMENT"
4
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExportKind = void 0;
4
+ var ExportKind;
5
+ (function (ExportKind) {
6
+ ExportKind["Sheet"] = "SHEET";
7
+ ExportKind["Document"] = "DOCUMENT";
8
+ })(ExportKind || (exports.ExportKind = ExportKind = {}));
@@ -1,13 +1,14 @@
1
1
  import { WriteStream } from "fs";
2
2
  import { EnvService } from "../config/env.service";
3
+ import { ExportExtension } from "./exportExtension.enum";
3
4
  export declare class ExportStorageService {
4
5
  private readonly env;
5
6
  private readonly logger;
6
7
  private readonly root;
7
8
  constructor(env: EnvService);
8
- pathFor(ownerId: string, jobId: string): string;
9
- openWriteStream(ownerId: string, jobId: string): Promise<WriteStream>;
10
- exists(ownerId: string, jobId: string): Promise<boolean>;
11
- remove(ownerId: string, jobId: string): Promise<void>;
9
+ pathFor(ownerId: string, jobId: string, extension?: ExportExtension): string;
10
+ openWriteStream(ownerId: string, jobId: string, extension?: ExportExtension): Promise<WriteStream>;
11
+ exists(ownerId: string, jobId: string, extension?: ExportExtension): Promise<boolean>;
12
+ remove(ownerId: string, jobId: string, extension?: ExportExtension): Promise<void>;
12
13
  removeExpired(maxAgeMs: number): Promise<number>;
13
14
  }
@@ -16,8 +16,8 @@ const fs_1 = require("fs");
16
16
  const promises_1 = require("fs/promises");
17
17
  const path_1 = require("path");
18
18
  const env_service_1 = require("../config/env.service");
19
+ const exportExtension_enum_1 = require("./exportExtension.enum");
19
20
  const SAFE_ID = /^[A-Za-z0-9_-]{1,64}$/;
20
- const EXTENSION = ".xlsx";
21
21
  let ExportStorageService = ExportStorageService_1 = class ExportStorageService {
22
22
  constructor(env) {
23
23
  this.env = env;
@@ -27,28 +27,28 @@ let ExportStorageService = ExportStorageService_1 = class ExportStorageService {
27
27
  ? configured
28
28
  : (0, path_1.join)(process.cwd(), configured);
29
29
  }
30
- pathFor(ownerId, jobId) {
30
+ pathFor(ownerId, jobId, extension = exportExtension_enum_1.ExportExtension.Xlsx) {
31
31
  if (!SAFE_ID.test(ownerId) || !SAFE_ID.test(jobId)) {
32
32
  throw new Error("Identificador inválido para caminho de relatório");
33
33
  }
34
- const path = (0, path_1.resolve)(this.root, ownerId, `${jobId}${EXTENSION}`);
34
+ const path = (0, path_1.resolve)(this.root, ownerId, `${jobId}.${extension}`);
35
35
  if (!path.startsWith((0, path_1.resolve)(this.root))) {
36
36
  throw new Error("Caminho de relatório fora da pasta de exportação");
37
37
  }
38
38
  return path;
39
39
  }
40
- async openWriteStream(ownerId, jobId) {
41
- const path = this.pathFor(ownerId, jobId);
40
+ async openWriteStream(ownerId, jobId, extension = exportExtension_enum_1.ExportExtension.Xlsx) {
41
+ const path = this.pathFor(ownerId, jobId, extension);
42
42
  await (0, promises_1.mkdir)((0, path_1.dirname)(path), { recursive: true });
43
43
  return (0, fs_1.createWriteStream)(path);
44
44
  }
45
- async exists(ownerId, jobId) {
46
- return await (0, promises_1.stat)(this.pathFor(ownerId, jobId))
45
+ async exists(ownerId, jobId, extension = exportExtension_enum_1.ExportExtension.Xlsx) {
46
+ return await (0, promises_1.stat)(this.pathFor(ownerId, jobId, extension))
47
47
  .then(() => true)
48
48
  .catch(() => false);
49
49
  }
50
- async remove(ownerId, jobId) {
51
- await (0, promises_1.rm)(this.pathFor(ownerId, jobId), { force: true });
50
+ async remove(ownerId, jobId, extension = exportExtension_enum_1.ExportExtension.Xlsx) {
51
+ await (0, promises_1.rm)(this.pathFor(ownerId, jobId, extension), { force: true });
52
52
  }
53
53
  async removeExpired(maxAgeMs) {
54
54
  const cutoff = Date.now() - maxAgeMs;
@@ -0,0 +1,32 @@
1
+ export declare const PDF_PAGE_SIZE = "A4";
2
+ export declare const PDF_A4: {
3
+ readonly width: 595.28;
4
+ readonly height: 841.89;
5
+ };
6
+ export declare const PDF_PAGE_MARGINS: [number, number, number, number];
7
+ export declare const PDF_FONT_SIZE: {
8
+ readonly base: 9;
9
+ readonly small: 7.5;
10
+ readonly h1: 18;
11
+ readonly h2: 12;
12
+ readonly section: 10.5;
13
+ };
14
+ export declare const PDF_LOGO_TIMEOUT_MS = 5000;
15
+ export declare const PDF_LOGO_MAX_BYTES: number;
16
+ export declare const PDF_LOGO_FIT: [number, number];
17
+ export declare const PDF_LOGO_SCALE = 3;
18
+ export declare const PDF_LOGO_TYPES: readonly ["image/png", "image/jpeg"];
19
+ export declare const PDF_STYLE: {
20
+ readonly h1: "h1";
21
+ readonly h2: "h2";
22
+ readonly section: "section";
23
+ readonly tableHeader: "tableHeader";
24
+ readonly money: "money";
25
+ readonly muted: "muted";
26
+ readonly small: "small";
27
+ readonly strong: "strong";
28
+ };
29
+ export declare const PDF_TABLE_LAYOUT: {
30
+ readonly clean: "rlClean";
31
+ readonly striped: "rlStriped";
32
+ };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PDF_TABLE_LAYOUT = exports.PDF_STYLE = exports.PDF_LOGO_TYPES = exports.PDF_LOGO_SCALE = exports.PDF_LOGO_FIT = exports.PDF_LOGO_MAX_BYTES = exports.PDF_LOGO_TIMEOUT_MS = exports.PDF_FONT_SIZE = exports.PDF_PAGE_MARGINS = exports.PDF_A4 = exports.PDF_PAGE_SIZE = void 0;
4
+ exports.PDF_PAGE_SIZE = "A4";
5
+ exports.PDF_A4 = { width: 595.28, height: 841.89 };
6
+ exports.PDF_PAGE_MARGINS = [28, 44, 28, 40];
7
+ exports.PDF_FONT_SIZE = {
8
+ base: 9,
9
+ small: 7.5,
10
+ h1: 18,
11
+ h2: 12,
12
+ section: 10.5,
13
+ };
14
+ exports.PDF_LOGO_TIMEOUT_MS = 5_000;
15
+ exports.PDF_LOGO_MAX_BYTES = 2 * 1024 * 1024;
16
+ exports.PDF_LOGO_FIT = [120, 36];
17
+ exports.PDF_LOGO_SCALE = 3;
18
+ exports.PDF_LOGO_TYPES = ["image/png", "image/jpeg"];
19
+ exports.PDF_STYLE = {
20
+ h1: "h1",
21
+ h2: "h2",
22
+ section: "section",
23
+ tableHeader: "tableHeader",
24
+ money: "money",
25
+ muted: "muted",
26
+ small: "small",
27
+ strong: "strong",
28
+ };
29
+ exports.PDF_TABLE_LAYOUT = {
30
+ clean: "rlClean",
31
+ striped: "rlStriped",
32
+ };
@@ -0,0 +1 @@
1
+ export declare const mixWithWhite: (hex: string, ratio: number) => string;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mixWithWhite = void 0;
4
+ const parseHex = (hex) => [
5
+ parseInt(hex.slice(1, 3), 16),
6
+ parseInt(hex.slice(3, 5), 16),
7
+ parseInt(hex.slice(5, 7), 16),
8
+ ];
9
+ const toHex = (channel) => Math.round(channel).toString(16).padStart(2, "0");
10
+ const mixWithWhite = (hex, ratio) => {
11
+ const [r, g, b] = parseHex(hex);
12
+ const mix = (channel) => channel + (255 - channel) * ratio;
13
+ return `#${toHex(mix(r))}${toHex(mix(g))}${toHex(mix(b))}`;
14
+ };
15
+ exports.mixWithWhite = mixWithWhite;
@@ -0,0 +1,28 @@
1
+ import type { Content } from "pdfmake/interfaces";
2
+ import { Writable } from "stream";
3
+ import { EnvService } from "../../config/env.service";
4
+ import { PdfTheme } from "./pdfTheme.interface";
5
+ export interface PdfFrame {
6
+ title: string;
7
+ subtitle?: string;
8
+ details?: string[];
9
+ }
10
+ export interface PdfDocument {
11
+ frame: PdfFrame;
12
+ content: Content;
13
+ landscape?: boolean;
14
+ }
15
+ export declare class PdfDocumentService {
16
+ private readonly env;
17
+ private readonly logger;
18
+ readonly theme: PdfTheme;
19
+ private logo;
20
+ constructor(env: EnvService);
21
+ write(document: PdfDocument, sink: Writable): Promise<void>;
22
+ private definition;
23
+ private cover;
24
+ private styles;
25
+ private tableLayouts;
26
+ private loadLogo;
27
+ private fetchLogo;
28
+ }
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ var PdfDocumentService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.PdfDocumentService = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const pdfmake_1 = __importDefault(require("pdfmake"));
19
+ const sharp_1 = __importDefault(require("sharp"));
20
+ const promises_1 = require("stream/promises");
21
+ const env_service_1 = require("../../config/env.service");
22
+ const pdf_constants_1 = require("./pdf.constants");
23
+ const pdfColor_util_1 = require("./pdfColor.util");
24
+ const pdfFonts_util_1 = require("./pdfFonts.util");
25
+ const contentWidth = (landscape) => (landscape ? pdf_constants_1.PDF_A4.height : pdf_constants_1.PDF_A4.width) -
26
+ pdf_constants_1.PDF_PAGE_MARGINS[0] -
27
+ pdf_constants_1.PDF_PAGE_MARGINS[2];
28
+ let PdfDocumentService = PdfDocumentService_1 = class PdfDocumentService {
29
+ constructor(env) {
30
+ this.env = env;
31
+ this.logger = new common_1.Logger(PdfDocumentService_1.name);
32
+ this.logo = null;
33
+ const brand = this.env.get("APP_BRAND_COLOR");
34
+ this.theme = {
35
+ appName: this.env.get("APP_NAME"),
36
+ brand,
37
+ brandTint: (0, pdfColor_util_1.mixWithWhite)(brand, 0.9),
38
+ text: "#1f2937",
39
+ muted: "#6b7280",
40
+ line: "#e5e7eb",
41
+ stripe: "#f9fafb",
42
+ positive: "#15803d",
43
+ warning: "#b45309",
44
+ onBrand: "#ffffff",
45
+ };
46
+ pdfmake_1.default.setFonts({ Roboto: (0, pdfFonts_util_1.robotoFonts)() });
47
+ pdfmake_1.default.setTableLayouts(this.tableLayouts());
48
+ pdfmake_1.default.setUrlAccessPolicy(() => false);
49
+ pdfmake_1.default.setLocalAccessPolicy(pdfFonts_util_1.isRobotoFile);
50
+ }
51
+ async write(document, sink) {
52
+ const definition = await this.definition(document);
53
+ const stream = await pdfmake_1.default.createPdf(definition).getStream();
54
+ stream.pipe(sink);
55
+ stream.end();
56
+ await (0, promises_1.finished)(sink);
57
+ }
58
+ async definition(document) {
59
+ const { frame, content, landscape } = document;
60
+ const { theme } = this;
61
+ const logo = await this.loadLogo();
62
+ const running = [theme.appName, frame.title, frame.subtitle]
63
+ .filter(Boolean)
64
+ .join(" · ");
65
+ return {
66
+ pageSize: pdf_constants_1.PDF_PAGE_SIZE,
67
+ pageOrientation: landscape ? "landscape" : "portrait",
68
+ pageMargins: pdf_constants_1.PDF_PAGE_MARGINS,
69
+ info: { title: running, author: theme.appName, creator: theme.appName },
70
+ defaultStyle: {
71
+ font: "Roboto",
72
+ fontSize: pdf_constants_1.PDF_FONT_SIZE.base,
73
+ color: theme.text,
74
+ lineHeight: 1.2,
75
+ },
76
+ styles: this.styles(),
77
+ header: (page) => page === 1
78
+ ? ""
79
+ : {
80
+ text: running,
81
+ style: pdf_constants_1.PDF_STYLE.small,
82
+ color: theme.muted,
83
+ margin: [pdf_constants_1.PDF_PAGE_MARGINS[0], 18, pdf_constants_1.PDF_PAGE_MARGINS[2], 0],
84
+ },
85
+ footer: (page, pages) => ({
86
+ columns: [
87
+ { text: running, style: pdf_constants_1.PDF_STYLE.small, color: theme.muted },
88
+ {
89
+ text: `Página ${page} de ${pages}`,
90
+ style: pdf_constants_1.PDF_STYLE.small,
91
+ color: theme.muted,
92
+ alignment: "right",
93
+ },
94
+ ],
95
+ margin: [pdf_constants_1.PDF_PAGE_MARGINS[0], 14, pdf_constants_1.PDF_PAGE_MARGINS[2], 0],
96
+ }),
97
+ content: [this.cover(frame, logo, landscape === true), content],
98
+ };
99
+ }
100
+ cover(frame, logo, landscape) {
101
+ const { theme } = this;
102
+ const left = logo
103
+ ? [{ text: frame.title, style: pdf_constants_1.PDF_STYLE.h1 }]
104
+ : [
105
+ { text: theme.appName, style: pdf_constants_1.PDF_STYLE.small, color: theme.brand },
106
+ { text: frame.title, style: pdf_constants_1.PDF_STYLE.h1 },
107
+ ];
108
+ if (frame.subtitle) {
109
+ left.push({ text: frame.subtitle, style: pdf_constants_1.PDF_STYLE.h2, color: theme.muted });
110
+ }
111
+ return {
112
+ stack: [
113
+ {
114
+ columns: [
115
+ logo
116
+ ? {
117
+ width: pdf_constants_1.PDF_LOGO_FIT[0] + 12,
118
+ image: logo,
119
+ fit: pdf_constants_1.PDF_LOGO_FIT,
120
+ margin: [0, 2, 12, 0],
121
+ }
122
+ : { width: 0, text: "" },
123
+ { width: "*", stack: left },
124
+ {
125
+ width: "auto",
126
+ stack: (frame.details ?? []).map((line) => ({
127
+ text: line,
128
+ style: pdf_constants_1.PDF_STYLE.small,
129
+ color: theme.muted,
130
+ alignment: "right",
131
+ })),
132
+ },
133
+ ],
134
+ columnGap: 8,
135
+ },
136
+ {
137
+ canvas: [
138
+ {
139
+ type: "line",
140
+ x1: 0,
141
+ y1: 0,
142
+ x2: contentWidth(landscape),
143
+ y2: 0,
144
+ lineWidth: 1.5,
145
+ lineColor: theme.brand,
146
+ },
147
+ ],
148
+ margin: [0, 10, 0, 14],
149
+ },
150
+ ],
151
+ };
152
+ }
153
+ styles() {
154
+ const { theme } = this;
155
+ return {
156
+ [pdf_constants_1.PDF_STYLE.h1]: { fontSize: pdf_constants_1.PDF_FONT_SIZE.h1, bold: true },
157
+ [pdf_constants_1.PDF_STYLE.h2]: { fontSize: pdf_constants_1.PDF_FONT_SIZE.h2 },
158
+ [pdf_constants_1.PDF_STYLE.section]: {
159
+ fontSize: pdf_constants_1.PDF_FONT_SIZE.section,
160
+ bold: true,
161
+ color: theme.brand,
162
+ margin: [0, 12, 0, 6],
163
+ },
164
+ [pdf_constants_1.PDF_STYLE.tableHeader]: {
165
+ bold: true,
166
+ fontSize: pdf_constants_1.PDF_FONT_SIZE.small,
167
+ color: theme.brand,
168
+ fillColor: theme.brandTint,
169
+ characterSpacing: 0.4,
170
+ },
171
+ [pdf_constants_1.PDF_STYLE.money]: { alignment: "right" },
172
+ [pdf_constants_1.PDF_STYLE.muted]: { color: theme.muted },
173
+ [pdf_constants_1.PDF_STYLE.small]: { fontSize: pdf_constants_1.PDF_FONT_SIZE.small },
174
+ [pdf_constants_1.PDF_STYLE.strong]: { bold: true },
175
+ };
176
+ }
177
+ tableLayouts() {
178
+ const { theme } = this;
179
+ const clean = {
180
+ hLineWidth: (i, node) => (i === 0 || i === node.table.body.length ? 0 : 0.5),
181
+ vLineWidth: () => 0,
182
+ hLineColor: () => theme.line,
183
+ paddingLeft: () => 6,
184
+ paddingRight: () => 6,
185
+ paddingTop: () => 4,
186
+ paddingBottom: () => 4,
187
+ };
188
+ return {
189
+ [pdf_constants_1.PDF_TABLE_LAYOUT.clean]: clean,
190
+ [pdf_constants_1.PDF_TABLE_LAYOUT.striped]: {
191
+ ...clean,
192
+ fillColor: (row, node) => row >= (node.table.headerRows ?? 0) && row % 2 === 0
193
+ ? theme.stripe
194
+ : null,
195
+ },
196
+ };
197
+ }
198
+ loadLogo() {
199
+ if (!this.logo) {
200
+ this.logo = this.fetchLogo().catch((error) => {
201
+ this.logger.warn(`Relatório sem logo: ${error.message}`);
202
+ this.logo = null;
203
+ return null;
204
+ });
205
+ }
206
+ return this.logo;
207
+ }
208
+ async fetchLogo() {
209
+ const url = this.env.get("APP_LOGO_URL");
210
+ if (!url) {
211
+ return null;
212
+ }
213
+ const response = await fetch(url, {
214
+ signal: AbortSignal.timeout(pdf_constants_1.PDF_LOGO_TIMEOUT_MS),
215
+ });
216
+ if (!response.ok) {
217
+ throw new Error(`${url} respondeu ${response.status}`);
218
+ }
219
+ const type = (response.headers.get("content-type") ?? "").split(";")[0];
220
+ if (!pdf_constants_1.PDF_LOGO_TYPES.includes(type)) {
221
+ throw new Error(`${url} não é PNG nem JPEG (${type || "sem tipo"})`);
222
+ }
223
+ const bytes = Buffer.from(await response.arrayBuffer());
224
+ if (bytes.byteLength > pdf_constants_1.PDF_LOGO_MAX_BYTES) {
225
+ throw new Error(`${url} passa de ${pdf_constants_1.PDF_LOGO_MAX_BYTES} bytes`);
226
+ }
227
+ const reduced = await (0, sharp_1.default)(bytes)
228
+ .resize({
229
+ width: pdf_constants_1.PDF_LOGO_FIT[0] * pdf_constants_1.PDF_LOGO_SCALE,
230
+ height: pdf_constants_1.PDF_LOGO_FIT[1] * pdf_constants_1.PDF_LOGO_SCALE,
231
+ fit: "inside",
232
+ withoutEnlargement: true,
233
+ })
234
+ .png()
235
+ .toBuffer();
236
+ return `data:image/png;base64,${reduced.toString("base64")}`;
237
+ }
238
+ };
239
+ exports.PdfDocumentService = PdfDocumentService;
240
+ exports.PdfDocumentService = PdfDocumentService = PdfDocumentService_1 = __decorate([
241
+ (0, common_1.Injectable)(),
242
+ __metadata("design:paramtypes", [env_service_1.EnvService])
243
+ ], PdfDocumentService);
@@ -0,0 +1,3 @@
1
+ import type { TFontFamilyTypes } from "pdfmake/interfaces";
2
+ export declare const robotoFonts: () => TFontFamilyTypes;
3
+ export declare const isRobotoFile: (path: string) => boolean;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRobotoFile = exports.robotoFonts = void 0;
4
+ const path_1 = require("path");
5
+ const ROBOTO_DIR = (0, path_1.join)((0, path_1.dirname)(require.resolve("pdfmake/package.json")), "build", "fonts", "Roboto");
6
+ const robotoFonts = () => ({
7
+ normal: (0, path_1.join)(ROBOTO_DIR, "Roboto-Regular.ttf"),
8
+ bold: (0, path_1.join)(ROBOTO_DIR, "Roboto-Medium.ttf"),
9
+ italics: (0, path_1.join)(ROBOTO_DIR, "Roboto-Italic.ttf"),
10
+ bolditalics: (0, path_1.join)(ROBOTO_DIR, "Roboto-MediumItalic.ttf"),
11
+ });
12
+ exports.robotoFonts = robotoFonts;
13
+ const isRobotoFile = (path) => (0, path_1.resolve)(path).startsWith(ROBOTO_DIR + path_1.sep);
14
+ exports.isRobotoFile = isRobotoFile;
@@ -0,0 +1,12 @@
1
+ export interface PdfTheme {
2
+ appName: string;
3
+ brand: string;
4
+ brandTint: string;
5
+ text: string;
6
+ muted: string;
7
+ line: string;
8
+ stripe: string;
9
+ positive: string;
10
+ warning: string;
11
+ onBrand: string;
12
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,4 +3,5 @@ export declare class RequestExportCommand {
3
3
  search?: string;
4
4
  sortBy?: string;
5
5
  sortDir?: "ASC" | "DESC";
6
+ params?: Record<string, unknown>;
6
7
  }
@@ -49,3 +49,12 @@ __decorate([
49
49
  (0, class_validator_1.IsIn)(["ASC", "DESC"]),
50
50
  __metadata("design:type", String)
51
51
  ], RequestExportCommand.prototype, "sortDir", void 0);
52
+ __decorate([
53
+ (0, swagger_1.ApiPropertyOptional)({
54
+ description: "Parâmetros do documento, validados pelo relatório pedido",
55
+ example: { referenceMonth: "2026-09" },
56
+ }),
57
+ (0, class_validator_1.IsOptional)(),
58
+ (0, class_validator_1.IsObject)(),
59
+ __metadata("design:type", Object)
60
+ ], RequestExportCommand.prototype, "params", void 0);
package/dist/index.d.ts CHANGED
@@ -75,6 +75,7 @@ export { QueueModule } from "./core/queue/queue.module";
75
75
  export { buildRedisOptions } from "./core/queue/redisConnection";
76
76
  export { RedisJobEventsBridge } from "./core/queue/redisJobEvents.bridge";
77
77
  export { jobEventsChannel, JobEventsMessage, RedisJobEventsPublisher, } from "./core/queue/redisJobEvents.publisher";
78
+ export { EXPORT_PARAMS_MAX_LENGTH } from "./core/export/export.constants";
78
79
  export { ExportController } from "./core/export/export.controller";
79
80
  export { ExportModule } from "./core/export/export.module";
80
81
  export { ExportProcessor } from "./core/export/export.processor";
@@ -83,13 +84,20 @@ export { ExportAcceptedResponse } from "./core/export/exportAccepted.response";
83
84
  export { ExportApiModule } from "./core/export/exportApi.module";
84
85
  export { ExportCleanupService } from "./core/export/exportCleanup.service";
85
86
  export { ExportColumn, ExportColumnFormat, } from "./core/export/exportColumn.interface";
87
+ export { DocumentHandler, DocumentJobPayload, EXPORT_DOCUMENT, ExportDocument, ExportProgress, } from "./core/export/exportDocument.interface";
86
88
  export { ExportEngineService } from "./core/export/exportEngine.service";
89
+ export { EXPORT_CONTENT_TYPES, ExportExtension, } from "./core/export/exportExtension.enum";
87
90
  export { EXPORT_HANDLER, ExportContext, ExportHandler, ExportJobPayload, ExportJobSummary, ExportReport, } from "./core/export/exportHandler.interface";
88
- export { ExportHandlerRegistry } from "./core/export/exportHandler.registry";
91
+ export { ExportHandlerRegistry, RegisteredExport, } from "./core/export/exportHandler.registry";
92
+ export { ExportKind } from "./core/export/exportKind.enum";
89
93
  export { ExportStatusResponse } from "./core/export/exportStatus.response";
90
94
  export { ExportStorageService } from "./core/export/exportStorage.service";
91
95
  export { ExportWorkerModule } from "./core/export/exportWorker.module";
96
+ export { PDF_STYLE, PDF_TABLE_LAYOUT, } from "./core/export/pdf/pdf.constants";
97
+ export { PdfDocument, PdfDocumentService, PdfFrame, } from "./core/export/pdf/pdfDocument.service";
98
+ export { PdfTheme } from "./core/export/pdf/pdfTheme.interface";
92
99
  export { RequestExportCommand } from "./core/export/requestExport.command";
100
+ export type { Content, ContentColumns, ContentStack, ContentTable, ContentText, TableCell, } from "pdfmake/interfaces";
93
101
  export { ImageCleanupService } from "./core/images/imageCleanup.service";
94
102
  export { ImageFetchService, isBlockedAddress } from "./core/images/imageFetch.service";
95
103
  export { ImageProcessingService } from "./core/images/imageProcessing.service";
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Paginated = exports.parseFilter = exports.paginateWithFilter = exports.applySearch = exports.resolveCatalogOrder = exports.toFilterSchema = exports.FilterOptionResponse = exports.FilterFieldResponse = exports.FilterOperator = exports.FilterCombinator = exports.FilterFieldType = exports.FILTER_MAX_NODES = exports.FILTER_MAX_LENGTH = exports.FILTER_MAX_IN_ITEMS = exports.FILTER_MAX_DEPTH = exports.DEFAULT_OPERATORS = exports.applyOwnershipScope = exports.applyFilter = exports.SCOPE_TEAM = exports.SCOPE_OWN = exports.SCOPE_ANY = exports.anyOrTeam = exports.anyOrOwn = exports.resolveOwnershipScope = exports.PermissionsGuard = exports.JwtAuthGuard = exports.CsrfGuard = exports.AppThrottlerGuard = exports.RequirePermission = exports.RequireAnyPermission = exports.PERMISSIONS_KEY = exports.ANY_PERMISSIONS_KEY = exports.Public = exports.IS_PUBLIC_KEY = exports.CurrentUser = exports.setAuthCookies = exports.REFRESH_COOKIE = exports.clearAuthCookies = exports.ACCESS_COOKIE = exports.EnvService = exports.validateEnv = exports.envSchema = exports.ConfigModule = exports.RlCoreModule = exports.coreEntities = exports.coreMigrations = exports.createCoreDataSource = exports.CoreModule = exports.bootstrapWorker = exports.bootstrapCore = void 0;
4
4
  exports.EXPORT_CONCURRENCY = exports.COMPLETED_RETENTION_SECONDS = exports.toSafeError = exports.JobWorkerHost = exports.JobStatusResponse = exports.JobProgressResponse = exports.JobErrorResponse = exports.JobEventsService = exports.JOB_EVENTS_PUBLISHER = exports.JobProducer = exports.BullConnectionModule = exports.Auditable = exports.RequestOutcome = exports.ErrorType = exports.codedUnauthorized = exports.codedTooManyRequests = exports.codedBadRequest = exports.AppErrorCode = exports.AppEvent = exports.auditContext = exports.User = exports.PasswordHistory = exports.Scope = exports.Role = exports.Resource = exports.Permission = exports.Group = exports.Action = exports.NotificationsPublisher = exports.NotificationsModule = exports.NotificationScope = exports.NotificationRead = exports.Notification = exports.NotificationsService = exports.ScopeType = exports.NotificationType = exports.SCOPES = exports.ROLES = exports.RESOURCES = exports.PERMISSIONS = exports.ACTIONS = exports.AuthToken = exports.RequestLog = exports.AuditDataChange = exports.seedPermissions = exports.seedAdmin = exports.BaseEntity = exports.PaginationMetaResponse = exports.resolveOrder = exports.PaginationQuery = void 0;
5
- exports.ImagesController = exports.ImageProfileRegistry = exports.ImageUploadProfile = exports.ImageSourceKind = exports.ImageFormat = exports.ImageFit = exports.IMAGE_PROFILE = exports.EXTENSION_BY_FORMAT = exports.ImageProcessingService = exports.isBlockedAddress = exports.ImageFetchService = exports.ImageCleanupService = exports.RequestExportCommand = exports.ExportWorkerModule = exports.ExportStorageService = exports.ExportStatusResponse = exports.ExportHandlerRegistry = exports.ExportReport = exports.EXPORT_HANDLER = exports.ExportEngineService = exports.ExportColumnFormat = exports.ExportCleanupService = exports.ExportApiModule = exports.ExportAcceptedResponse = exports.ExportService = exports.ExportProcessor = exports.ExportModule = exports.ExportController = exports.RedisJobEventsPublisher = exports.jobEventsChannel = exports.RedisJobEventsBridge = exports.buildRedisOptions = exports.QueueModule = exports.queuePrefix = exports.QUEUE_NAMES = exports.JOBS_QUEUE = exports.JOB_PROGRESS_EVENT = exports.JOB_FAILED_EVENT = exports.JOB_CONCURRENCY = exports.JOB_COMPLETED_EVENT = exports.JOB_BACKOFF_DELAY_MS = exports.JOB_ATTEMPTS = exports.isRedisConfigured = exports.isQueueName = exports.IMAGES_QUEUE = exports.IMAGE_LOCK_DURATION_MS = exports.IMAGE_CONCURRENCY = exports.FAILED_RETENTION_SECONDS = exports.EXPORTS_QUEUE = exports.EXPORT_LOCK_DURATION_MS = void 0;
6
- exports.normalizeIp = exports.sha256 = exports.randomToken = exports.numericCode = exports.backupCode = exports.encryptSecret = exports.decryptSecret = exports.MailerService = exports.createAppLogger = exports.WS_USER_RESOLVER = exports.WsAuthService = exports.wrapSocketEvent = exports.SOCKET_EVENT_VERSION = exports.userRoom = exports.SESSION_REFRESH_EVENT = exports.roleRoom = exports.NOTIFICATIONS_NAMESPACE = exports.BaseGateway = exports.UploadImageFromUrlCommand = exports.ImageStatusResponse = exports.ImageProfileResponse = exports.ImageAcceptedResponse = exports.ImagesWorkerModule = exports.ImageStorageService = exports.ImagesApiModule = exports.ImagesService = exports.ImagesProcessor = exports.ImagesModule = void 0;
5
+ exports.isBlockedAddress = exports.ImageFetchService = exports.ImageCleanupService = exports.RequestExportCommand = exports.PdfDocumentService = exports.PDF_TABLE_LAYOUT = exports.PDF_STYLE = exports.ExportWorkerModule = exports.ExportStorageService = exports.ExportStatusResponse = exports.ExportKind = exports.ExportHandlerRegistry = exports.ExportReport = exports.EXPORT_HANDLER = exports.ExportExtension = exports.EXPORT_CONTENT_TYPES = exports.ExportEngineService = exports.ExportDocument = exports.EXPORT_DOCUMENT = exports.ExportColumnFormat = exports.ExportCleanupService = exports.ExportApiModule = exports.ExportAcceptedResponse = exports.ExportService = exports.ExportProcessor = exports.ExportModule = exports.ExportController = exports.EXPORT_PARAMS_MAX_LENGTH = exports.RedisJobEventsPublisher = exports.jobEventsChannel = exports.RedisJobEventsBridge = exports.buildRedisOptions = exports.QueueModule = exports.queuePrefix = exports.QUEUE_NAMES = exports.JOBS_QUEUE = exports.JOB_PROGRESS_EVENT = exports.JOB_FAILED_EVENT = exports.JOB_CONCURRENCY = exports.JOB_COMPLETED_EVENT = exports.JOB_BACKOFF_DELAY_MS = exports.JOB_ATTEMPTS = exports.isRedisConfigured = exports.isQueueName = exports.IMAGES_QUEUE = exports.IMAGE_LOCK_DURATION_MS = exports.IMAGE_CONCURRENCY = exports.FAILED_RETENTION_SECONDS = exports.EXPORTS_QUEUE = exports.EXPORT_LOCK_DURATION_MS = void 0;
6
+ exports.normalizeIp = exports.sha256 = exports.randomToken = exports.numericCode = exports.backupCode = exports.encryptSecret = exports.decryptSecret = exports.MailerService = exports.createAppLogger = exports.WS_USER_RESOLVER = exports.WsAuthService = exports.wrapSocketEvent = exports.SOCKET_EVENT_VERSION = exports.userRoom = exports.SESSION_REFRESH_EVENT = exports.roleRoom = exports.NOTIFICATIONS_NAMESPACE = exports.BaseGateway = exports.UploadImageFromUrlCommand = exports.ImageStatusResponse = exports.ImageProfileResponse = exports.ImageAcceptedResponse = exports.ImagesWorkerModule = exports.ImageStorageService = exports.ImagesApiModule = exports.ImagesService = exports.ImagesProcessor = exports.ImagesModule = exports.ImagesController = exports.ImageProfileRegistry = exports.ImageUploadProfile = exports.ImageSourceKind = exports.ImageFormat = exports.ImageFit = exports.IMAGE_PROFILE = exports.EXTENSION_BY_FORMAT = exports.ImageProcessingService = void 0;
7
7
  var bootstrapCore_util_1 = require("./core/bootstrap/bootstrapCore.util");
8
8
  Object.defineProperty(exports, "bootstrapCore", { enumerable: true, get: function () { return bootstrapCore_util_1.bootstrapCore; } });
9
9
  var bootstrapWorker_util_1 = require("./core/bootstrap/bootstrapWorker.util");
@@ -198,6 +198,8 @@ Object.defineProperty(exports, "RedisJobEventsBridge", { enumerable: true, get:
198
198
  var redisJobEvents_publisher_1 = require("./core/queue/redisJobEvents.publisher");
199
199
  Object.defineProperty(exports, "jobEventsChannel", { enumerable: true, get: function () { return redisJobEvents_publisher_1.jobEventsChannel; } });
200
200
  Object.defineProperty(exports, "RedisJobEventsPublisher", { enumerable: true, get: function () { return redisJobEvents_publisher_1.RedisJobEventsPublisher; } });
201
+ var export_constants_1 = require("./core/export/export.constants");
202
+ Object.defineProperty(exports, "EXPORT_PARAMS_MAX_LENGTH", { enumerable: true, get: function () { return export_constants_1.EXPORT_PARAMS_MAX_LENGTH; } });
201
203
  var export_controller_1 = require("./core/export/export.controller");
202
204
  Object.defineProperty(exports, "ExportController", { enumerable: true, get: function () { return export_controller_1.ExportController; } });
203
205
  var export_module_1 = require("./core/export/export.module");
@@ -214,19 +216,32 @@ var exportCleanup_service_1 = require("./core/export/exportCleanup.service");
214
216
  Object.defineProperty(exports, "ExportCleanupService", { enumerable: true, get: function () { return exportCleanup_service_1.ExportCleanupService; } });
215
217
  var exportColumn_interface_1 = require("./core/export/exportColumn.interface");
216
218
  Object.defineProperty(exports, "ExportColumnFormat", { enumerable: true, get: function () { return exportColumn_interface_1.ExportColumnFormat; } });
219
+ var exportDocument_interface_1 = require("./core/export/exportDocument.interface");
220
+ Object.defineProperty(exports, "EXPORT_DOCUMENT", { enumerable: true, get: function () { return exportDocument_interface_1.EXPORT_DOCUMENT; } });
221
+ Object.defineProperty(exports, "ExportDocument", { enumerable: true, get: function () { return exportDocument_interface_1.ExportDocument; } });
217
222
  var exportEngine_service_1 = require("./core/export/exportEngine.service");
218
223
  Object.defineProperty(exports, "ExportEngineService", { enumerable: true, get: function () { return exportEngine_service_1.ExportEngineService; } });
224
+ var exportExtension_enum_1 = require("./core/export/exportExtension.enum");
225
+ Object.defineProperty(exports, "EXPORT_CONTENT_TYPES", { enumerable: true, get: function () { return exportExtension_enum_1.EXPORT_CONTENT_TYPES; } });
226
+ Object.defineProperty(exports, "ExportExtension", { enumerable: true, get: function () { return exportExtension_enum_1.ExportExtension; } });
219
227
  var exportHandler_interface_1 = require("./core/export/exportHandler.interface");
220
228
  Object.defineProperty(exports, "EXPORT_HANDLER", { enumerable: true, get: function () { return exportHandler_interface_1.EXPORT_HANDLER; } });
221
229
  Object.defineProperty(exports, "ExportReport", { enumerable: true, get: function () { return exportHandler_interface_1.ExportReport; } });
222
230
  var exportHandler_registry_1 = require("./core/export/exportHandler.registry");
223
231
  Object.defineProperty(exports, "ExportHandlerRegistry", { enumerable: true, get: function () { return exportHandler_registry_1.ExportHandlerRegistry; } });
232
+ var exportKind_enum_1 = require("./core/export/exportKind.enum");
233
+ Object.defineProperty(exports, "ExportKind", { enumerable: true, get: function () { return exportKind_enum_1.ExportKind; } });
224
234
  var exportStatus_response_1 = require("./core/export/exportStatus.response");
225
235
  Object.defineProperty(exports, "ExportStatusResponse", { enumerable: true, get: function () { return exportStatus_response_1.ExportStatusResponse; } });
226
236
  var exportStorage_service_1 = require("./core/export/exportStorage.service");
227
237
  Object.defineProperty(exports, "ExportStorageService", { enumerable: true, get: function () { return exportStorage_service_1.ExportStorageService; } });
228
238
  var exportWorker_module_1 = require("./core/export/exportWorker.module");
229
239
  Object.defineProperty(exports, "ExportWorkerModule", { enumerable: true, get: function () { return exportWorker_module_1.ExportWorkerModule; } });
240
+ var pdf_constants_1 = require("./core/export/pdf/pdf.constants");
241
+ Object.defineProperty(exports, "PDF_STYLE", { enumerable: true, get: function () { return pdf_constants_1.PDF_STYLE; } });
242
+ Object.defineProperty(exports, "PDF_TABLE_LAYOUT", { enumerable: true, get: function () { return pdf_constants_1.PDF_TABLE_LAYOUT; } });
243
+ var pdfDocument_service_1 = require("./core/export/pdf/pdfDocument.service");
244
+ Object.defineProperty(exports, "PdfDocumentService", { enumerable: true, get: function () { return pdfDocument_service_1.PdfDocumentService; } });
230
245
  var requestExport_command_1 = require("./core/export/requestExport.command");
231
246
  Object.defineProperty(exports, "RequestExportCommand", { enumerable: true, get: function () { return requestExport_command_1.RequestExportCommand; } });
232
247
  var imageCleanup_service_1 = require("./core/images/imageCleanup.service");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rl-core-api",
3
- "version": "0.16.8",
3
+ "version": "0.17.1",
4
4
  "description": "Core NestJS: autenticação com 2FA, RBAC, auditoria, notificações e listagens com filtro dinâmico",
5
5
  "author": "Rodrigo Liberti",
6
6
  "license": "MIT",
@@ -78,6 +78,7 @@
78
78
  "@nestjs/schedule": "^6.1.3",
79
79
  "@nestjs/throttler": "^6.5.0",
80
80
  "@nestjs/websockets": "^11.2.1",
81
+ "@types/pdfmake": "^0.3.3",
81
82
  "argon2": "^0.45.1",
82
83
  "bullmq": "^5.81.4",
83
84
  "cookie-parser": "^1.4.7",
@@ -91,6 +92,7 @@
91
92
  "otplib": "^13.4.1",
92
93
  "passport": "^0.7.0",
93
94
  "passport-jwt": "^4.0.1",
95
+ "pdfmake": "^0.3.11",
94
96
  "qrcode": "^1.5.4",
95
97
  "sharp": "^0.35.3",
96
98
  "socket.io": "^4.8.3",