Resolvendo o Exercise Tracker - Free-code-camp

adrielh024

Adriel Henrique

Posted on June 15, 2023

Resolvendo o Exercise Tracker - Free-code-camp

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()

Enter fullscreen mode Exit fullscreen mode

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());

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
    <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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
  }
});
Enter fullscreen mode Exit fullscreen mode

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);
  }

})

Enter fullscreen mode Exit fullscreen mode

A resposta do tipo get deve ser assim:

Image description

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);

  }
});

Enter fullscreen mode Exit fullscreen mode

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);

  }
})
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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);

  }
})
Enter fullscreen mode Exit fullscreen mode

A resposta deve ser assim:

Image description

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)
})

Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
adrielh024
Adriel Henrique

Posted on June 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related