Resolvendo o Exercise Tracker - Free-code-camp
Tags: express, javascript, freecodecamp, mongoose
Published: 2023-06-15 19:04:47

    
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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vgf4lspb01mg84j35u47.JPG)


   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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o179hl8xufkaygcjx1u4.png)

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: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oddfnsp1mqwkbs1h09dl.png)

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