docs(swagger): clarify API contracts and response schemas

This commit is contained in:
Joelbrit0 2026-01-26 18:48:06 -03:00
parent 258b97eb15
commit 66adb39b9b
9 changed files with 185 additions and 68 deletions

View File

@ -18,21 +18,20 @@ Retorna a lista de impressoras instaladas no servidor onde a API está rodando.
```json ```json
[ [
{ {
"name": "POS-80", "Name": "POS-80",
"portName": "USB001", "DriverName": "Generic / Text Only",
"driverName": "Generic / Text Only", "PrinterStatus": 0
"printerStatus": 0,
"deviceId": "POS-80"
}, },
{ {
"name": "Microsoft Print to PDF", "Name": "Microsoft Print to PDF",
"portName": "PORTPROMPT:", "DriverName": "Microsoft Print To PDF",
"driverName": "Microsoft Print To PDF", "PrinterStatus": 0
"printerStatus": 0
} }
] ]
``` ```
> Nota: atualmente esse endpoint repassa o resultado do PowerShell `Get-Printer` (Windows) com chaves em PascalCase.
--- ---
### 2. Imprimir HTML (Etiquetas/Layouts) ### 2. Imprimir HTML (Etiquetas/Layouts)
@ -53,6 +52,16 @@ Converte um código HTML (com suporte a Tailwind CSS) para PDF e imprime na impr
} }
``` ```
- **Exemplo de Resposta:**
```json
{
"success": true,
"message": "Tarefa enviada para a fila de impressão",
"jobId": "123"
}
```
- **Detalhes:** - **Detalhes:**
- O backend injeta automaticamente o script do Tailwind CSS. - O backend injeta automaticamente o script do Tailwind CSS.
- O HTML é renderizado via Puppeteer (Chrome headless). - O HTML é renderizado via Puppeteer (Chrome headless).
@ -82,22 +91,29 @@ Envia comandos de texto diretamente para a impressora. Ideal para cupons simples
} }
``` ```
- **Exemplo de Resposta:**
```json
{
"success": true,
"message": "Print successful!"
}
```
## Tipos (TypeScript Interfaces) ## Tipos (TypeScript Interfaces)
Se estiver usando TypeScript no Frontend, utilize estas interfaces: Se estiver usando TypeScript no Frontend, utilize estas interfaces:
```typescript ```typescript
export interface Printer { export interface Printer {
name: string; Name: string;
portName?: string; DriverName?: string;
driverName?: string; PrinterStatus?: number;
printerStatus?: number;
deviceId?: string;
} }
export interface PrintHtmlPayload { export interface PrintHtmlPayload {
html: string; html: string;
printerName: string; printerName?: string;
width?: string; width?: string;
height?: string; height?: string;
jobId?: string; jobId?: string;
@ -109,6 +125,17 @@ export interface PrintTextPayload {
upsideDown?: boolean; upsideDown?: boolean;
printerInterface?: string; printerInterface?: string;
} }
export interface PrintHtmlResponse {
success: boolean;
message: string;
jobId?: string;
}
export interface PrintTextResponse {
success: boolean;
message: string;
}
``` ```
## Swagger ## Swagger

View File

@ -28,13 +28,18 @@ A solução utiliza uma arquitetura baseada em filas para assegurar a resiliênc
O serviço utiliza as seguintes definições no arquivo `.env`: O serviço utiliza as seguintes definições no arquivo `.env`:
| Variável | Descrição | Exemplo | | Variável | Descrição | Exemplo |
| :------------------- | :------------------------------- | :------------------------ | | :------------------- | :-------------------------------------------- | :------------------------ |
| `BODY_LIMIT` | Limite do body (JSON/urlencoded) | `2mb` |
| `REDIS_HOST` | Endereço do servidor Redis | `localhost` | | `REDIS_HOST` | Endereço do servidor Redis | `localhost` |
| `REDIS_PORT` | Porta do servidor Redis | `6379` | | `REDIS_PORT` | Porta do servidor Redis | `6379` |
| `REDIS_PASSWORD` | Senha do Redis (opcional) | `minha-senha` |
| `DB_ENABLED` | Habilita inicialização do Oracle/TypeORM | `true` |
| `DB_USERNAME` | Usuário do Banco de Dados Oracle | `admin` | | `DB_USERNAME` | Usuário do Banco de Dados Oracle | `admin` |
| `DB_PASSWORD` | Senha do Banco de Dados Oracle | `1234` | | `DB_PASSWORD` | Senha do Banco de Dados Oracle | `1234` |
| `DB_CONNECT_STRING` | String de conexão Oracle | `(DESCRIPTION=...)` | | `DB_CONNECT_STRING` | String de conexão Oracle | `(DESCRIPTION=...)` |
| `ORACLE_CLIENT_PATH` | Caminho do Oracle Instant Client | `C:\oracle\instantclient` | | `DB_RETRY_ATTEMPTS` | Tentativas de conexão ao iniciar | `5` |
| `DB_RETRY_DELAY_MS` | Delay base (ms) entre tentativas | `2000` |
| `ORACLE_CLIENT_PATH` | Caminho do Oracle Instant Client (thick mode) | `C:\oracle\instantclient` |
### Pré-requisitos ### Pré-requisitos
@ -102,9 +107,10 @@ A interface de monitoramento em tempo real das filas está disponível no endpoi
Permite a visualização de tarefas ativas, concluídas e falhas, além da reinjeção manual de jobs. Permite a visualização de tarefas ativas, concluídas e falhas, além da reinjeção manual de jobs.
### Telemetria ### Swagger
O serviço está instrumentado com **OpenTelemetry**, exportando traces via protocolo OTLP para análise de performance e rastreabilidade distribuída. A documentação interativa (OpenAPI) está disponível em:
`http://localhost:3000/api`
--- ---

View File

@ -7,12 +7,20 @@ import {
HttpStatus, HttpStatus,
Param, Param,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import {
ApiBody,
ApiOkResponse,
ApiOperation,
ApiParam,
ApiTags,
} from '@nestjs/swagger';
import { PrinterService } from '../services/printer.service'; import { PrinterService } from '../services/printer.service';
import { PrintDataDto } from '../dto/print-data.dto'; import { PrintDataDto } from '../dto/print-data.dto';
import { PrintHtmlDto } from '../dto/print-html.dto'; import { PrintHtmlDto } from '../dto/print-html.dto';
import { PrinterDto } from '../dto/printer.dto'; import { PrinterDto } from '../dto/printer.dto';
import { getPrinters } from '../services/get-printer'; import { getPrinters } from '../services/get-printer';
import { PrintResultDto } from '../dto/print-result.dto';
import { PrintHtmlResultDto } from '../dto/print-html-result.dto';
@ApiTags('Printer') @ApiTags('Printer')
@Controller('printer') @Controller('printer')
@ -24,7 +32,15 @@ export class PrinterController {
@ApiOperation({ @ApiOperation({
summary: 'Envia um comando de impressão de texto simples (ESC/POS)', summary: 'Envia um comando de impressão de texto simples (ESC/POS)',
}) })
@ApiResponse({ status: 200, description: 'Impressão enviada com sucesso' }) @ApiParam({
name: 'printerName',
example: 'POS-80',
description: 'Nome da impressora (será usado como printerInterface)',
})
@ApiOkResponse({
description: 'Impressão enviada com sucesso',
type: PrintResultDto,
})
async print( async print(
@Param('printerName') printerName: string, @Param('printerName') printerName: string,
@Body() printData: PrintDataDto, @Body() printData: PrintDataDto,
@ -36,6 +52,15 @@ export class PrinterController {
@Post('print') @Post('print')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Envia um comando de impressão (Compatibilidade)' }) @ApiOperation({ summary: 'Envia um comando de impressão (Compatibilidade)' })
@ApiBody({
type: PrintDataDto,
description:
'Compatibilidade: informe printerInterface no body ou use /printer/:printerName/print.',
})
@ApiOkResponse({
description: 'Impressão enviada com sucesso',
type: PrintResultDto,
})
async printLegacy(@Body() printData: PrintDataDto) { async printLegacy(@Body() printData: PrintDataDto) {
return this.printerService.printReceipt(printData); return this.printerService.printReceipt(printData);
} }
@ -46,7 +71,15 @@ export class PrinterController {
summary: summary:
'Converte HTML para PDF e imprime na impressora especificada na URL', 'Converte HTML para PDF e imprime na impressora especificada na URL',
}) })
@ApiResponse({ status: 200, description: 'HTML enviado para impressão' }) @ApiParam({
name: 'printerName',
example: 'POS-80',
description: 'Nome da impressora para o spooler (pdf-to-printer)',
})
@ApiOkResponse({
description: 'HTML enviado para impressão (job enfileirado)',
type: PrintHtmlResultDto,
})
async printHtml( async printHtml(
@Param('printerName') printerName: string, @Param('printerName') printerName: string,
@Body() printHtmlDto: PrintHtmlDto, @Body() printHtmlDto: PrintHtmlDto,
@ -58,14 +91,22 @@ export class PrinterController {
@Post('print-html') @Post('print-html')
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Converte HTML para PDF (Compatibilidade)' }) @ApiOperation({ summary: 'Converte HTML para PDF (Compatibilidade)' })
@ApiBody({
type: PrintHtmlDto,
description:
'Compatibilidade: informe printerName no body ou use /printer/:printerName/print-html.',
})
@ApiOkResponse({
description: 'HTML enviado para impressão (job enfileirado)',
type: PrintHtmlResultDto,
})
async printHtmlLegacy(@Body() printHtmlDto: PrintHtmlDto) { async printHtmlLegacy(@Body() printHtmlDto: PrintHtmlDto) {
return this.printerService.printHtml(printHtmlDto); return this.printerService.printHtml(printHtmlDto);
} }
@Get('list') @Get('list')
@ApiOperation({ summary: 'Lista impressoras disponíveis no sistema' }) @ApiOperation({ summary: 'Lista impressoras disponíveis no sistema' })
@ApiResponse({ @ApiOkResponse({
status: 200,
description: 'Lista de impressoras encontradas', description: 'Lista de impressoras encontradas',
type: [PrinterDto], type: [PrinterDto],
}) })

View File

@ -2,8 +2,12 @@ import { ApiProperty } from '@nestjs/swagger';
export class PrintDataDto { export class PrintDataDto {
@ApiProperty({ @ApiProperty({
example: ['--------------------------------', ' TESTE DE IMPRESSAO ', '--------------------------------'], example: [
description: 'Lista de linhas de texto para imprimir' '--------------------------------',
' TESTE DE IMPRESSAO ',
'--------------------------------',
],
description: 'Lista de linhas de texto para imprimir',
}) })
lines: string[]; lines: string[];
@ -11,21 +15,22 @@ export class PrintDataDto {
required: false, required: false,
enum: ['left', 'center', 'right'], enum: ['left', 'center', 'right'],
example: 'center', example: 'center',
description: 'Alinhamento do texto' description: 'Alinhamento do texto',
}) })
alignment?: 'left' | 'center' | 'right'; alignment?: 'left' | 'center' | 'right';
@ApiProperty({ @ApiProperty({
required: false, required: false,
example: false, example: false,
description: 'Se verdadeiro, imprime de cabeça para baixo (se suportado)' description: 'Se verdadeiro, imprime de cabeça para baixo (se suportado)',
}) })
upsideDown?: boolean; upsideDown?: boolean;
@ApiProperty({ @ApiProperty({
required: false, required: false,
example: 'tcp://10.1.119.13', example: 'tcp://10.1.119.13',
description: 'Interface da impressora. Ex: tcp://ip:porta ou printer:NomeDaImpressora' description:
'Interface da impressora. Obrigatorio ao usar /printer/print; ignorado ao usar /printer/:printerName/print. Ex: tcp://ip:porta ou printer:NomeDaImpressora',
}) })
printerInterface?: string; printerInterface?: string;
} }

View File

@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { PrintResultDto } from './print-result.dto';
export class PrintHtmlResultDto extends PrintResultDto {
@ApiProperty({
required: false,
example: '123',
description: 'ID do job enfileirado no BullMQ',
})
jobId?: string;
}

View File

@ -3,26 +3,36 @@ import { ApiProperty } from '@nestjs/swagger';
export class PrintHtmlDto { export class PrintHtmlDto {
@ApiProperty({ @ApiProperty({
example: '<h1>Titulo</h1><p>Conteudo do pedido...</p>', example: '<h1>Titulo</h1><p>Conteudo do pedido...</p>',
description: 'Conteúdo HTML para impressão' description: 'Conteúdo HTML para impressão',
}) })
html: string; html: string;
@ApiProperty({ @ApiProperty({
example: 'POS-80', example: 'POS-80',
description: 'Nome da impressora onde será impresso o PDF gerado' required: false,
description:
'Nome da impressora. Obrigatorio ao usar /printer/print-html; ignorado ao usar /printer/:printerName/print-html.',
}) })
printerName: string; printerName?: string;
@ApiProperty({ example: '60mm', required: false, description: 'Largura da etiqueta (default: 80mm)' }) @ApiProperty({
example: '60mm',
required: false,
description: 'Largura da etiqueta (default: 80mm)',
})
width?: string; width?: string;
@ApiProperty({ example: '40mm', required: false, description: 'Altura da etiqueta (default: auto)' }) @ApiProperty({
example: '40mm',
required: false,
description: 'Altura da etiqueta (default: auto)',
})
height?: string; height?: string;
@ApiProperty({ @ApiProperty({
example: 'impresso-12345', example: 'impresso-12345',
required: false, required: false,
description: 'ID único do job para idempotência (evita duplicados)' description: 'ID único do job para idempotência (evita duplicados)',
}) })
jobId?: string; jobId?: string;
} }

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class PrintResultDto {
@ApiProperty({ example: true })
success: boolean;
@ApiProperty({ example: 'Print successful!' })
message: string;
}

View File

@ -1,18 +1,24 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
export class PrinterDto { export class PrinterDto {
@ApiProperty({ example: 'POS-80', description: 'Nome da impressora' }) @ApiProperty({
name: string; example: 'POS-80',
description: 'Nome da impressora (retornado pelo PowerShell: Get-Printer)',
})
Name: string;
@ApiProperty({ example: 'USB001', description: 'Porta da impressora (Win32)' }) @ApiProperty({
portName?: string; example: 'Generic / Text Only',
required: false,
description: 'Nome do driver (retornado pelo PowerShell: Get-Printer)',
})
DriverName?: string;
@ApiProperty({ example: 'Generic / Text Only', description: 'Nome do driver' }) @ApiProperty({
driverName?: string; example: 0,
required: false,
@ApiProperty({ example: 0, description: 'Status da impressora (0 = Idle)' }) description:
printerStatus?: number; 'Status da impressora (retornado pelo PowerShell: Get-Printer)',
})
@ApiProperty({ example: 'POS-80', description: 'ID do dispositivo (usado para impressão)' }) PrinterStatus?: number;
deviceId?: string;
} }

View File

@ -18,9 +18,11 @@ async function bootstrap() {
app.use(urlencoded({ extended: true, limit: bodyLimit })); app.use(urlencoded({ extended: true, limit: bodyLimit }));
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('API Documentation') .setTitle('Servico de Impressao')
.setDescription('The API description') .setDescription(
.setVersion('1.0') 'Microservico NestJS para impressao local (ESC/POS e HTML->PDF), com processamento assincrono via BullMQ/Redis e opcional integracao Oracle via TypeORM.',
)
.setVersion('1.0.0')
.build(); .build();
const document = SwaggerModule.createDocument(app, config); const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document); SwaggerModule.setup('api', app, document);