chore/organize-project #1
|
|
@ -1,21 +1,14 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { typeOrmConfig } from './config/typeorm.config';
|
||||
import { ListPrinterController } from './controller/list-printer.controller';
|
||||
import { ListPrinterService } from './services/create-printer';
|
||||
import { PrinterController } from './controller/printer.controller';
|
||||
import { PrinterService } from './services/printer.service';
|
||||
import { PrinterProcessor } from './services/printer.processor';
|
||||
import { BullQueuesModule } from './config/BullModule';
|
||||
import { InfrastructureModule } from './infrastructure/infrastructure.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
TypeOrmModule.forRoot(typeOrmConfig),
|
||||
BullQueuesModule
|
||||
],
|
||||
controllers: [AppController, ListPrinterController],
|
||||
providers: [AppService, ListPrinterService, PrinterProcessor],
|
||||
imports: [InfrastructureModule],
|
||||
controllers: [AppController, PrinterController],
|
||||
providers: [AppService, PrinterService, PrinterProcessor],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,23 @@ import { ExpressAdapter } from '@bull-board/express';
|
|||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
|
||||
function stripWrappingQuotes(value: string | undefined): string | undefined {
|
||||
if (value == null) return undefined;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.replace(/^['"](.*)['"]$/, '$1');
|
||||
}
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
BullModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
connection: {
|
||||
host: configService.get<string>('REDIS_HOST'),
|
||||
port: configService.get<number>('REDIS_PORT') || 6379,
|
||||
password: configService.get<string>('REDIS_PASSWORD'),
|
||||
host: stripWrappingQuotes(configService.get<string>('REDIS_HOST')),
|
||||
port: parseInt(configService.get<string>('REDIS_PORT') ?? '6379', 10),
|
||||
password: stripWrappingQuotes(
|
||||
configService.get<string>('REDIS_PASSWORD'),
|
||||
),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
|
|
|
|||
|
|
@ -1,20 +1,52 @@
|
|||
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
import { DataSourceOptions } from 'typeorm';
|
||||
|
||||
export const typeOrmConfig: DataSourceOptions = {
|
||||
export function createTypeOrmOptions(
|
||||
configService: ConfigService,
|
||||
): TypeOrmModuleOptions {
|
||||
const baseOptions: DataSourceOptions = {
|
||||
type: 'oracle',
|
||||
username: process.env.DB_USERNAME || 'teste',
|
||||
password: process.env.DB_PASSWORD || 'teste',
|
||||
username: configService.get<string>('DB_USERNAME') || 'teste',
|
||||
password: configService.get<string>('DB_PASSWORD') || 'teste',
|
||||
connectString:
|
||||
process.env.DB_CONNECT_STRING ||
|
||||
configService.get<string>('DB_CONNECT_STRING') ||
|
||||
'(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=10.1.1.241)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=BDTESTE)))',
|
||||
synchronize: false,
|
||||
logging: true,
|
||||
entities: [__dirname + '/../**/*.{entity,view}.{js,ts}'],
|
||||
subscribers: [process.env.DB_SUBSCRIBERS_PATH || 'dist/subscriber/*.js'],
|
||||
subscribers: [
|
||||
configService.get<string>('DB_SUBSCRIBERS_PATH') ||
|
||||
'dist/subscriber/*.js',
|
||||
],
|
||||
extra: {
|
||||
poolSize: parseInt(process.env.DB_POOL_SIZE || '10', 10),
|
||||
maxRows: parseInt(process.env.DB_MAX_ROWS || '1000', 10),
|
||||
poolSize: parseInt(configService.get<string>('DB_POOL_SIZE') || '10', 10),
|
||||
maxRows: parseInt(configService.get<string>('DB_MAX_ROWS') || '1000', 10),
|
||||
},
|
||||
};
|
||||
|
||||
export const AppDataSource = new DataSource(typeOrmConfig);
|
||||
const retryAttempts = parseInt(
|
||||
configService.get<string>('DB_RETRY_ATTEMPTS') || '5',
|
||||
10,
|
||||
);
|
||||
const retryDelayMs = parseInt(
|
||||
configService.get<string>('DB_RETRY_DELAY_MS') || '2000',
|
||||
10,
|
||||
);
|
||||
|
||||
const logger = new Logger('TypeORM');
|
||||
|
||||
logger.debug(
|
||||
`TypeORM configured for manual initialization (retryAttempts=${retryAttempts}, retryDelayMs=${retryDelayMs}).`,
|
||||
);
|
||||
|
||||
return {
|
||||
...baseOptions,
|
||||
|
||||
manualInitialization: true,
|
||||
|
||||
retryAttempts: 0,
|
||||
retryDelay: 0,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import { Controller, Post, Body, Get, HttpCode, HttpStatus, Param } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Body,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { ListPrinterService } from '../services/create-printer';
|
||||
import { PrinterService } from '../services/printer.service';
|
||||
import { PrintDataDto } from '../dto/print-data.dto';
|
||||
import { PrintHtmlDto } from '../dto/print-html.dto';
|
||||
import { PrinterDto } from '../dto/printer.dto';
|
||||
|
|
@ -8,16 +16,18 @@ import { getPrinters } from '../services/get-printer';
|
|||
|
||||
@ApiTags('Printer')
|
||||
@Controller('printer')
|
||||
export class ListPrinterController {
|
||||
constructor(private readonly printerService: ListPrinterService) {}
|
||||
export class PrinterController {
|
||||
constructor(private readonly printerService: PrinterService) {}
|
||||
|
||||
@Post(':printerName/print')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Envia um comando de impressão de texto simples (ESC/POS)' })
|
||||
@ApiOperation({
|
||||
summary: 'Envia um comando de impressão de texto simples (ESC/POS)',
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'Impressão enviada com sucesso' })
|
||||
async print(
|
||||
@Param('printerName') printerName: string,
|
||||
@Body() printData: PrintDataDto
|
||||
@Body() printData: PrintDataDto,
|
||||
) {
|
||||
printData.printerInterface = printerName;
|
||||
return this.printerService.printReceipt(printData);
|
||||
|
|
@ -32,11 +42,14 @@ export class ListPrinterController {
|
|||
|
||||
@Post(':printerName/print-html')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Converte HTML para PDF e imprime na impressora especificada na URL' })
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Converte HTML para PDF e imprime na impressora especificada na URL',
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'HTML enviado para impressão' })
|
||||
async printHtml(
|
||||
@Param('printerName') printerName: string,
|
||||
@Body() printHtmlDto: PrintHtmlDto
|
||||
@Body() printHtmlDto: PrintHtmlDto,
|
||||
) {
|
||||
printHtmlDto.printerName = printerName;
|
||||
return this.printerService.printHtml(printHtmlDto);
|
||||
|
|
@ -54,7 +67,7 @@ export class ListPrinterController {
|
|||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Lista de impressoras encontradas',
|
||||
type: [PrinterDto]
|
||||
type: [PrinterDto],
|
||||
})
|
||||
async listPrinters() {
|
||||
return getPrinters();
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { DynamicModule, Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { createTypeOrmOptions } from '../config/typeorm.config';
|
||||
import { parseBooleanEnv } from '../helpers/env.helper';
|
||||
import { OracleDbInitializer } from './oracle-db.initializer';
|
||||
|
||||
@Module({})
|
||||
export class DatabaseModule {
|
||||
static register(): DynamicModule {
|
||||
const dbEnabled = parseBooleanEnv(process.env.DB_ENABLED, true);
|
||||
|
||||
if (!dbEnabled) {
|
||||
return { module: DatabaseModule };
|
||||
}
|
||||
|
||||
return {
|
||||
module: DatabaseModule,
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) =>
|
||||
createTypeOrmOptions(configService),
|
||||
}),
|
||||
],
|
||||
providers: [OracleDbInitializer],
|
||||
exports: [TypeOrmModule],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { parseBooleanEnv } from '../helpers/env.helper';
|
||||
|
||||
@Injectable()
|
||||
export class OracleDbInitializer implements OnApplicationBootstrap {
|
||||
private readonly logger = new Logger(OracleDbInitializer.name);
|
||||
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
onApplicationBootstrap(): void {
|
||||
const enabled = parseBooleanEnv(
|
||||
this.configService.get<string>('DB_ENABLED'),
|
||||
true,
|
||||
);
|
||||
if (!enabled) {
|
||||
this.logger.log('DB initialization skipped (DB_ENABLED=false).');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dataSource.isInitialized) return;
|
||||
|
||||
void this.initializeWithRetry();
|
||||
}
|
||||
|
||||
private async initializeWithRetry(): Promise<void> {
|
||||
const attempts = parseInt(
|
||||
this.configService.get<string>('DB_RETRY_ATTEMPTS') || '5',
|
||||
10,
|
||||
);
|
||||
const baseDelayMs = parseInt(
|
||||
this.configService.get<string>('DB_RETRY_DELAY_MS') || '2000',
|
||||
10,
|
||||
);
|
||||
|
||||
for (let attempt = 1; attempt <= attempts; attempt++) {
|
||||
try {
|
||||
await this.dataSource.initialize();
|
||||
this.logger.log(
|
||||
`Oracle DB connected (attempt ${attempt}/${attempts}).`,
|
||||
);
|
||||
return;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
this.logger.warn(
|
||||
`Oracle DB connection failed (attempt ${attempt}/${attempts}): ${message}`,
|
||||
);
|
||||
|
||||
if (attempt < attempts) {
|
||||
const delay = baseDelayMs * attempt;
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.error(
|
||||
`Oracle DB unreachable after ${attempts} attempts; continuing without DB (degraded mode).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export function parseBooleanEnv(
|
||||
value: string | undefined,
|
||||
defaultValue: boolean,
|
||||
): boolean {
|
||||
if (value == null) return defaultValue;
|
||||
|
||||
const normalized = value
|
||||
.trim()
|
||||
.replace(/^['"]|['"]$/g, '')
|
||||
.toLowerCase();
|
||||
|
||||
if (normalized === 'true') return true;
|
||||
if (normalized === 'false') return false;
|
||||
return defaultValue;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { Global, Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { BullQueuesModule } from '../config/BullModule';
|
||||
import { DatabaseModule } from '../database/database.module';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
DatabaseModule.register(),
|
||||
BullQueuesModule,
|
||||
],
|
||||
exports: [BullQueuesModule, DatabaseModule],
|
||||
})
|
||||
export class InfrastructureModule {}
|
||||
12
src/main.ts
12
src/main.ts
|
|
@ -1,11 +1,21 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { json, urlencoded } from 'express';
|
||||
import { AppModule } from './app.module';
|
||||
import { initializeOracleClient } from './helpers/oracle.helper';
|
||||
|
||||
function getBodyLimit(): string {
|
||||
const raw = process.env.BODY_LIMIT || '2mb';
|
||||
return raw.trim().replace(/^['"]|['"]$/g, '');
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
initializeOracleClient();
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create(AppModule, { bodyParser: false });
|
||||
|
||||
const bodyLimit = getBodyLimit();
|
||||
app.use(json({ limit: bodyLimit }));
|
||||
app.use(urlencoded({ extended: true, limit: bodyLimit }));
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('API Documentation')
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import { PrintHtmlDto } from '../dto/print-html.dto';
|
|||
import { ThermalPrinter, PrinterTypes } from 'node-thermal-printer';
|
||||
|
||||
@Injectable()
|
||||
export class ListPrinterService {
|
||||
export class PrinterService {
|
||||
private readonly printerType = PrinterTypes.EPSON;
|
||||
|
||||
constructor(@InjectQueue('printer') private printerQueue: Queue) {}
|
||||
|
||||
async printReceipt(data: PrintDataDto): Promise<{ success: boolean; message: string }> {
|
||||
async printReceipt(
|
||||
data: PrintDataDto,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
if (!data.lines?.length) {
|
||||
return { success: false, message: 'No lines provided' };
|
||||
}
|
||||
|
|
@ -19,7 +21,11 @@ export class ListPrinterService {
|
|||
try {
|
||||
let printerInterface = data.printerInterface;
|
||||
|
||||
if (printerInterface && !printerInterface.includes('://') && !printerInterface.startsWith('printer:')) {
|
||||
if (
|
||||
printerInterface &&
|
||||
!printerInterface.includes('://') &&
|
||||
!printerInterface.startsWith('printer:')
|
||||
) {
|
||||
printerInterface = `printer:${printerInterface}`;
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +55,7 @@ export class ListPrinterService {
|
|||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Print failed: ${error.message || error}`
|
||||
message: `Print failed: ${error.message || error}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -66,11 +72,15 @@ export class ListPrinterService {
|
|||
return printer.alignLeft();
|
||||
}
|
||||
|
||||
async printHtml(data: PrintHtmlDto): Promise<{ success: boolean; message: string; jobId?: string }> {
|
||||
async printHtml(
|
||||
data: PrintHtmlDto,
|
||||
): Promise<{ success: boolean; message: string; jobId?: string }> {
|
||||
const jobOptions: any = {};
|
||||
|
||||
if (data.jobId) {
|
||||
const safeJobId = /^\d+$/.test(data.jobId) ? `print-${data.jobId}` : data.jobId;
|
||||
const safeJobId = /^\d+$/.test(data.jobId)
|
||||
? `print-${data.jobId}`
|
||||
: data.jobId;
|
||||
jobOptions.jobId = safeJobId;
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +89,7 @@ export class ListPrinterService {
|
|||
return {
|
||||
success: true,
|
||||
message: 'Tarefa enviada para a fila de impressão',
|
||||
jobId: job.id
|
||||
jobId: job.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue