diff --git a/README.md b/README.md index 90eb9b9..033f7f0 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,162 @@ -# Payment Records API +# Payment Records API -API REST para gerenciamento de registros financeiros, contas bancárias e transações. +[![CI Pipeline](https://github.com/jaedson/payment_records/workflows/CI%20pipeline/badge.svg)](https://github.com/jaedson/payment_records/actions) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/) +[![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -## Tecnologias +API REST robusta e escalável para gerenciamento completo de registros financeiros, contas bancárias e transações, com autenticação segura e documentação interativa. -- Node.js 18+ -- TypeScript -- Express -- TypeORM -- PostgreSQL -- JWT (autenticação) -- Zod (validação de schemas) -- Class Validator (validação de DTOs) -- Nodemailer (envio de emails) +## Features -## Instalação +- **Autenticação JWT** com tokens de acesso e refresh +- **Gerenciamento de Usuários** com validação de ownership +- **Gestão de Bancos** com seeds pré-configurados +- **Contas Bancárias** com diferentes tipos (corrente, poupança, investimento) +- **Transações Financeiras** (receitas, despesas, transferências) +- **Notificações por Email** (boas-vindas, reset de senha) +- **Documentação Swagger/OpenAPI** em `/api` +- **Cobertura de Testes** (unitários e integração) +- **Docker & Docker Compose** para ambiente de desenvolvimento +- **Redis** para cache e sessões +- **CI/CD** com GitHub Actions -### Requisitos +## Tecnologias -- Node.js 18 ou superior -- PostgreSQL +### Core +- **Node.js 18+** - Runtime JavaScript +- **TypeScript 5.9** - Linguagem tipada +- **Express 5** - Framework web + +### Banco de Dados +- **PostgreSQL** - Banco de dados relacional +- **TypeORM 0.3** - ORM para TypeScript +- **Redis 5** - Cache e gerenciamento de sessões + +### Autenticação & Segurança +- **JWT** - Tokens de autenticação +- **bcryptjs** - Hash de senhas +- **Zod** - Validação de schemas +- **class-validator** - Validação de DTOs + +### Ferramentas +- **Swagger/OpenAPI** - Documentação interativa +- **Nodemailer** - Envio de emails +- **ESLint** - Linting de código +- **Jest** - Framework de testes +- **Docker** - Containerização + +## Pré-requisitos + +Antes de começar, certifique-se de ter instalado: + +- [Node.js](https://nodejs.org/) 18 ou superior +- [PostgreSQL](https://www.postgresql.org/) 14+ +- [Redis](https://redis.io/) 5+ +- [Docker](https://www.docker.com/) e Docker Compose (opcional, mas recomendado) - npm ou yarn -### Configuração +## Instalação e Configuração -1. Clone o repositório: +### 1. Clone o repositório ```bash -git clone +git clone https://github.com/seu-usuario/payment_records.git cd payment_records ``` -2. Instale as dependências: +### 2. Instale as dependências ```bash npm install ``` -3. Configure as variáveis de ambiente criando um arquivo `.env` na raiz do projeto: +### 3. Configure as variáveis de ambiente + +Crie um arquivo `.env` na raiz do projeto: ```env +# Servidor PORT=3000 -POSTGRES_HOST=localhost -POSTGRES_PORT=5432 -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_DB=payment_records -JWT_SECRET=your-secret-key -JWT_REFRESH_SECRET=your-refresh-secret-key -EMAIL_HOST=smtp.example.com -EMAIL_PORT=587 -EMAIL_USER=your-email@example.com -EMAIL_PASS=your-email-password +FRONTEND_URL=http://localhost:5173 + +# PostgreSQL +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=postgres +DB_DATABASE=payment_records + +# JWT +ACCESS_SECRET=seu-access-secret-aqui +ACCESS_EXPIRE=15m +REFRESH_SECRET=seu-refresh-secret-aqui +REFRESH_EXPIRE=7d +RESET_SECRET=seu-reset-secret-aqui +RESET_EXPIRE=15m + +# Email (SMTP) +SMTP_HOST=smtp.gmail.com +EMAIL_USER=seu-email@gmail.com +EMAIL_PASS=sua-senha-app + +# Redis +REDIS_URL=redis://localhost:6379 ``` -4. Inicie o banco de dados com Docker (opcional): +### 4. Inicie os serviços com Docker ```bash docker-compose up -d ``` -5. Execute as migrations do banco de dados: +Isso iniciará: +- PostgreSQL na porta `5432` +- Redis na porta `6379` -```bash -npm run migration:run -``` - -6. Inicie o servidor: +### 5. Execute o servidor ```bash +# Desenvolvimento (com hot-reload) +npm run dev + +# Produção +npm run build npm start ``` O servidor estará disponível em `http://localhost:3000`. -## Padrões de Request e Response +### 6. Acesse a documentação -### Padrão de Request +Abra seu navegador em: `http://localhost:3000/api` -Todas as requisições que requerem autenticação devem incluir o token JWT no cookie `access_token` ou no header `Authorization`: -```http -Authorization: Bearer + +### Padrões de Request e Response + +#### Request + +Requisições autenticadas devem incluir o token JWT: + +**Cookie:** +``` +access_token= ``` -Requisições com body devem enviar dados em formato JSON: +**Ou Header:** +```http +Authorization: Bearer +``` +**Body (JSON):** ```json { "campo": "valor" } ``` -### Padrão de Response +#### Response Todas as respostas seguem o padrão `DefaultMessage`: @@ -101,9 +165,10 @@ Todas as respostas seguem o padrão `DefaultMessage`: ```json { "success": true, - "message": "Mensagem descritiva da operação", + "message": "Operação realizada com sucesso", "data": { - "objeto": "dados retornados" + "id": "123", + "name": "Exemplo" } } ``` @@ -113,116 +178,203 @@ Todas as respostas seguem o padrão `DefaultMessage`: ```json { "success": false, - "message": "Mensagem de erro", + "message": "Mensagem de erro descritiva", "status": 400 } ``` -### Códigos HTTP - -- `200` - OK (operação bem-sucedida) -- `201` - Created (recurso criado com sucesso) -- `400` - Bad Request (dados inválidos) -- `401` - Unauthorized (não autenticado) -- `403` - Forbidden (sem permissão) -- `404` - Not Found (recurso não encontrado) -- `409` - Conflict (conflito, ex: email já existe) -- `500` - Internal Server Error (erro interno) - -## Endpoints da api -acesse `/api` +### Principais Endpoints + +#### Autenticação (`/auth`) +- `POST /auth/register` - Registrar novo usuário +- `POST /auth/login` - Login de usuário +- `POST /auth/refresh` - Renovar access token +- `POST /auth/logout` - Logout do usuário +- `POST /auth/forgot-password` - Solicitar reset de senha +- `POST /auth/reset-password` - Resetar senha + +#### Usuários (`/user`) +- `GET /user/me` - Obter dados do usuário autenticado +- `PATCH /user/me` - Atualizar dados do usuário +- `DELETE /user/me` - Deletar conta do usuário + +#### Bancos (`/bank`) +- `GET /bank` - Listar todos os bancos +- `GET /bank/:id` - Obter banco por ID +- `POST /bank` - Criar novo banco +- `PATCH /bank/:id` - Atualizar banco +- `DELETE /bank/:id` - Deletar banco + +#### Contas (`/account`) +- `GET /account` - Listar contas do usuário +- `GET /account/:id` - Obter conta por ID +- `POST /account` - Criar nova conta +- `PATCH /account/:id` - Atualizar conta +- `DELETE /account/:id` - Deletar conta + +#### Transações (`/transaction`) +- `GET /transaction` - Listar transações do usuário +- `GET /transaction/:id` - Obter transação por ID +- `POST /transaction` - Criar nova transação +- `PATCH /transaction/:id` - Atualizar transação +- `DELETE /transaction/:id` - Deletar transação + +#### Saúde +- `GET /health` - Status da API ## Estrutura do Projeto ``` -src/ -├── app.ts # Arquivo principal da aplicação -├── docs/ -│ ├── swagger.ts -├── lib/ -│ ├── enums.ts # Enumerações (tipos de conta, transação, etc) -│ ├── schema.ts # Schemas Zod para validação -│ ├── types.ts # Tipos TypeScript -│ └── utils.ts # Funções utilitárias -├── middlewares/ -│ ├── bodyparser.ts # Middleware de validação Zod -│ ├── error.ts # Middleware de tratamento de erros -│ └── jwt.ts # Middleware de validação JWT -├── modules/ -│ ├── Auth/ # Módulo de autenticação -│ │ ├── auth.controller.ts -│ │ ├── auth.service.ts -│ │ ├── auth.routes.ts -│ │ ├── auth.factory.ts -│ │ ├── dto/ -│ │ ├── email/ -│ │ └── repository/ -│ ├── User/ # Módulo de usuário -│ │ ├── user.controller.ts -│ │ ├── user.service.ts -│ │ ├── user.routes.ts -│ │ ├── user.factory.ts -│ │ ├── dto/ -│ │ ├── entity/ -│ │ └── repository/ -│ ├── Bank/ # Módulo de banco -│ │ ├── bank.controller.ts -│ │ ├── bank.service.ts -│ │ ├── bank.routes.ts -│ │ ├── bank.factory.ts -│ │ ├── dto/ -│ │ └── entity/ -│ ├── Account/ # Módulo de conta bancária -│ │ ├── account.controller.ts -│ │ ├── account.service.ts -│ │ ├── account.routes.ts -│ │ ├── account.factory.ts -│ │ ├── dto/ -│ │ └── entity/ -│ └── Transaction/ # Módulo de transação -│ ├── transaction.controller.ts -│ ├── transaction.service.ts -│ ├── transaction.routes.ts -│ ├── transaction.factory.ts -│ ├── dto/ -│ └── entity/ -└── shared/ - ├── db/ - │ └── data-source.ts # Configuração TypeORM - └── email.service.ts # Serviço de email +payment_records/ +├── .github/ +│ └── workflows/ +│ └── ci.yaml # Pipeline CI/CD +├── src/ +│ ├── app.ts # Ponto de entrada da aplicação +│ ├── core/ +│ │ ├── abstractions/ # Interfaces e abstrações +│ │ │ └── email.ts +│ │ └── interfaces/ +│ │ └── logger.ts +│ ├── docs/ +│ │ └── swagger.ts # Configuração Swagger/OpenAPI +│ ├── lib/ +│ │ ├── enums.ts # Enumerações (tipos de conta, transação) +│ │ ├── schema.ts # Schemas Zod para validação +│ │ ├── types.ts # Tipos TypeScript customizados +│ │ └── utils.ts # Funções utilitárias +│ ├── middlewares/ +│ │ ├── bodyparser.ts # Validação Zod de requests +│ │ ├── error.ts # Tratamento centralizado de erros +│ │ ├── jwt.ts # Validação e autenticação JWT +│ │ └── loggger.middleware.ts +│ ├── modules/ +│ │ ├── Account/ # Módulo de Contas Bancárias +│ │ │ ├── account.controller.ts +│ │ │ ├── account.service.ts +│ │ │ ├── account.routes.ts +│ │ │ ├── account.factory.ts +│ │ │ ├── dto/ # Data Transfer Objects +│ │ │ └── entity/ # Entidades TypeORM +│ │ ├── Auth/ # Módulo de Autenticação +│ │ │ ├── auth.controller.ts +│ │ │ ├── auth.service.ts +│ │ │ ├── auth.routes.ts +│ │ │ ├── auth.factory.ts +│ │ │ ├── dto/ +│ │ │ └── email/ # Templates de email +│ │ ├── Bank/ # Módulo de Bancos +│ │ ├── redis/ # Módulo Redis +│ │ ├── Transaction/ # Módulo de Transações +│ │ └── User/ # Módulo de Usuários +│ ├── shared/ +│ │ ├── db/ +│ │ │ └── data-source.ts # Configuração TypeORM +│ │ ├── seeds/ # Seeds do banco de dados +│ │ │ ├── bank.seed.ts +│ │ │ └── index.ts +│ │ ├── email.service.ts # Serviço de envio de emails +│ │ └── env.ts # Validação de variáveis de ambiente +│ └── tests/ +│ ├── integration/ # Testes de integração +│ │ ├── account.service.spec.ts +│ │ ├── auth.service.spec.ts +│ │ ├── bank.service.spec.ts +│ │ ├── user.service.spec.ts +│ │ ├── redis.service.spec.ts +│ │ └── postgres-db.ts +│ └── unit/ # Testes unitários +│ ├── middlewares.spec.ts +│ └── utils.spec.ts +├── coverage/ # Relatórios de cobertura de testes +├── docker-compose.yaml # Configuração Docker +├── init-db.sql # Script de inicialização do DB +├── jest.config.js # Configuração Jest +├── tsconfig.json # Configuração TypeScript +├── eslint.config.mts # Configuração ESLint +└── package.json ``` -## Padrões de Arquitetura +### Executar Testes -### Repository Pattern +```bash +# Todos os testes +npm test -Cada módulo possui um repository para acesso ao banco de dados. +# Apenas testes unitários +npm run test:unit -### Factory Pattern +# Apenas testes de integração +npm run test:integration -Controllers são criados através de factories que injetam as dependências necessárias. +# Testes com cobertura +npm run test:coverage +``` -### DTO Pattern +### Estrutura de Testes -Data Transfer Objects validados com class-validator para entrada de dados. +- **Testes Unitários** (`src/tests/unit/`): Testam funções isoladas (utils, middlewares) +- **Testes de Integração** (`src/tests/integration/`): Testam serviços com banco de dados real -### Middleware Chain +### Cobertura -Validação de schemas (Zod) → Autenticação (JWT) → Controller → Error Handler +Os relatórios de cobertura são gerados em `coverage/` e incluem: +- HTML report: `coverage/lcov-report/index.html` +- LCOV: `coverage/lcov.info` +- Clover: `coverage/clover.xml` ## Scripts Disponíveis -```bash -npm start # Inicia o servidor em modo desenvolvimento -npm run build # Compila o TypeScript -npm run migration:generate # Gera uma nova migration -npm run migration:run # Executa as migrations pendentes +| Script | Descrição | +|--------|-----------| +| `npm run dev` | Inicia o servidor em modo desenvolvimento com hot-reload | +| `npm run build` | Compila o TypeScript para JavaScript (dist/) | +| `npm start` | Inicia o servidor em modo produção | +| `npm run lint` | Executa o ESLint para análise de código | +| `npm run test:unit` | Executa testes unitários | +| `npm run test:integration` | Executa testes de integração | + +## CI/CD + +O projeto utiliza **GitHub Actions** para integração e entrega contínuas. + +### Pipeline Automático + +```yaml +Trigger: Push/Pull Request + ↓ + Build + ├── Checkout código + ├── Setup Node.js 18 + ├── Instalar dependências + ├── Lint código (ESLint) + └── Build TypeScript + ↓ + Tests + ├── Iniciar containers (PostgreSQL + Redis) + ├── Executar testes unitários + ├── Executar testes de integração + └── Gerar relatório de cobertura + ↓ ``` -## Segurança +### Arquivo de Configuração + +Veja [.github/workflows/ci.yaml](.github/workflows/ci.yaml) para detalhes. + +### Healthcheck + +Os containers possuem healthchecks configurados para garantir disponibilidade antes dos testes. + + +## Licença + +Este projeto está sob a licença **ISC**. Veja o arquivo [LICENSE](LICENSE) para mais detalhes. + +## 👨‍💻 Autor + +**Jaedson Macedo** +- Email: jaedsonnm@gmail.com +- GitHub: [@jaedson](https://github.com/jaedson) -- Senhas são criptografadas com bcrypt -- Autenticação via JWT com tokens de acesso e refresh -- Validação de ownership: usuários só podem acessar seus próprios recursos -- Validação de entrada com Zod e class-validator -- Proteção contra duplicatas em campos únicos +--- \ No newline at end of file diff --git a/src/docs/swagger.ts b/src/docs/swagger.ts index f328378..61252b3 100644 --- a/src/docs/swagger.ts +++ b/src/docs/swagger.ts @@ -4,13 +4,15 @@ import { transactionRegistry } from "@modules/Transaction/transaction.routes"; import { bankRegister } from "@modules/Bank/bank.routes"; import { authRegistry } from "@modules/Auth/auth.routes"; import { accountRegistry } from "@modules/Account/account.routes"; +import { redisRegistry } from "@modules/redis/redis.routes"; const register = new OpenAPIRegistry([ userRegistry, transactionRegistry, bankRegister, authRegistry, - accountRegistry + accountRegistry, + redisRegistry ]); const docs = new OpenApiGeneratorV3(register.definitions).generateDocument({ diff --git a/src/modules/redis/redis.routes.ts b/src/modules/redis/redis.routes.ts index c140b05..0f4d78a 100644 --- a/src/modules/redis/redis.routes.ts +++ b/src/modules/redis/redis.routes.ts @@ -3,8 +3,10 @@ import { RedisFactory } from "./redis.factory"; import { Request, Response } from "express"; import { validateToken } from "@middlewares/jwt"; import { Role as RoleEnum } from "@lib/enums"; +import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi"; export const redisRouter = Router(); +export const redisRegistry = new OpenAPIRegistry(); // Here i don't want to expose the store method // The store is a internal method that should be used by other services, not by external clients @@ -16,6 +18,36 @@ redisRouter.get("/", validateToken, async (req: Request, res: Response) => { const data = await RedisFactory.createController().getAll(); return res.status(200).json({data}); }) +redisRegistry.registerPath({ + method: "get", + path: "/redis", + summary: "Get all Redis keys and values", + tags: ["Redis"], + responses: { + 200: { + description: "Successful response with all Redis keys and values", + content: { + "application/json": { + schema: { + type: "object", + properties:{ + data: { + type: "array", + items: { + type: "object", + properties:{ + key: {type: "string"}, + value: {type: "string"} + } + } + } + } + } + } + } + } + } +}) redisRouter.get("/:key", validateToken, async (req: Request, res: Response) => { const {key} = req.params; @@ -25,4 +57,37 @@ redisRouter.get("/:key", validateToken, async (req: Request, res: Response) => { const data = await RedisFactory.createController().get(key); if(!data) return res.status(404).json({message: "Key not found"}); return res.status(200).json({data}); +}) +redisRegistry.registerPath({ + method: "get", + path: "/redis/:key", + summary: "Get a redis value by key", + tags: ["Redis"], + parameters: [ + { + name: "key", + in: "path", + required: true, + schema: { + type: "string" + }, + description: "The key of the redis value to retrieve" + } + ], + responses: { + 200: { + description: "Successful response with the redis value", + content: { + "application/json": { + schema: { + type: "object", + properties: { + key: {type: "string"}, + value: {type: "string"} + } + } + } + } + } + } }) \ No newline at end of file