aboutsummaryrefslogtreecommitdiff
path: root/api/src/server.ts
blob: 9ab75ebe90a7ca57ae1d26882b15a3a897067fe5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
 * server.ts
 *
 * Entry point do backend Helpdesk.
 *
 * Responsabilidades (em ordem):
 *   1. Executar migrações do banco no boot
 *   2. Criar e configurar a instância Fastify
 *   3. Registrar plugins globais
 *   4. Registrar módulos de rotas
 *   5. Iniciar o servidor HTTP
 */

import 'dotenv/config'; // Carrega .env antes de qualquer outro import
import Fastify from 'fastify';
import cors from '@fastify/cors';
import sensible from '@fastify/sensible';
import { validatorCompiler } from 'fastify-type-provider-zod';

import { runMigrations } from '@/db/migrate.js';
import dbPlugin from '@/plugins/db.plugin.js';
import authPlugin from '@/plugins/auth.plugin.js';
import authRoutes from '@/modules/auth/auth.routes.js';
import ticketsRoutes from '@/modules/tickets/tickets.routes.js';
import interactionsRoutes from '@/modules/interactions/interactions.routes.js';
import categoriesRoutes from '@/modules/categories/categories.routes.js';
import settingsRoutes from '@/modules/settings/settings.routes.js';
import statsRoutes from '@/modules/stats/stats.routes.js';
import usersRoutes from '@/modules/users/users.routes.js';
import eventsPlugin from '@/plugins/events.plugin.js';
import eventsRoutes from '@/plugins/events.routes.js';

// ─── Configuração ─────────────────────────────────────────────────────────────
const PORT = Number(process.env['PORT'] ?? 3000);
const HOST = process.env['HOST'] ?? '0.0.0.0';
const IS_DEV = process.env['NODE_ENV'] !== 'production';

// ─── Factory da aplicação ─────────────────────────────────────────────────────
// Exportar como função facilita testes de integração (cada teste cria uma instância)
export async function buildApp() {
  const fastify = Fastify({
    logger: {
      level: IS_DEV ? 'info' : 'warn',
      // Formatação legível no terminal em desenvolvimento
      transport: IS_DEV
        ? { target: 'pino-pretty', options: { colorize: true } }
        : undefined,
    },
  });

  // Configura o Fastify para usar validação com Zod
  fastify.setValidatorCompiler(validatorCompiler);

  // ── Plugins Globais ───────────────────────────────────────────────────────
  // CORS: permite requisições do frontend (Vite roda na porta 5173)
  await fastify.register(cors, {
    origin: IS_DEV ? 'http://localhost:5173' : process.env['FRONTEND_URL'],
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  });

  // Sensible: adiciona helpers como reply.notFound(), reply.badRequest(), etc.
  await fastify.register(sensible);

  // Banco de dados: disponibiliza fastify.db em todas as rotas
  await fastify.register(dbPlugin);

  // JWT: disponibiliza fastify.authenticate em todas as rotas
  await fastify.register(authPlugin);

  // ── Plugins Locais ────────────────────────────────────────────────────────
  await fastify.register(eventsPlugin);

  // ── Rotas ─────────────────────────────────────────────────────────────────
  // Auth: rotas públicas (sem authenticate)
  await fastify.register(authRoutes,         { prefix: '/api/v1/auth' });
  await fastify.register(statsRoutes,        { prefix: '/api/v1/stats' });
  await fastify.register(eventsRoutes,       { prefix: '/api/v1/events' });
  await fastify.register(categoriesRoutes,   { prefix: '/api/v1/categories' });
  await fastify.register(settingsRoutes,     { prefix: '/api/v1/settings' });
  await fastify.register(usersRoutes,        { prefix: '/api/v1/users' });
  await fastify.register(ticketsRoutes,      { prefix: '/api/v1/tickets' });
  await fastify.register(interactionsRoutes, { prefix: '/api/v1' });

  // Health check — útil para monitoramento e load balancer
  fastify.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));

  return fastify;
}

// ─── Bootstrap ───────────────────────────────────────────────────────────────
async function start(): Promise<void> {
  // 1. Aplica migrações pendentes antes de abrir o servidor para tráfego
  await runMigrations();

  // 2. Cria a instância configurada
  const app = await buildApp();

  // 3. Graceful shutdown — garante que requests em andamento terminem antes de fechar
  const gracefulShutdown = async (signal: string) => {
    app.log.info(`Sinal ${signal} recebido. Encerrando servidor...`);
    await app.close();
    process.exit(0);
  };

  process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Kubernetes / systemd
  process.on('SIGINT',  () => gracefulShutdown('SIGINT'));  // Ctrl+C no terminal

  // 4. Inicia o servidor
  await app.listen({ port: PORT, host: HOST });
}

start().catch((err) => {
  console.error('❌ Falha ao iniciar o servidor:', err);
  process.exit(1);
});