Skip to content

Edson-Mendes/finances-api

Repository files navigation

Finances API

Badge Concluído Badge Java Badge Spring Badge Maven Badge JUnit5 Badge Postgres Badge Heroku

📖 Resumo do projeto

Finances API é uma REST API para controle de orçamento familiar que visa auxiliar o usuário a gerenciar suas receitas e despesas de forma clara e segura.

A aplicação possui endpoints para gerenciar e manipular os recursos Usuário (User), Receitas (Income), Despesas (Expense), Autenticação (Auth), Resumos (Summary), Categorias (Category), que são protegidos e requerem autenticação por JWT (Json Web Token) para serem manipulados.

O projeto foi proposto pela Alura no Challenge Backend 2ª edição.

🧰 Tecnologias

🔨 Funcionalidades

🔒 API de gerenciamento de autenticação

  • Cadastrar usuário: Cadastro de usuários através de um POST /api/auth/signup com as informações name, email, password e confirm em um JSON no corpo da requisição.

    • O password é salvo criptografado no banco de dados usando BCryp.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "name": "John Doe",
      "email": "john.doe@email.com",
      "password": "1234567890",
      "confirmPassword": "1234567890"
    }

    Em caso de sucesso a resposta tem status 201 com um JSON no corpo da resposta contendo id, name, email, status e createdAt do usuário cadastrado.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "id": 150,
      "name": "John Doe",
      "email": "john.doe@email.com"
    }
  • Logar usuário: Sign in de usuários através de um POST /api/auth/signin com as informações de autenticação do usuário (email e senha) em um JSON no corpo da requisição.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "email": "john.doe@email.com",
      "password": "1234567890"
    }

    Em caso de sucesso a resposta tem status 200 com um JSON no corpo da resposta contendo token e type, onde token é um JWT que deve ser enviado em todas as requisições que requerem usuário autenticado, e type é o tipo do token, no caso desse sistema é o tipo Bearer.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNzE4MTk1OTQ5LCJleHAiOjE3MTgyODIzNDksImlzcyI6IkZpbmFuY2VzIEFQSSJ9.MEN5dWRe5XZ1F1j6A24loW9lzD3U_SvRMFlOG5TtLuM",
      "type": "Bearer"
    }

👤 API de gerenciamento de usuário

  • Buscar usuários: Busca paginada de usuários através de um GET /api/users, apenas usuário ADMIN tem permissão para usar essa funcionalidade.

    • Caso o cliente queira outra página da pesquisa deve passar a query param page, o valor padrão de page é 0.
    • Caso o cliente queira outro tamanho de página deve passar a query param size, o valor padrão de size é 10.
    • Os usuários vem ordenados por id em ordem crescente.

    Em caso de sucesso a resposta tem status 200 e um JSON no corpo da resposta com as informações paginadas dos usuários cadastrados.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "totalElements": 2,
      "totalPages": 1,
      "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        "sort": {
          "sorted": true,
          "empty": false,
          "unsorted": false
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
      },
      "size": 10,
      "content": [
        {
          "id": 1,
          "name": "John Doe",
          "email": "john@email.com"
        },
        {
          "id": 3,
          "name": "Jane Doe",
          "email": "jane@email.com"
        }
      ],
      "number": 0,
      "sort": {
        "sorted": true,
        "empty": false,
        "unsorted": false
      },
      "numberOfElements": 2,
      "first": true,
      "last": true,
      "empty": false
    }
  • Atualizar senha: Atualizar password através de um POST /api/users/password com as informações oldPassword, newPassword e confirm em um JSON no corpo da requisição.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "oldPassword": "1234567890",
      "newPassword": "0987654321",
      "confirm": "0987654321"
    }

    Em caso de sucesso a resposta tem status 204.

  • Deletar usuário: Deletar usuário através de um DELETE /api/users/{id}, apenas usuário ADMIN tem permissão para usar essa funcionalidade.

    Em caso de sucesso a resposta tem status 204.

📈 API de gerenciamento de receita

  • Cadastrar receita: Cadastrar receitas através de um POST /api/incomes com as informações description, date e value em um JSON no corpo da requisição.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "description": "Salário",
      "date": "2024-01-04",
      "value": 3240.59
    }

    Em caso de sucesso a resposta tem status 201 e um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "id": 1
      "description": "Salário",
      "date": "2024-01-04",
      "value": 3240.59
    }
  • Buscar receitas: Busca paginada de receitas através de um GET /api/incomes, opcional buscar por description.

    • Caso o cliente queira outra página deve passar a query param page, o valor padrão de page é 0.
    • Caso o cliente queira outro tamanho de página deve passar a query param size, o valor padrão de size é 10.
    • Caso o cliente queira receitas com uma dada description deve passar a query param description, esse parâmetro é opcional.
    • As receitas vem ordenados por date da mais recente para menos recente.

    Em caso de sucesso a resposta tem status 200 com um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta para a requisição GET /api/incomes.

    {
      "totalElements": 1,
      "totalPages": 1,
      "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        "sort": {
          "sorted": true,
          "empty": false,
          "unsorted": false
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
      },
      "size": 10,
      "content": [
        {
          "id": 2,
          "description": "Salário",
          "date": "2024-01-04",
          "value": 3240.59
        }
      ],
      "number": 0,
      "sort": {
        "sorted": true,
        "empty": false,
        "unsorted": false
      },
      "numberOfElements": 1,
      "first": true,
      "last": true,
      "empty": false
    }
  • Buscar receita por id: Busca receita por id através de um GET /api/incomes/{id}, onde id é o identificador da receita.

    • Usuário só tem permissão para buscar receitas que pertencem a si mesmo.

    Em caso de sucesso a resposta tem status 200 com um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta para a requisição GET /api/incomes/2

    {
      "id": 2,
      "description": "Salário",
      "date": "2024-01-04",
      "value": 3240.59
    }
  • Buscar receitas por ano e mês: Busca paginada de receitas por ano e mês, através de um GET /api/incomes{year}/{month}.

    • Caso o cliente queira outra página deve passar a query param page, o valor padrão de page é 0.
    • Caso o cliente queira outro tamanho de página deve passar a query param size, o valor padrão de size é 10.
    • As receitas vem ordenadas por date da mais recente para menos recente.

    Em caso de sucesso a resposta tem status 200 com um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta para a requisição GET /api/incomes/2024/1.

    {
      "totalElements": 1,
      "totalPages": 1,
      "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        "sort": {
          "sorted": true,
          "empty": false,
          "unsorted": false
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
      },
      "size": 10,
      "content": [
        {
          "id": 2,
          "description": "Salário",
          "date": "2024-01-04",
          "value": 3240.59
        }
      ],
      "number": 0,
      "sort": {
        "sorted": true,
        "empty": false,
        "unsorted": false
      },
      "numberOfElements": 1,
      "first": true,
      "last": true,
      "empty": false
    }
  • Atualizar receita: Atualizar receita por id através de um PUT /api/incomes/{id}, onde id é o identificador da receita, e enviando as novas informações da receita (description, date e value) em um JSON no corpo da requisição.

    • Usuário só tem permissão de atualizar receitas que pertencem a si mesmo.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "description": "Salário",
      "date": "2024-01-05",
      "value": 3800.00
    }

    Em caso de sucesso a resposta tem status 200, com um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "id": 2,
      "description": "Salário",
      "date": "2024-01-05",
      "value": 3800.00
    }
  • Deletar receita: Deletar receita por id através de um DELETE /api/incomes/{id}, onde id é o identificador da receita a ser deletada.

    • Usuário só tem permissão de deletar receitas que pertencem a si mesmo.

    Em caso de sucesso a resposta tem status 204.

📉 API de gerenciamento de despesas

  • Cadastrar despesa: Cadastrar despesas através de um POST /api/expenses com as informações description, date, value e category (opcional), em um JSON no corpo da requisição.

    • O campo category é opcional e caso não seja enviado, a despesa é salva com a category OUTRAS.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "description": "Mercado",
      "date": "2024-01-15",
      "value": 271.94,
      "category": "ALIMENTACAO"
    }

    Em caso de sucesso a resposta tem status 201, e um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "id": 5,
      "description": "Mercado",
      "date": "2024-01-15",
      "value": 271.94,
      "category": "ALIMENTACAO"
    }
  • Buscar despesas: Busca paginada de despesas através de um GET /api/expenses, opcional buscar por description.

    • Caso o cliente queira outra página deve passar a query param page, o valor padrão de page é 0.
    • Caso o cliente queira outro tamanho de página deve passar a query param size, o valor padrão de size é 10.
    • Caso o cliente queira despesas com uma dada description deve passar a query param description, esse parâmetro é opcional.
    • As despesas vem ordenadas por date da mais recente para menos recente.

    Em caso de sucesso a resposta tem status 200, e um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "totalElements": 1,
      "totalPages": 1,
      "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        "sort": {
          "sorted": true,
          "empty": false,
          "unsorted": false
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
      },
      "size": 10,
      "content": [
        {
          "id": 5,
          "description": "Mercado",
          "date": "2024-01-15",
          "value": 271.94,
          "category": "ALIMENTACAO"
        }
      ],
      "number": 0,
      "sort": {
        "sorted": true,
        "empty": false,
        "unsorted": false
      },
      "numberOfElements": 1,
      "first": true,
      "last": true,
      "empty": false
    }
  • Buscar despesa por id: Busca de despesa por id através de um GET para /api/expenses/{id}, onde id é o identificador da despesa.

    • Usuário só tem permissão para buscar despesas que pertencem a si mesmo.

    Em caso de sucesso a resposta tem status 200 com um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta para a requisição GET /api/expenses/5

    {
      "id": 5,
      "description": "Mercado",
      "date": "2024-01-15",
      "value": 271.94,
      "category": "ALIMENTACAO"
    }
  • Buscar despesas por ano e mês: Busca paginada de despesas por ano e mês através de um GET /api/expenses/{year}/{month}.

    • Caso o cliente queira outra página deve passar a query param page, o valor padrão de page é 0.
    • Caso o cliente queira outro tamanho de página deve passar a query param size, o valor padrão de size é 10.
    • As despesas vem ordenados por date da mais recente para menos recente.

    Em caso de sucesso a resposta tem status 200 com um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta para a requisição GET /api/expenses/2024/1.

    {
      "totalElements": 1,
      "totalPages": 1,
      "pageable": {
        "pageNumber": 0,
        "pageSize": 10,
        "sort": {
          "sorted": true,
          "empty": false,
          "unsorted": false
        },
        "offset": 0,
        "paged": true,
        "unpaged": false
      },
      "size": 10,
      "content": [
        {
          "id": 5,
          "description": "Mercado",
          "date": "2024-01-15",
          "value": 271.94,
          "category": "ALIMENTACAO"
        }
      ],
      "number": 0,
      "sort": {
        "sorted": true,
        "empty": false,
        "unsorted": false
      },
      "numberOfElements": 1,
      "first": true,
      "last": true,
      "empty": false
    }
  • Atualizar despesa: Atualizar despesa por id através de um PUT /api/expenses/{id}, onde id é o identificador da despesa e enviando as novas informações da despesa (description, date, value e category) em um JSON no corpo da requisição.

    • Usuário só tem permissão de atualizar despesas que pertencem a si mesmo.

    Segue abaixo um exemplo do corpo da requisição.

    {
      "description": "Mercado",
      "date": "2024-01-20",
      "value": 300.00,
      "category": "ALIMENTACAO"
    }

    Em caso de sucesso a resposta tem status 201, e um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "id": 5,
      "description": "Mercado",
      "date": "2024-01-20",
      "value": 300.00,
      "category": "ALIMENTACAO"
    }
  • Deletar despesa: Deletar despesa por id através de um DELETE /api/expenses/{id}, onde id é o identificador da despesa a ser deletada.

    • Usuário só tem permissão de deletar despesas que pertencem a si mesmo.

    Em caso de sucesso a resposta tem status 204.

🔣 API de gerenciamento de categorias

  • Buscar categorias: Buscar categorias através de um GET /api/categories.

    Em caso de sucesso a resposta tem status 200, e um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    [
      {
        "category": "ALIMENTACAO"
      },
      {
        "category": "SAUDE"
      },
      {
        "category": "MORADIA"
      },
      {
        "category": "TRANSPORTE"
      },
      {
        "category": "EDUCACAO"
      },
      {
        "category": "LAZER"
      },
      {
        "category": "IMPREVISTOS"
      },
      {
        "category": "OUTRAS"
      }
    ]

📊 API de gerenciamento de resumo

  • Buscar resumo do mês: Buscar resumo do mês através de um GET /api/summaries/{year}/{month}, o usuário tem acesso ao total de receitas e despesas de um dado ano e mês, assim como o saldo do mês e o total de despesas por categoria.

    Em caso de sucesso a resposta tem status 200 e um JSON no corpo da resposta.

    Segue abaixo um exemplo do corpo da resposta.

    {
      "incomeTotalValue": 3240.59,
      "expenseTotalValue": 271.94,
      "finalBalance": 2968.65,
      "valuesByCategory": [
        {
          "category": "ALIMENTACAO",
          "value": 271.94
        }
      ]
    }

⬜ Diagramas

Diagrama entidade relacionamento

---
    title: Database Schema
---
    erDiagram
        USER {
            bigserial id PK
            varchar(100) name
            varchar(150) email UK
            varchar(255) password
        }
        ROLE {
            serial id PK
            varchar(50) name UK
        }
        USER_ROLES {
            bigint user_id FK
            int roles_id FK
        }
        INCOME {
            bigserial id PK
            varchar(255) description
            numeric value
            date date
            bigint user_id FK
        }
        EXPENSE {
            bigserial id PK
            varchar(255) description
            numeric value
            varchar(25) category
            date date
            bigint user_id FK
        }
        
        USER }o--o{ USER_ROLES : has
        ROLE }o--o{ USER_ROLES : allows
        USER ||--o{ INCOME : has
        USER ||--o{ EXPENSE : has
Loading

🛠️ Deploy

O deploy da aplicação foi realizado no Heroku, você pode testar/brincar/usar aqui

OBS: As aplicações que usam conta gratuita do heroku adormecem se ficarem inativas, então pode ser que a primeira requisição demore um pouco (até uns 60 segundos), apenas seja paciente 😉.

🗃️ Documentação

Documentação feita com Springdoc openapi, gerando Swagger UI e Api docs

⚙️ Atualizações futuras

  • Configuração de CORS
  • Mais relatórios, tipo relatório anual, ou por categoria em dados mês e ano.
  • Adicionar algum recurso para monitorar e gerenciar a aplicação, tipo o Actuator
  • Endpoint para listar todas as categorias.
  • Remoção da regra de negócio que não permitia receitas/despesas com a mesmo DESCRIÇÃO no mesmo MÊS e ANO.
  • Atualizar versão do Spring boot (2.7.* ou 3.3.*)