DEV Community

Adriel Henrique
Adriel Henrique

Posted on • Edited on

1

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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay