Bem-vindo ao HappyIce, uma aplicação completa para gerenciar suas receitas favoritas, descobrir novas criações e organizar seus ingredientes. Este projeto é construído com uma arquitetura robusta e moderna, utilizando as melhores práticas de desenvolvimento.
HappyIce é uma plataforma que permite aos usuários:
- Registrar e fazer login de forma segura.
- Criar, visualizar, atualizar e excluir suas próprias receitas.
- Buscar receitas por nome ou por ingredientes.
- Marcar receitas como favoritas e gerenciar sua lista de favoritos.
A aplicação segue uma arquitetura de microsserviços (ou, mais precisamente, uma arquitetura em camadas bem definida para um monólito), dividida em três componentes principais:
- Backend (API): Responsável pela lógica de negócios, persistência de dados e exposição dos endpoints da API.
- Frontend (Web): A interface do usuário que consome a API do backend para exibir e interagir com os dados.
- Banco de Dados: Armazena todos os dados da aplicação.
A comunicação entre o frontend e o backend é feita via requisições HTTP. Todos os componentes são conteinerizados usando Docker para facilitar o desenvolvimento e a implantação.
A stack tecnológica foi escolhida para garantir performance, escalabilidade e uma ótima experiência de desenvolvimento:
- Backend:
- Node.js: Ambiente de execução JavaScript assíncrono e eficiente.
- Express.js: Framework web minimalista e flexível para Node.js, usado para construir a API RESTful.
- TypeScript: Superset do JavaScript que adiciona tipagem estática, melhorando a manutenibilidade e a detecção de erros.
- Jest: Framework de testes para JavaScript e TypeScript.
- Axios: Cliente HTTP para fazer requisições.
- Frontend:
- React: Biblioteca JavaScript para construção de interfaces de usuário interativas.
- Vite: Ferramenta de build rápida para projetos frontend.
- TypeScript: Para tipagem estática no frontend.
- React Router DOM: Para gerenciamento de rotas na aplicação SPA (Single Page Application).
- Axios: Cliente HTTP para fazer requisições à API.
- Banco de Dados:
- PostgreSQL: Sistema de gerenciamento de banco de dados relacional robusto e de código aberto.
- Conteinerização:
- Docker & Docker Compose: Para empacotar, distribuir e executar a aplicação em ambientes isolados.
O banco de dados PostgreSQL é composto pelas seguintes tabelas:
Armazena informações dos usuários da aplicação.
id(UUID, PK, DEFAULT gen_random_uuid()): Identificador único do usuário.email(VARCHAR(255), UNIQUE, NOT NULL): Endereço de e-mail do usuário (único).password(VARCHAR(255), NOT NULL): Senha criptografada do usuário.created_at(TIMESTAMP WITH TIME ZONE, DEFAULT CURRENT_TIMESTAMP): Data e hora de criação do registro.
Armazena os detalhes das receitas criadas pelos usuários.
id(UUID, PK, DEFAULT gen_random_uuid()): Identificador único da receita.user_id(UUID, NOT NULL, FK parausers.id): ID do usuário que criou a receita.name(VARCHAR(255), NOT NULL): Nome da receita.description(TEXT): Descrição detalhada da receita.steps(TEXT[]): Array de strings com os passos de preparo.created_at(TIMESTAMP WITH TIME ZONE, DEFAULT CURRENT_TIMESTAMP): Data e hora de criação do registro.
Armazena uma lista de ingredientes genéricos.
id(UUID, PK, DEFAULT gen_random_uuid()): Identificador único do ingrediente.name(VARCHAR(255), UNIQUE, NOT NULL): Nome do ingrediente (único).
Tabela de junção para relacionar receitas com seus ingredientes, incluindo detalhes específicos.
recipe_id(UUID, PK, FK pararecipes.id): ID da receita.ingredient_id(UUID, PK, FK paraingredients.id): ID do ingrediente.quantity(VARCHAR(100)): Quantidade do ingrediente (ex: "2", "1/2 xícara").unit(VARCHAR(100)): Unidade de medida (ex: "gramas", "ml", "unidades").display_order(INTEGER, NOT NULL): Ordem de exibição do ingrediente na receita.
Registra as receitas que um usuário marcou como favoritas.
user_id(UUID, PK, FK parausers.id): ID do usuário que favoritou.recipe_id(UUID, PK, FK pararecipes.id): ID da receita favoritada.created_at(TIMESTAMP WITH TIME ZONE, DEFAULT CURRENT_TIMESTAMP): Data e hora em que a receita foi favoritada.
A API é acessível através do serviço backend na porta 3000 (mapeada para 3000 no host). O prefixo base para todas as rotas é /api.
POST /api/users/register- Descrição: Registra um novo usuário.
- Corpo da Requisição:
{ email: string, password: string } - Respostas:
201 Created,400 Bad Request
POST /api/users/login- Descrição: Autentica um usuário e retorna um token JWT e os dados do usuário.
- Corpo da Requisição:
{ email: string, password: string } - Respostas:
200 OK(com{ token: string, user: { id: string, email: string } }),400 Bad Request
GET /api/recipes- Descrição: Lista todas as receitas. Acessível sem autenticação.
- Respostas:
200 OK(comRecipe[]),500 Internal Server Error
GET /api/recipes/{id}- Descrição: Obtém os detalhes de uma receita específica por ID. Acessível sem autenticação.
- Parâmetros de Rota:
id(string, UUID da receita) - Respostas:
200 OK(comRecipe),404 Not Found,500 Internal Server Error
POST /api/recipes- Descrição: Cria uma nova receita. Requer autenticação.
- Corpo da Requisição:
{ name: string, description: string, steps: string[], ingredients: RecipeIngredient[] } - Respostas:
201 Created,400 Bad Request,401 Unauthorized
PUT /api/recipes/{id}- Descrição: Atualiza uma receita existente por ID. Requer autenticação.
- Parâmetros de Rota:
id(string, UUID da receita) - Corpo da Requisição:
{ name: string, description: string, steps: string[], ingredients: RecipeIngredient[] } - Respostas:
200 OK,400 Bad Request,401 Unauthorized
DELETE /api/recipes/{id}- Descrição: Exclui uma receita por ID. Requer autenticação.
- Parâmetros de Rota:
id(string, UUID da receita) - Respostas:
200 OK,400 Bad Request,401 Unauthorized
GET /api/recipes/search/name?name={query}- Descrição: Busca receitas por nome. Acessível sem autenticação.
- Parâmetros de Query:
name(string, termo de busca) - Respostas:
200 OK(comRecipe[]),400 Bad Request(senameestiver ausente),500 Internal Server Error
GET /api/recipes/search/ingredient?ingredient={query}- Descrição: Busca receitas por ingrediente. Acessível sem autenticação.
- Parâmetros de Query:
ingredient(string, termo de busca) - Respostas:
200 OK(comRecipe[]),400 Bad Request(seingredientestiver ausente),500 Internal Server Error
GET /api/favorites- Descrição: Lista as receitas favoritas do usuário autenticado. Requer autenticação.
- Respostas:
200 OK(comRecipe[]),401 Unauthorized,500 Internal Server Error
POST /api/favorites- Descrição: Adiciona uma receita aos favoritos do usuário autenticado. Requer autenticação.
- Corpo da Requisição:
{ recipeId: string } - Respostas:
201 Created,400 Bad Request,401 Unauthorized
DELETE /api/favorites/{recipeId}- Descrição: Remove uma receita dos favoritos do usuário autenticado. Requer autenticação.
- Parâmetros de Rota:
recipeId(string, UUID da receita) - Respostas:
200 OK,400 Bad Request,401 Unauthorized
O frontend é uma Single Page Application (SPA) construída com React e TypeScript. Ele oferece uma interface intuitiva para interagir com a API do backend.
/(Home): Exibe a lista de todas as receitas, com opções de busca por nome ou ingrediente. Acessível sem autenticação./recipes: Também exibe a lista de todas as receitas. Acessível sem autenticação./recipes/new: Formulário para criar uma nova receita. Requer autenticação./recipes/edit/:id: Formulário para editar uma receita existente. Requer autenticação./recipes/:id: Detalhes de uma receita específica./favorites: Lista as receitas favoritas do usuário. Requer autenticação./login: Página de login./register: Página de registro de usuário.
Certifique-se de ter o Docker e o Docker Compose instalados em sua máquina.
-
Clone o Repositório:
git clone https://github.com/wfTom/happyice.git cd happyice -
Configurar Variáveis de Ambiente (Backend): Crie um arquivo
.envdentro da pastabackend/com as seguintes variáveis:DB_USER=user DB_PASSWORD=password DB_NAME=happyice DB_HOST=db DB_PORT=5432 JWT_SECRET=secret # Você pode adicionar outras variáveis de ambiente aqui -
Configurar Variáveis de Ambiente (Frontend): Crie um arquivo
.envdentro da pastafrontend/com a seguinte variável:VITE_API_URL=http://localhost:3000/api VITE_JWT_SECRET=secretNota: Se você estiver rodando em um ambiente diferente de
localhost, ajustehttp://localhost:3000/apipara a URL da sua API. -
Construir e Iniciar os Serviços: No diretório raiz do projeto (
happyice/), execute:docker-compose up --build
--build: Garante que as imagens Docker sejam construídas a partir dos Dockerfiles. Remova-o em execuções futuras se não houver mudanças nos Dockerfiles ou dependências.- Este comando irá:
- Criar a rede
happyice-net. - Iniciar o serviço
db(PostgreSQL). - Aguardar o banco de dados estar saudável.
- Iniciar o serviço
backend(API Node.js). - Iniciar o serviço
frontend(Aplicação React).
- Criar a rede
O banco de dados é populado automaticamente na primeira vez que os serviços Docker são iniciados, fornecendo dados de exemplo para facilitar o uso imediato da aplicação.
Você pode usar os seguintes usuários de exemplo para testar a aplicação:
-
Email:
user1@example.com -
Senha:
123456 -
Email:
user2@example.com -
Senha:
123456
Esses usuários já possuem algumas receitas e favoritos associados, permitindo que você explore as funcionalidades da aplicação sem a necessidade de criar novos dados manualmente.
- Acessar a Aplicação:
- O frontend estará acessível em
http://localhost:80(ou apenashttp://localhostse a porta 80 for a padrão). - A API do backend estará acessível em
http://localhost:3000/api.
- O frontend estará acessível em
Para executar os testes de unidade do backend:
- Certifique-se de que o serviço
backendesteja rodando (ou construa a imagem se ainda não o fez). - Execute o comando dentro do contêiner do backend:
Ou, se preferir rodar diretamente na sua máquina (assumindo Node.js e npm instalados e dependências instaladas na pasta
docker-compose exec backend npm test
backend):cd backend npm install # Se ainda não fez npm test
O arquivo backend/swagger.yaml descreve os endpoints da sua API. Para testar a API com base nesta especificação:
- Inicie a aplicação com Docker Compose (
docker-compose up). - Use uma ferramenta como Postman:
- Importe o arquivo
backend/swagger.yamlno Postman. - O Postman criará coleções de requisições que você pode usar para testar cada endpoint.
- Lembre-se de configurar a autenticação (Bearer Token) para os endpoints protegidos.
- Importe o arquivo
Para parar todos os serviços e remover os contêineres:
docker-compose downPara remover também os volumes (o que apagará os dados do banco de dados):
docker-compose down -vO banco de dados é inicializado e populado automaticamente na primeira vez que o serviço do Docker é iniciado. Esse processo é gerenciado pelos scripts SQL localizados em backend/src/infrastructure/database/sql/:`
10-init.sql: Este script é executado primeiro e é responsável por criar toda a estrutura de tabelas, chaves primárias, estrangeiras e outros constraints necessários para a aplicação.20-seed.sql: Após a criação da estrutura, este script é executado para popular o banco de dados com dados iniciais (seeds). Isso inclui, por exemplo, uma lista de ingredientes comuns e algumas receitas de exemplo, permitindo que a aplicação seja utilizada imediatamente após a instalação.
Se você precisar resetar o banco de dados, pode parar os contêineres com docker-compose down -v (o -v remove os volumes, incluindo os dados do banco) e iniciá-los novamente com docker-compose up.
Para gerar um relatório detalhado de cobertura de testes, que mostra quais partes do código foram testadas, execute:
docker-compose exec backend npm run test:coverageIsso criará uma pasta coverage/ no diretório backend/ com um relatório HTML interativo que pode ser aberto no navegador.
É crucial entender como a variável de ambiente VITE_API_URL no arquivo .env do frontend funciona:
- Para acesso via navegador (local): Use
VITE_API_URL=http://localhost:3000/api. O seu navegador acessa olocalhost, e o Docker redireciona a porta3000para o contêiner do backend. - Para comunicação interna do Docker: Se um serviço precisasse se comunicar com outro dentro da rede do Docker, ele usaria o nome do serviço (ex:
http://backend:3000/api). No nosso caso, como a requisição parte do navegador do usuário, usamos semprelocalhost.