# Arquitetura do Projeto — Gertel Skills Backend

## Visão Geral

Este projeto utiliza uma **Clean Architecture simplificada** (também chamada de **Arquitetura em Camadas com Repository Pattern e Use Cases**), inspirada nos princípios de Robert C. Martin (Uncle Bob) e adaptada para a realidade de APIs REST com Node.js/TypeScript.

A ideia central é **separar responsabilidades em camadas**, onde cada camada tem um papel claro e só depende das camadas mais internas — nunca das mais externas.

---

## Diagrama de Camadas

```
┌─────────────────────────────────────────────────────────┐
│                    HTTP (Fastify)                        │
│  controllers/ → recebem request, validam, respondem     │
│  middlewares/ → JWT, roles, logging                     │
│  routes.ts   → mapeiam URLs para controllers            │
├─────────────────────────────────────────────────────────┤
│                    USE CASES                            │
│  control-*.ts   → regras de negócio                     │
│  factories/     → montam use cases com dependências     │
├─────────────────────────────────────────────────────────┤
│                    REPOSITORY                           │
│  *-repository.ts       → interfaces (contratos)         │
│  prisma/*-repository.ts → implementações com Prisma     │
├─────────────────────────────────────────────────────────┤
│                    INFRAESTRUTURA                       │
│  lib/           → Prisma client, Mongoose, crypto       │
│  env/           → variáveis de ambiente                 │
│  error/         → tratamento centralizado de erros      │
│  schemas/       → schemas do Mongoose                   │
│  @types/        → extensões de tipos do TypeScript      │
└─────────────────────────────────────────────────────────┘
```

**Fluxo de uma requisição:**
```
Request HTTP
  → Middleware (JWT, Role)
    → Controller (valida input com Zod)
      → Factory (monta o Use Case com repositórios)
        → Use Case (aplica regras de negócio)
          → Repository Interface
            → Prisma Repository (acessa o banco)
              → Banco de Dados (MariaDB)
```

---

## Estrutura de Pastas e Propósito de Cada Arquivo

### `src/server.ts`
**O que é:** Ponto de entrada da aplicação.
**Por que existe:** Responsável por iniciar o servidor Fastify, conectar ao MongoDB e definir em qual host/porta a aplicação vai escutar. Separa a *configuração* do servidor (app.ts) da *execução* dele.

### `src/app.ts`
**O que é:** Configuração central do Fastify.
**Por que existe:** Registra todos os plugins (CORS, JWT, multipart, static files), hooks globais (sniffer de requisições), rotas e o error handler global. É o "esqueleto" da aplicação HTTP, sem iniciar o servidor — isso permite importar o `app` em testes sem levantar o servidor.

---

### `src/@types/` — Extensões de Tipos

| Arquivo | O que faz |
|---------|-----------|
| `fastify.d.ts` | Estende a interface `FastifyRequest` para incluir campos customizados como `logEntryUser`, `logEntryId`, `logEntryError`, `multipartData`. Permite que middlewares anexem dados ao request de forma tipada. |
| `fastify-jwt.d.ts` | Define o formato do payload JWT (`sub`, `role`, `type`), garantindo tipagem ao acessar `request.user`. |
| `serializes-error.d.ts` | Define a interface para serialização de erros. |

**Por que existem:** O TypeScript precisa saber que `request.logEntryUser` existe. Sem esses arquivos, todo acesso a campos customizados daria erro de compilação.

---

### `src/env/index.ts` — Configuração de Ambiente

**O que é:** Valida e exporta as variáveis de ambiente usando Zod.
**Por que existe:** Garante que a aplicação não inicie com variáveis faltando ou inválidas. Em vez de `process.env.JWT_SECRET` (que pode ser `undefined`), usa-se `env.JWT_SECRET` (sempre `string`). A validação acontece no boot — se algo estiver errado, a aplicação falha imediatamente com uma mensagem clara.

---

### `src/error/` — Tratamento de Erros

| Arquivo | O que faz |
|---------|-----------|
| `app-error.ts` | Define a classe `AppError` (erros de negócio com statusCode e mensagem segura para o cliente) e a função `sendError()` que é o error handler global do Fastify. |
| `handle-prisma-error.ts` | Mapeia códigos de erro do Prisma (P2002 = duplicado → 409, P2025 = não encontrado → 404, etc.) para respostas HTTP seguras. |
| `normalize.ts` | Normaliza qualquer tipo de erro (Error, string, objeto) para um formato consistente de logging. |

**Por que existem:** Centralizam o tratamento de erros em um único lugar. Os controllers e use cases apenas *lançam* erros (`throw new AppError(...)`) — nunca decidem como formatar a resposta de erro. Isso evita duplicação de try/catch e garante que erros internos (SQL, stack traces) nunca vazem para o cliente.

---

### `src/lib/` — Integrações Externas

| Arquivo | O que faz |
|---------|-----------|
| `prisma.ts` | Cria e exporta o singleton do PrismaClient com adapter MariaDB. Ativa logging de queries em ambiente dev. |
| `mongoose.ts` | Exporta a função `connectMongo()` para conexão com MongoDB. |
| `ati-crypto.ts` | Implementa a descriptografia AES-256-CBC do `id_ati` (identificador criptografado recebido do sistema ATI). |

**Por que existem:** Isolam a configuração de bibliotecas externas. Se amanhã o banco mudar de MariaDB para PostgreSQL, só este arquivo muda — nenhum controller ou use case precisa ser alterado.

---

### `src/schemas/` — Schemas do Mongoose

| Arquivo | O que faz |
|---------|-----------|
| `requests.ts` | Define o schema Mongoose para a collection `requests` no MongoDB, usada pelo sniffer para logging de requisições. |

**Por que existe:** O MongoDB (via Mongoose) é usado exclusivamente para auditoria de requisições. Este schema define a estrutura dos documentos de log.

---

### `src/http/middlewares/` — Middlewares

| Arquivo | O que faz |
|---------|-----------|
| `jwt.ts` | Verifica o token JWT, extrai o payload, busca o usuário/operador no banco via `ControlAccessUseCase` e popula `request.logEntryUser`. |
| `verifyRole.ts` | Verifica se o `role` do JWT está na lista de roles permitidas. Retorna 403 se não estiver. |
| `sniffer.ts` | Hook `onRequest`: registra a requisição no MongoDB. Hook `onSend`: atualiza o registro com a resposta, duração e eventuais erros. |

**Por que existem:** Implementam *cross-cutting concerns* — funcionalidades que atravessam todas as rotas (autenticação, autorização, logging) sem poluir os controllers.

---

### `src/http/controllers/` — Controllers (Camada HTTP)

Organizados por **domínio** (auth, users, categories, medias, requests, test). Cada domínio tem:

#### `routes.ts`
**O que faz:** Registra as rotas do domínio no Fastify, definindo método HTTP, path, middlewares e controller.
**Por que existe:** Centraliza o mapeamento URL → handler em um único arquivo por domínio. Facilita ver "quais endpoints existem para categorias?" abrindo um único arquivo.

#### Controllers individuais (`create-*.ts`, `list-*.ts`, `get-*.ts`, `update-*.ts`, `delete-*.ts`)

**O que fazem:**
1. Validam o input (body, params, query) com schemas Zod
2. Chamam a factory para obter o use case
3. Executam o método do use case
4. Retornam a resposta HTTP

**Por que existem:** São a "cola" entre o mundo HTTP e a lógica de negócio. Cada controller é uma função isolada com responsabilidade única — isso facilita localizar, testar e modificar endpoints individualmente.

**Por que um arquivo por ação (e não um arquivo por recurso)?** Evita arquivos grandes e monolíticos. Cada ação tem seu próprio schema de validação e pode ser encontrada/modificada sem afetar as demais.

**Domínios:**

| Domínio | Arquivos | Propósito |
|---------|----------|-----------|
| `auth/` | `user-ati.ts`, `me.ts`, `routes.ts` | Autenticação via ATI (criptografado), endpoint `/auth/me` |
| `users/` | CRUD completo (5 controllers + routes) | Gerenciamento de usuários (apenas operadores) |
| `categories/` | CRUD completo (5 controllers + routes) | Categorias de mídia |
| `medias/` | CRUD completo (5 controllers + routes) | Mídias (vídeos e PDFs) |
| `requests/` | `list-requests.ts`, `get-request.ts`, `routes.ts` | Consulta de logs de requisição (MongoDB) |
| `test/` | `routes.ts` | Endpoints de teste (apenas em dev/homologação) |

---

### `src/repository/` — Camada de Acesso a Dados

#### Interfaces (contratos)

| Arquivo | O que define |
|---------|-------------|
| `user-repository.ts` | Métodos que qualquer repositório de User deve implementar: `findById`, `findByLogin`, `findByIdSar`, `findMany`, `create`, `update`, `softDelete`. Também exporta os tipos `CreateUserData` e `UpdateUserData`. |
| `operator-repository.ts` | Contrato para Operator: `findById`, `findByIdAti`, `create`, `update`. |
| `media-category-repository.ts` | Contrato para MediaCategory: `findById`, `findMany`, `create`, `update`, `softDelete`. |
| `media-repository.ts` | Contrato para Media: `findById`, `findMany` (com filtros), `create`, `update`, `softDelete`, `countByCategory`. |

**Por que existem:** São o coração do **Dependency Inversion Principle (SOLID)**. Os use cases dependem dessas interfaces — não da implementação concreta do Prisma. Isso permite:
- Trocar o ORM sem alterar regras de negócio
- Criar implementações in-memory para testes unitários
- Documentar explicitamente quais operações de dados cada domínio precisa

#### Implementações Prisma (`prisma/`)

| Arquivo | O que faz |
|---------|-----------|
| `prisma-user-repository.ts` | Implementa `UserRepository` usando o Prisma Client. Traduz cada método da interface em queries Prisma. |
| `prisma-operator-repository.ts` | Implementa `OperatorRepository` com Prisma. |
| `prisma-media-category-repository.ts` | Implementa `MediaCategoryRepository` com Prisma. |
| `prisma-media-repository.ts` | Implementa `MediaRepository` com Prisma. Inclui `include: { media_category: true }` nas queries de leitura. |

**Por que existem:** Encapsulam todo o código específico do Prisma/banco. Nenhuma outra camada sabe que o banco é MariaDB ou que o ORM é Prisma. Filtros como `deleted_at: null` (soft delete) ficam aqui, não nos controllers.

---

### `src/use-cases/` — Regras de Negócio

| Arquivo | O que faz |
|---------|-----------|
| `control-user.ts` | Classe `ControlUserUseCase` — regras de negócio para usuários: verificar login duplicado antes de criar, validar existência antes de atualizar/deletar. |
| `control-access.ts` | Classe `ControlAccessUseCase` — lógica de autenticação: encontrar ou criar usuário/operador a partir do `id_ati`, buscar por ID para validação de JWT. |
| `control-media-category.ts` | Classe `ControlMediaCategoryUseCase` — regras para categorias: impedir exclusão de categoria com mídias vinculadas. |
| `control-media.ts` | Classe `ControlMediaUseCase` — regras para mídias: validar existência antes de atualizar/deletar. |

**Por que existem:** São o lugar onde mora a lógica que *não é HTTP* e *não é banco de dados*. Exemplos:
- "Não pode criar usuário com login duplicado" → regra de negócio, não validação de input
- "Não pode deletar categoria que tem mídias" → regra que envolve consulta a outro recurso
- "Se o usuário não existe pelo id_ati, crie; se existe, atualize" → lógica de upsert

**Padrão de construção:** Cada use case recebe seus repositórios via **constructor injection**:
```typescript
class ControlMediaCategoryUseCase {
    constructor(
        private mediaCategoryRepository: MediaCategoryRepository,
        private mediaRepository: MediaRepository,
    ) { }
}
```

#### `factories/` — Fábricas de Use Cases

| Arquivo | O que faz |
|---------|-----------|
| `make-control-user.ts` | Instancia `PrismaUserRepository` e injeta no `ControlUserUseCase`. |
| `make-control-access.ts` | Instancia `PrismaUserRepository` + `PrismaOperatorRepository` e injeta no `ControlAccessUseCase`. |
| `make-control-media-category.ts` | Instancia `PrismaMediaCategoryRepository` + `PrismaMediaRepository` e injeta no `ControlMediaCategoryUseCase`. |
| `make-control-media.ts` | Instancia `PrismaMediaRepository` e injeta no `ControlMediaUseCase`. |

**Por que existem:** São o ponto de **composição** — onde as dependências concretas são decididas. Os controllers chamam `makeControlUserUseCase()` e recebem um use case pronto, sem saber quais repositórios foram usados. Isso substitui um container de DI (como tsyringe ou inversify) por algo mais simples e explícito.

---

## Por Que Esta Arquitetura?

### Vantagens

| Vantagem | Explicação |
|----------|------------|
| **Testabilidade** | Use cases podem ser testados com repositórios in-memory, sem banco de dados real. Basta criar classes que implementam as mesmas interfaces. |
| **Separação de responsabilidades** | Cada arquivo tem uma única razão para mudar. Uma mudança no schema do banco afeta apenas o repositório Prisma. Uma nova regra de negócio afeta apenas o use case. |
| **Troca de tecnologia** | Trocar Prisma por TypeORM, MariaDB por PostgreSQL, ou Fastify por Express — cada mudança fica isolada em sua camada. |
| **Facilidade de localização** | Quer saber como uma mídia é criada no banco? → `prisma-media-repository.ts`. Quer saber a regra antes de deletar uma categoria? → `control-media-category.ts`. Quer saber o que a rota POST /medias faz? → `create-media.ts`. |
| **Consistência entre domínios** | Todos os domínios (users, medias, categories) seguem exatamente o mesmo padrão. Um desenvolvedor que entende um domínio entende todos. |
| **Erros centralizados** | O error handler global captura `AppError`, `ZodError` e erros Prisma automaticamente. Nenhum controller precisa se preocupar com formatação de erro. |
| **Segurança** | Erros internos (SQL, stack traces) nunca chegam ao cliente. O `sendError()` sempre retorna mensagens seguras e genéricas para erros não mapeados. |
| **Soft delete uniforme** | A lógica de `deleted_at` fica nos repositórios. Nenhuma outra camada precisa lembrar de filtrar registros deletados. |

### Desvantagens

| Desvantagem | Explicação |
|-------------|------------|
| **Mais arquivos** | Um CRUD simples exige ~12 arquivos (5 controllers + routes + interface + implementação + use case + factory + tipos). Para projetos pequenos ou protótipos, isso pode parecer excesso de engenharia. |
| **Indireção** | Para entender o fluxo completo de uma requisição, é preciso navegar por 4-5 arquivos (controller → factory → use case → repository → prisma). Isso aumenta a curva de aprendizado inicial. |
| **Boilerplate repetitivo** | Muitos use cases fazem basicamente "verificar se existe → chamar repositório". Para CRUDs simples, a camada de use case pode parecer desnecessária. |
| **Sem container de DI** | As factories são manuais. Se um use case precisar de 5 repositórios, a factory fica verbosa. Frameworks como tsyringe resolveriam isso automaticamente, mas adicionariam complexidade. |
| **Performance marginal** | Cada requisição instancia novos objetos (repositórios e use cases) via factory. O impacto é negligível em Node.js, mas um padrão singleton seria tecnicamente mais eficiente. |
| **Duplicação de tipos** | Os tipos de input/output existem tanto nas interfaces dos repositórios quanto nos schemas Zod dos controllers. Uma mudança em um campo exige atualização em ambos os lugares. |

---

## Quando Esta Arquitetura Faz Sentido

**Ideal para:**
- APIs com regras de negócio não-triviais
- Projetos com múltiplos desenvolvedores
- Sistemas que precisam de testes automatizados
- Projetos de longa duração que vão crescer

**Pode ser excessiva para:**
- Protótipos e MVPs descartáveis
- APIs puramente CRUD sem regras de negócio
- Projetos de um único desenvolvedor com prazo muito curto

---

## Referência Rápida: Onde Fica Cada Coisa

| Preciso... | Vou em... |
|------------|-----------|
| Adicionar um novo endpoint | `src/http/controllers/{dominio}/` + `routes.ts` |
| Mudar uma regra de negócio | `src/use-cases/control-*.ts` |
| Mudar como os dados são salvos | `src/repository/prisma/prisma-*-repository.ts` |
| Adicionar um novo campo ao banco | `prisma/schema.prisma` + interface do repository + implementação Prisma |
| Adicionar validação no input | Schema Zod no controller |
| Adicionar um novo domínio | Criar: interface repo → impl Prisma → use case → factory → controllers → routes → registrar em app.ts |
| Adicionar middleware global | `src/http/middlewares/` + registrar em `app.ts` |
| Mudar tratamento de erros | `src/error/` |
| Mudar config do Fastify/plugins | `src/app.ts` |
| Mudar variáveis de ambiente | `src/env/index.ts` + `.env` |
