Resolvendo o Exercise Tracker - Free-code-camp
Adriel Henrique
Posted on June 15, 2023
A algum tempo fiz a certificação de Back-end da free-code camp,a maioria dos projetos propostos são no geral bem simples, no entanto, houve um que o nível de complexidade é mais elevado, que é o projeto de “exercise tracker”, neste artigo vou explicar como acabei resolvendo ele.
Para este artigo vou usar algumas ferramentas:
- O repositório oficial do freecode camp do projeto.
- Postman para trabalhar com a API
Além de alguns conhecimentos prévios, como:
Algumas noções de requições HTTP/REST.
-
Express, Mongoose e Atlas DB
O projeto quando importado do Github ou do Replit tem apenas as funções básicas do express:
const express = require('express')
const app = express()
const cors = require('cors')
require('dotenv').config()
Devemos adicionar as dependências do projeto tais como o Mongoose, para que funcione como nosso banco de dados NoSQL e o Body-parser para que possamos trabalhar com nossas requisições.
const express = require('express')
const app = express()
const cors = require('cors')
require('dotenv').config()
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json());
Indo para o html:
<form action="/api/users" method="post">
<h3>Create a New User</h3>
<p><code>POST /api/users</code></p>
<input id="username" type="text" name="username" placeholder="username" />
<input type="submit" value="Submit" />
</form>
<form id="exercise-form" method="post">
<h3>Add exercises</h3>
<p><code>POST /api/users/:_id/exercises</code></p>
<input id="uid" type="text" name="_id" placeholder=":_id" />
<input id="desc" type="text" name="description" placeholder="description*" />
<input id="dur" type="text" name="duration" placeholder="duration* (mins.)" />
<input id="date" type="text" name="date" placeholder="date (yyyy-mm-dd)" />
<input type="submit" value="Submit" />
</form>
<script>
const exerciseForm = document.getElementById("exercise-form");
exerciseForm.addEventListener("submit", () => {
const userId = document.getElementById("uid").value;
exerciseForm.action = `/api/users/${userId}/exercises`;
exerciseForm.submit();
});
</script>
Análisando o html vemos que este possui dois formulários que chamam rotas diferentes uma chama a rota ‘/api/users/’ com uma requisição do tipo POST e a outra ‘api/users/:_id/exercises’.
Primeiramente,após definirmos as variaveis de ambiente e fazer a conexão com o AtlasDB devemos criar os Schemas e Modelos que usaremos:
async function main() {
await mongoose.connect(mySecret);
}
//-----------
const userSchema = mongoose.Schema({
username: String,
_id: String
});
const exerciseSchema = mongoose.Schema({
duser: String,
description:{
type: String,
required: true
},
duration:{
type: Number,
required: true
},
date: Date
});
var User = mongoose.model('user',userSchema);
var exercise = mongoose.model('exercise',exerciseSchema);
No primeiro requisito deve-se utilizando um nome de usuário fornecido no formulário 'Create a New User' para um usuário e devolva uma resposta em JSON com o nome do usuário e seu id
app.post('/api/users',async function(req,res){
const user = req.body.username;
try{
const username = await User.findOne({username: user});
if(username == null){
let newId = new mongoose.mongo.ObjectId();
const newUser = new User({username: req.body.username, _id: newId});
await newUser.save();
res.json({'username':newUser.username,'_id': newUser._id});
}else{
res.json({'username':username.username,'_id': username._id});
}
}catch(err){
res.status(500).json(err);
}
});
No trecho de código acima, antes de registrar ele procura para ver se este usuário existe, caso o resultado seja "null" ele criara um novo usuário.
Como requisito para terminar o projeto também é necessário que quando houver uma requisição do tipo GET para a mesma rota ela devolva um array de objetos com o nome de usuário e id dos usuários registrados,algo relativamente simples:
app.get('/api/users',async function(req,res){
try{
var users = [];
users = await User.find();
res.json(users);
}catch(err){
res.status(500).json(err);
}
})
A resposta do tipo get deve ser assim:
Seguindo o desafio,devemos fazer com que a API possa salvar exercícios desde que ceda uma id(através da URL) de usuário,uma descrição e uma duração, caso uma data não seja cedida,devemos pegar a data atual.
app.post('/api/users/:_id/exercises', async function(req,res){
const id = req.params._id;
const data = req.body;
let date;
if(data.date == null || data.date == ''){
date = new Date();
}else{
date = data.date;
}
try{
const newexer = new exercise({
duser: id,
description:data.description,
duration: data.duration,
date:date
});
await newexer.save();
const user = await User.findById(id).exec();
res.json({
username: user.username,
_id: user._id,
description:newexer.description,
duration:newexer.duration,
date: new Date(newexer.date).toDateString()
});
}catch(err){
res.status(500).json(err);
}
});
Note que usaremos o campo "duser" para guardar o id do usuário. A rota de GET seguirá uma linha parecida com a anterior:
app.get('/api/users/:_id/exercises', async function(req,res){
const id = req.params._id;
try{
const exers = await exercise.find({duser: id});
res.json(exers);
}catch(err){
res.status(500).json(err);
}
})
A rota agora construiremos a rota de logs, onde reside maior parte do desafio:
Quando solicitado à rota deve-ser devolver o objeto do usuário com o campo "count" que recebe o valor do número de exercicios cadastrados para o usuário, além do array dos exercicios, além da possibilidade de podermos filtrar por faixa de tempo, para isso usaremos o Postman.
Vendo a parte tracejada em vermelho,temos um exemplo de solicitação GET onde a Query esta na URL, no caso após "?" temos as condicionais que devem ser atendidas na consulta, para isso devemos criar um objeto que receba os parametros from,to e Limit usando o método "req.query", assim:
app.get('/api/users/:_id/logs',async function(req,res){
const id = req.params._id;
const { from, to, limit } = req.query;
let dateObj = {}
if (from) {
dateObj["$gte"] = new Date(from)
}
if (to){
dateObj["$lte"] = new Date(to)
}
let filter = {
duser: id
}
if(from || to){
filter.date = dateObj;
}
try{
const user = await User.findById(id).exec();
const exers = await exercise.find(filter).limit(+limit ?? 500);
const num = await exercise.find({ duser: id}).count().exec();
let log = exers.map((e) => {
return {
'description': e.description,
'duration': e.duration,
'date': new Date(e.date).toDateString()
};
});
res.json({
username: user.username,
count : num,
_id: user._id,
log});
}catch(err){
res.status(500).json(err);
}
})
A resposta deve ser assim:
Por fim, o código completo:
const express = require('express')
const app = express()
const cors = require('cors')
require('dotenv').config()
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json());
app.use(cors())
app.use(express.static('public'))
app.get('/', (req, res) => {
res.sendFile(__dirname + '/views/index.html')
});
const mySecret = process.env['MONGO_URI']
main().catch(err => console.log(err));
async function main() {
await mongoose.connect(mySecret);
}
//-----------
const userSchema = mongoose.Schema({
username: String,
_id: String
});
const exerciseSchema = mongoose.Schema({
duser: String,
description:{
type: String,
required: true
},
duration:{
type: Number,
required: true
},
date: Date
});
var User = mongoose.model('user',userSchema);
var exercise = mongoose.model('exercise',exerciseSchema);
//------------
app.post('/api/users',async function(req,res){
const user = req.body.username;
try{
const username = await User.findOne({username: user});
if(username == null){
let newId = new mongoose.mongo.ObjectId();
const newUser = new User({username: req.body.username, _id: newId});
await newUser.save();
res.json({'username':newUser.username,'_id': newUser._id});
}else{
res.json({'username':username.username,'_id': username._id});
}
}catch(err){
res.status(500).json(err);
}
});
//--------------------
app.get('/api/users',async function(req,res){
try{
var users = [];
users = await User.find();
res.json(users);
}catch(err){
res.status(500).json(err);
}
})
app.post('/api/users/:_id/exercises', async function(req,res){
const id = req.params._id;
const data = req.body;
let date;
if(data.date == null || data.date == ''){
date = new Date();
}else{
date = data.date;
}
try{
const newexer = new exercise({
duser: id,
description:data.description,
duration: data.duration,
date:date
});
await newexer.save();
const user = await User.findById(id).exec();
res.json({
username: user.username,
_id: user._id,
description:newexer.description,
duration:newexer.duration,
date: new Date(newexer.date).toDateString()
});
}catch(err){
res.status(500).json(err);
}
});
app.get('/api/users/:_id/exercises', async function(req,res){
const id = req.params._id;
try{
const exers = await exercise.find({duser: id});
res.json(exers);
}catch(err){
res.status(500).json(err);
}
})
app.get('/api/users/:_id/logs',async function(req,res){
const id = req.params._id;
const { from, to, limit } = req.query;
let dateObj = {}
if (from) {
dateObj["$gte"] = new Date(from)
}
if (to){
dateObj["$lte"] = new Date(to)
}
let filter = {
duser: id
}
if(from || to){
filter.date = dateObj;
}
try{
const user = await User.findById(id).exec();
const exers = await exercise.find(filter).limit(+limit ?? 500);
const num = await exercise.find({ duser: id}).count().exec();
let log = exers.map((e) => {
return {
'description': e.
description
,
'duration': e.duration,
'date': new Date(e.date).toDateString()
};
});
res.json({
username: user.username,
count : num,
_id: user._id,
log});
}catch(err){
res.status(500).json(err);
}
})
const listener = app.listen(process.env.PORT || 3000, () => {
console.log('Your app is listening on port ' + listener.address().port)
})
link do projeto no replit: https://replit.com/@AdrielH024/boilerplate-project-exercisetracker?v=1
link do projeto no github:
https://github.com/AdrielH024/free-Code-Camp-ExerciseTracker
Posted on June 15, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.