O objetivo deste projeto foi simular um Blog, onde desenvolvi uma API e um banco de dados em Node.js, utilizando sequelize para fazer um CRUD, onde é possível criar usuários, fazer login, criar categorias de posts, criar posts, atualizá-los, deletá-los, buscá-los por id e por texto em seu título ou em seu conteúdo;
Para Clonar e testar a aplicação
Git
Thunder Client
MySQL
Node v16.13.0
- Clone o repositório
git clone [email protected]:georgia-rocha/Blogs-API.git
- Entre na pasta do repositório que você acabou de clonar:
cd Blogs-API
! É necessário ter um arquivo .env na raiz da aplicação, com o conteúdo:
NODE_ENV=development
API_PORT=3001
API_HOST=localhost
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DB_NAME=blogs-api
MYSQL_USER=root
MYSQL_PASSWORD=password
JWT_SECRET=suaSenhaSecreta
🐳 Rodando Projeto no Docker vs Localmente
Rode o serviço
node
com o comandodocker-compose up -d
.
- Esse serviço irá inicializar um container chamado
Blogs-API
. - A partir daqui você pode rodar o container via CLI ou abri-lo no VS Code.
Use o comando
docker exec -it Blogs-API bash
.
- Ele te dará acesso ao terminal interativo do container criado pelo compose, que está rodando em segundo plano.
Instale as dependências [Caso existam] com
npm install
Execute a aplicação com
npm start
ounpm run dev
Instale as dependências [Caso existam] com
npm install
- Para rodar a aplicação:
npm start
Em ambiente de desenvolvimento:
npm run dev
- Para testar a aplicação: Testar todas:
npm test
Testar individuamente:
- Colocar o número do requisito a ser testado;
npm test **01**
DESCREVENDO APLICAÇÃO
1 - Cria migrations para as tabelas users, categories, blog_posts, posts_categories
- Foram criadas as migrations para as tabelas respeitando a nomenclatura pedida no requisito e o diagrama de Entidade-Relacionamento e o formato das entidades;
- O teste fez uma conexão no banco de dados usando a configuração de teste do arquivo src/config/config.js, e foi posível validar que:
- É possível fazer um INSERT e um SELECT na tabela users;
- É possível fazer um INSERT e um SELECT na tabela categories;
- A partir de um INSERT em users, é possível fazer um INSERT e um SELECT na tabela blog_posts;
- A partir de INSERTs em users, categories e blog_posts, é possível fazer um INSERT e um SELECT na tabela posts_categories;
2 - Cria model de User
- É validado que existe o arquivo User.js;
- É validado que o modelo possui o nome User;
- É validado que o modelo possui a propriedade id;
- É validado que o modelo possui a propriedade display_name;
- É validado que o modelo possui a propriedade email;
- É validado que o modelo possui a propriedade password;
- É validado que o modelo possui a propriedade image;
3 - POST /login
- O endpoint é acessível pela URL /login;
- A requisição é feita no formato a seguir:
[
{
"email": "[email protected]",
"password": "123456"
}
]
-
Foi validado que não é possível fazer login sem todos os campos preenchidos:
- Se a requisição não tiver todos os campos devidamente preenchidos(não pode haver campos em branco), o resultado retornado deverá ser conforme exibido abaixo, com um status HTTP
400
:
{ "message": "Some required fields are missing" }
- Se a requisição não tiver todos os campos devidamente preenchidos(não pode haver campos em branco), o resultado retornado deverá ser conforme exibido abaixo, com um status HTTP
-
Foi validado que não é possível fazer login com um usuário que não existe:
- Se a requisição receber um par de
email
epassword
errados/inexistentes, o resultado retornado deverá ser conforme exibido abaixo, com um status HTTP400
:
{ "message": "Invalid fields" }
- Se a requisição receber um par de
-
É validado que é possível fazer login com sucesso:
- Se o login foi feito com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkIjp7ImlkIjo1LCJkaXNwbGF5TmFtZSI6InVzdWFyaW8gZGUgdGVzdGUiLCJlbWFpbCI6InRlc3RlQGVtYWlsLmNvbSIsImltYWdlIjoibnVsbCJ9LCJpYXQiOjE2MjAyNDQxODcsImV4cCI6MTYyMDY3NjE4N30.Roc4byj6mYakYqd9LTCozU1hd9k_Vw5IWKGL4hcCVG8" }
- Se o login foi feito com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
4 - POST /user
- O endpoint é acessível pela URL /user , onde é possível adicionar um novo usuário na tabela no banco de dados;
- A requisição é feita no formato a seguir:
{
"displayName": "Brett Wiltshire",
"email": "[email protected]",
"password": "123456",
"image": "http://4.bp.blogspot.com/_YA50adQ-7vQ/S1gfR_6ufpI/AAAAAAAAAAk/1ErJGgRWZDg/S45/brett.png"
// a imagem não é obrigatória
}
-
É validado que não é possível cadastrar com o campo
displayName
menor que 8 caracteres;- Se a requisição não tiver o campo
displayName
devidamente preenchido com 8 caracteres ou mais, o resultado retornado é conforme exibido abaixo, com um status HTTP400
:
{ "message": "\"displayName\" length must be at least 8 characters long" }
- Se a requisição não tiver o campo
-
É validado que não é possível cadastrar com o campo
email
com formato inválido;- Se a requisição não tiver o campo
email
devidamente preenchido com o formato<prefixo@dominio>
, o resultado retornado é conforme exibido abaixo, com um status HTTP400
:
{ "message": "\"email\" must be a valid email" }
- Se a requisição não tiver o campo
-
É validado que não é possível cadastrar com o campo
password
menor que 6 caracteres;- Se a requisição não tiver o campo
password
devidamente preenchido com 6 caracteres ou mais, o resultado retornado é conforme exibido abaixo, com um status HTTP400
:
{ "message": "\"password\" length must be at least 6 characters long" }
- Se a requisição não tiver o campo
-
É validado que não é possível cadastrar com um email já existente;
- Se a requisição enviar o campo
email
com um email que já existe, o resultado retornado é conforme exibido abaixo, com um status HTTP409
:
{ "message": "User already registered" }
- Se a requisição enviar o campo
-
É validado que é possível cadastrar um pessoa usuária com sucesso;
- Se o user for criado com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
201
:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXlsb2FkIjp7ImlkIjo1LCJkaXNwbGF5TmFtZSI6InVzdWFyaW8gZGUgdGVzdGUiLCJlbWFpbCI6InRlc3RlQGVtYWlsLmNvbSIsImltYWdlIjoibnVsbCJ9LCJpYXQiOjE2MjAyNDQxODcsImV4cCI6MTYyMDY3NjE4N30.Roc4byj6mYakYqd9LTCozU1hd9k_Vw5IWKGL4hcCVG8" }
- Se o user for criado com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
5 - GET /user/
- O endpoint é acessível pela URL /user , onde é possível buscar todos os usuários na tabela no banco de dados;
- É validado que é possível listar todos os usuários;
- Ao listar usuários com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP 200:
[
{
"id": 1,
"displayName": "Lewis Hamilton",
"email": "[email protected]",
"image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
},
/* ... */
]
6 - GET /user/:id
- O endpoint é acessível através do URL /user/:id;
- O endpoint é capaz de trazer o user baseado no id do banco de dados se ele existir;
- É validado que é possível listar um usuário específico com sucesso; - Ao listar um usuário com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP 200:
[
{
"id": 1,
"displayName": "Lewis Hamilton",
"email": "[email protected]",
"image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg"
}
]
- É validado que não é possível listar um usuário inexistente;
- Se o usuário for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP 404:
{ "message": "User does not exist" }
7 - Cria model de Category
- É validado que existe o arquivo Category.js;
- É validado que o modelo possui o nome Category;
- É validado que o modelo possui a propriedade id;
- É validado que o modelo possui a propriedade name;
8 - POST /categories
- O endpoint é acessível através do URL /categories;
- O endpoint é capaz de adicionar uma nova categoria na tabela no banco de dados;
- O corpo da requisição deverá seguir o formato abaixo:
[
{
"name": "Typescript"
}
]
-
É validado que não é possível cadastrar uma categoria sem o campo
name
;- Se a requisição não tiver o campo
name
devidamente preenchidos(não pode haver campo em branco), o resultado retornado é conforme exibido abaixo, com um status HTTP400
:
{ "message": "\"name\" is required" }
- Se a requisição não tiver o campo
-
É validado que é possível cadastrar uma categoria com sucesso;
- Se a categoria for criada com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
201
:
[ { "id": 3, "name": "Typescript" } ]
- Se a categoria for criada com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
9 - GET /categories
- O endpoint é acessível através do URL /categories;
- O endpoint é capaz de trazer todas categorias do banco de dados;
- É validado que é possível listar todas as categoria com sucesso;
- Ao listar categorias com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
[ { "id": 1, "name": "Inovação" }, { "id": 2, "name": "Escola" }, /* ... */ ]
- Ao listar categorias com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
10 - Cria model de BlogPost
Pontos avaliados-
É validado que existe o arquivo BlogPost.js;
-
É validado que o modelo possui o nome BlogPost;
-
É validado que o modelo possui a propriedade id;
-
É validado que o modelo possui a propriedade title;
-
É validado que o modelo possui a propriedade content;
-
É validado que o modelo possui a propriedade user_id;
-
É validado que o modelo possui a propriedade published;
-
É validado que o modelo possui a propriedade updated;
-
É validado que o modelo em BlogPost.js, define a associação belongsTo, com a entidade de nome User;
-
É validado que o modelo em User.js, define a associação hasMany, com a entidade de nome BlogPost;
11 - Cria model de PostCategory
-
É validado que existe o arquivo PostCategory.js;
-
É validado que o modelo possui o nome PostCategory;
-
É validado que o modelo possui a propriedade post_id;
-
É validado que o modelo possui a propriedade category_id;
-
É validado que o modelo em PostCategory.js, através do(s) modelos(s) de nome(s) Category; BlogPost, define a associação belongsToMany respectivamente, com o(s) modelo(s) de nome(s) BlogPost, Category;
12 - POST /post
- O endpoint é acessível através do URL /post;
- O endpoint é capaz de adicionar um novo blog post e vinculá-lo às categorias em suas tabelas no banco de dados;
- O corpo da requisição segue o formato abaixo:
-
É validado que não é possível cadastrar sem todos os campos preenchidos;
- Se a requisição não tiver todos os campos devidamente preenchidos(não pode haver campos em branco), o resultado retornado é conforme exibido abaixo, com um status HTTP
400
:
{ "message": "Some required fields are missing" }
- Se a requisição não tiver todos os campos devidamente preenchidos(não pode haver campos em branco), o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que não é possível cadastrar um blog_post com uma
categoryIds
inexistente;- Se a requisição não tiver o campo
categoryIds
devidamente preenchido com um array com todas as categorias existentes, o resultado retornado é conforme exibido abaixo, com um status HTTP400
:
{ "message": "one or more \"categoryIds\" not found" }
- Se a requisição não tiver o campo
-
É validado que é possível cadastrar um blog_post com sucesso;
- Se o blog post for criado com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
201
:
{
"title": "Latest updates, August 1st",
"content": "The whole text for the blog post goes here in this key",
"categoryIds": [1, 2]
}
{
"id": 3,
"title": "Latest updates, August 1st",
"content": "The whole text for the blog post goes here in this key",
"userId": 1,
"updated": "2022-05-18T18:00:01.196Z",
"published": "2022-05-18T18:00:01.196Z"
}
13 - GET /post/
- O endpoint é acessível através do URL /post;
- O endpoint é capaz de trazer todos os blogs post, user dono dele e as categorias do banco de dados;
-
É validado que é possível listar blogpost com sucesso;
- Ao listar posts com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
[ { "id": 1, "title": "Post do Ano", "content": "Melhor post do ano", "userId": 1, "published": "2011-08-01T19:58:00.000Z", "updated": "2011-08-01T19:58:51.000Z", "user": { "id": 1, "displayName": "Lewis Hamilton", "email": "[email protected]", "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg" }, "categories": [ { "id": 1, "name": "Inovação" } ] }, /* ... */ ]
- Ao listar posts com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
14 - GET /post/:id
- O endpoint é acessível através do URL /post/:id;
- O endpoint é capaz de trazer o blog post baseado no id do banco de dados se ele existir;
-
É validado que é possível listar um blogpost com sucesso;
- Ao listar um post com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
{ "id": 1, "title": "Post do Ano", "content": "Melhor post do ano", "userId": 1, "published": "2011-08-01T19:58:00.000Z", "updated": "2011-08-01T19:58:51.000Z", "user": { "id": 1, "displayName": "Lewis Hamilton", "email": "[email protected]", "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg" }, "categories": [ { "id": 1, "name": "Inovação" } ] }
- Ao listar um post com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que não é possível listar um blogpost inexistente;
- Se o post for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
404
:
{ "message": "Post does not exist" }
- Se o post for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
15 - PUT /post/:id
- O endpoint é acessível através do URL /post/:id;
- O endpoint é capaz de alterar um post do banco de dados, se ele existir;
- A aplicação só permite a alteração de um blog post caso a pessoa seja dona dele;
- A aplicação não permite a alteração das categorias do post, somente os atributos title e content podem ser alterados;
- O corpo da requisição segue o formato abaixo:
-
É validado que não é possível editar um blogpost com outro usuário;
- Somente o user que criou o blog post poderá editá-lo, o resultado retornado é conforme exibido abaixo, com um status HTTP
401
{ "message": "Unauthorized user" }
- Somente o user que criou o blog post poderá editá-lo, o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que não é possível editar sem todos os campos preenchidos;
- Se a requisição não tiver todos os campos devidamente preenchidos(não pode haver campos em branco), o resultado retornado é conforme exibido abaixo, com um status HTTP
400
:
{ "message": "Some required fields are missing" }
- Se a requisição não tiver todos os campos devidamente preenchidos(não pode haver campos em branco), o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que é possível editar um blogpost com sucesso;
- Se o blog post for alterado com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
{ "id": 3, "title": "Latest updates, August 1st", "content": "The whole text for the blog post goes here in this key", "userId": 1, "published": "2022-05-18T18:00:01.000Z", "updated": "2022-05-18T18:07:32.000Z", "user": { "id": 1, "displayName": "Lewis Hamilton", "email": "[email protected]", "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg" }, "categories": [ { "id": 1, "name": "Inovação" }, { "id": 2, "name": "Escola" } ] }
- Se o blog post for alterado com sucesso o resultado retornado é conforme exibido abaixo, com um status HTTP
{
"title": "Latest updates, August 1st",
"content": "The whole text for the blog post goes here in this key"
}
16 - DELETE /post/:id
- O endpoint é acessível através do URL /post/:id;
- O endpoint é capaz de deletar um blog post baseado no id do banco de dados se ele existir;
- A aplicação só permite a deleção de um blog post caso a pessoa seja dona dele;
-
É validado que não é possível deletar um blogpost com outro usuário;
- Somente o user que criou o blog post poderá deletá-lo, o resultado retornado é conforme exibido abaixo, com um status HTTP
401
{ "message": "Unauthorized user" }
- Somente o user que criou o blog post poderá deletá-lo, o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que é possível deletar um blogpost com sucesso;
- Se o blog post for deletado com sucesso não deve ser retornada nenhuma resposta, apenas um status HTTP
204
:
- Se o blog post for deletado com sucesso não deve ser retornada nenhuma resposta, apenas um status HTTP
-
É validado que não é possível deletar um blogpost inexistente;
- Se o post for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
404
:
{ "message": "Post does not exist" }
- Se o post for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
17 - DELETE /user/me
- O endpoint é acessível através do URL /user/me;
- O endpoint é capaz de deletar você do banco de dados, baseado no id que esta dentro do seu token;
- A aplicação é capaz de utilizar o token de autenticação nos headers, para saber o user logado correspondente á ser apagado;
- É validado que é possível excluir meu usuário com sucesso;
- Se o user for deletado com sucesso não deve ser retornada nenhuma resposta, apenas um status HTTP 204:
18 - /GET /post/search
- O endpoint é acessível através do URL /post/search;
- O endpoint é capaz de trazer os blogs post baseados no q do banco de dados, se ele existir;
- A aplicação é capaz de retornar um array de blogs post que contenham em seu título ou conteúdo o termo passado na URL;
- A aplicação é capaz de retornar um array vázio caso nenhum blog post satisfaça a busca;
- O query params da requisição segue o formato abaixo:
-
É validado que é possível buscar um blogpost pelo
title
;- Se a buscar for pelo
title
o resultado retornado é conforme exibido abaixo, com um status HTTP200
:
// GET /post/search?q=Vamos que vamos [ { "id": 2, "title": "Vamos que vamos", "content": "Foguete não tem ré", "userId": 1, "published": "2011-08-01T19:58:00.000Z", "updated": "2011-08-01T19:58:51.000Z", "user": { "id": 1, "displayName": "Lewis Hamilton", "email": "[email protected]", "image": "HTTPs://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg" }, "categories": [ { "id": 2, "name": "Escola" } ] } ]
- Se a buscar for pelo
-
É validado que é possível buscar um blogpost pelo
content
;- Se a buscar for pelo
content
o resultado retornado é conforme exibido abaixo, com um status HTTP200
:
// GET /post/search?q=Foguete não tem ré [ { "id": 2, "title": "Vamos que vamos", "content": "Foguete não tem ré", "userId": 1, "published": "2011-08-01T19:58:00.000Z", "updated": "2011-08-01T19:58:51.000Z", "user": { "id": 1, "displayName": "Lewis Hamilton", "email": "[email protected]", "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg" }, "categories": [ { "id": 2, "name": "Escola" } ] } ]
- Se a buscar for pelo
-
É validado se é possível buscar todos os blogpost quando passa a busca vazia;
- Se a buscar for vazia o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
// GET /post/search?q= [ { "id": 1, "title": "Post do Ano", "content": "Melhor post do ano", "userId": 1, "published": "2011-08-01T19:58:00.000Z", "updated": "2011-08-01T19:58:51.000Z", "user": { "id": 1, "displayName": "Lewis Hamilton", "email": "[email protected]", "image": "https://upload.wikimedia.org/wikipedia/commons/1/18/Lewis_Hamilton_2016_Malaysia_2.jpg" }, "categories": [ { "id": 1, "name": "Inovação" } ] }, /* ... */ ]
- Se a buscar for vazia o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que é possível buscar um blogpost inexistente e retornar array vazio;
- Se a buscar um post inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
200
:
// GET /post/search?q=BATATA []
- Se a buscar um post inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
[
{
http://localhost:PORT/post/search?q=vamos
}
]
VALIDAÇÃO DO TOKEN
- No requisito 4 é preciso criar um token para que seja validado nos próximos requisitos para que fosse possível consumir o endpoint;
- É validado nos requisitos 5, 6, 8, 9, 12, 13, 14, 15, 16, 17, 18;
-
É validado que não é possível fazer uma operação sem o token na requisição;
- Se o token for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
401
:
{ "message": "Token not found" }
- Se o token for inexistente o resultado retornado é conforme exibido abaixo, com um status HTTP
-
É validado que não é possível fazer uma operação com o token inválido;
- Se o token for inválido o resultado retornado é conforme exibido abaixo, com um status HTTP
401
:
{ "message": "Expired or invalid token" }
- Se o token for inválido o resultado retornado é conforme exibido abaixo, com um status HTTP
O projeto foi desenvolvido seguindo requisitos pré-estabelecidos:
- 1. Crie migrations para as tabelas users, categories, blog_posts, posts_categories;
- 2. Crie o modelo User em src/models/User.js com as propriedades corretas;
- 3. Sua aplicação deve ter o endpoint POST /login;
- 4. Sua aplicação deve ter o endpoint POST /user;
- 5. Sua aplicação deve ter o endpoint GET /user;
- 6. Sua aplicação deve ter o endpoint GET /user/:id;
- 7. Crie o modelo Category em src/models/Category.js com as propriedades corretas;
- 8. Sua aplicação deve ter o endpoint POST /categories;
- 9. Sua aplicação deve ter o endpoint GET /categories;
- 10. Crie o modelo BlogPost em src/models/BlogPost.js com as propriedades e associações corretas;
- 11. Crie o modelo PostCategory em src/models/PostCategory.js com as propriedades e associações corretas;
- 12. Sua aplicação deve ter o endpoint POST /post;
- 13. Sua aplicação deve ter o endpoint GET /post;
- 14. Sua aplicação deve ter o endpoint GET /post/:id;
- 15. Sua aplicação deve ter o endpoint PUT /post/:id;
- 16. Sua aplicação deve ter o endpoint DELETE /post/:id;
- 17. Sua aplicação deve ter o endpoint DELETE /user/me;
- 18. Sua aplicação deve ter o endpoint GET /post/search?q=:searchTerm;