How to include _id in JSON response (Exercise Tracker)

These tests still fail:

You can POST to /api/exercise/add with form data userId=_id, description, duration, and optionally date. If no date is supplied, the current date will be used. The response returned will be the user object with the exercise fields added.

You can make a GET request to /api/exercise/log with a parameter of userId=_id to retrieve a full exercise log of any user. The returned response will be the user object with a log array of all the exercises added. Each log item has the description, duration, and date properties.

A request to a user's log (/api/exercise/log) returns an object with a count property representing the number of exercises returned.

You can add from, to and limit parameters to a /api/exercise/log request to retrieve part of the log of any user. from and to are dates in yyyy-mm-dd format. limit is an integer of how many logs to send back.

Here’s my current code:

const express = require("express");
const app = express();
const cors = require("cors");
require("dotenv").config();
const path = require("path");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");

mongoose.connect(process.env.DB_URI, { useNewUrlParser: true, useUnifiedTopology: true });

const Schema = mongoose.Schema;
const exerciseSchema = new Schema({
  description: String,
  duration: Number,
  date: Date
});
const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: [exerciseSchema]
});

const User = mongoose.model("user", userSchema);

app.use(cors());
app.use(express.static("public"));
app.get("/", (req, res) => {
  res.sendFile(path.join(`${__dirname}`, "/views/index.html"));
});

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post("/api/exercise/new-user", (req, res) => {
  const username = req.body.username;

  const user = new User({
    username: username
  });

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (!foundUser) {
      user.save((err, user) => {
        if (err) {
          console.log(err);
        }
        console.log(`user ${user.username} saved to database!`);
      });
    }

    res.json({
      username: user.username,
      _id: user._id
    });
  });
});

app.post("/api/exercise/add", (req, res) => {
  const userId = req.body.userId;
  const description = req.body.description;
  const duration = Number(req.body.duration);
  const currentDate = new Date();

  // use date provided by user or use current date
  const date = new Date(req.body.date).toDateString() || currentDate.toDateString();

  User.findByIdAndUpdate(userId, {
    $push: {
      exercises: {
        description: description,
        duration: duration,
        date: date
      }
    }
  }, { new: true, useFindAndModify: false }, (err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id,
        exercises: foundUser.exercises
      });
    }
  });
});

app.get("/api/exercise/users", (req, res) => {
  User.find({}, (err, foundUsers) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUsers.length === 0) {
      res.json({ error: "No users in database" });
    }

    res.json(foundUsers.map(user => {
      const { __v, ...rest } = user._doc;
      return rest;
    }));
  });
});

app.get("/api/exercise/log", (req, res) => {
  const userId = req.params.userId;
  const fromDate = new Date(req.params.from);
  const toDate = new Date(req.params.to);
  const logLimit = Number(req.params.limit);

  const isLeapYear = year => {
    if (year % 4 === 0) {
      if (year % 100 === 0) {
        if (year % 400 === 0) {
          return true;
        }
      }
    }
    return false;
  };

  const isDateValid = date => {
    // more than 29 days in February during leap year
    // or more than 28 days in February during common year
    // is invalid date and a negative date is invalid
    if (isLeapYear(date.getFullYear()) && (date.getMonth() + 1) === 2) {
      if (date.getDate() > 29) {
        return false;
      }
    } else {
      if (date.getDate() > 28) {
        return false;
      }
    }

    if (date.getDate() < 1) {
      return false;
    }

    // more than 31 days in these months is invalid
    // January, March, May, July, August, October, December
    if ((date.getMonth() + 1) === 1 || (date.getMonth() + 1) === 3 || (date.getMonth() + 1) === 7 ||
    (date.getMonth() + 1) === 8 || (date.getMongth() + 1) === 10 || (date.getMonth() + 1) === 12) {
      if (date.getDate() > 31) {
        return false;
      }
      // more than 30 days in these months is invalid
      // April, June, September, October, December
    } else if ((date.getMonth() + 1) === 4 || (date.getMonth() + 1) === 6 ||
    (date.getMonth() + 1) === 9 || (date.getMonth() + 1) === 11) {
      if (date.getDate() > 30) {
        return false;
      }
    }

    if ((date.getMonth() + 1) < 0 || (date.getMonth() + 1) > 12) {
      return false;
    }

    return true;
  };

  User.findById(userId).limit(logLimit).exec((err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      let filteredExercises = [];
      if (isDateValid(fromDate) && isDateValid(toDate)) {
        filteredExercises = foundUser.exercises.map(exercise => {
          if (!(exercise.date >= fromDate && exercise.date <= toDate)) {
            return false;
          }
          return true;
        });

        res.json({
          exercises: filteredExercises,
          count: filteredExercises.length + 1
        });
      } else {
        console.log("from date and/or to date is/are invalid");
      }
    }
  });
});

const listener = app.listen(process.env.PORT || 3000, () => {
  console.log("Your app is listening on port " + listener.address().port);
});

Any help is appreciated. Thanks.

if req.body.date is not provided it will be “undefined” then your date will be “invalid date”
you should use ternary operator instead of or operator
I can also see that you didn’t fixed the first issue I mentioned. I do not know if it has an impact on the test but when you try to create a new user with the same username the id returned by your route is not the id of the user registered in the database

You mean in here?

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (!foundUser) {
      user.save((err, user) => {
        if (err) {
          console.log(err);
        }
        console.log(`user ${user.username} saved to database!`);
      });
    }

    res.json({
      username: user.username,
      _id: user._id
    });
  });

My logic here is to insert the new user if one with that username isn’t already present. That’s why for the condition if (!foundUser) I do user.save: because foundUser isn’t truthy in that case. Should I add an else clause for when foundUser is truthy then?

Also, would fixing this issue also resolve the duplicate exercises array issue without the need for a subdocument?

what you are returning is the created user. it is best to not create a new user until you check it doesn’t exist. if the user exist then you will return the foundUser otherwise you will return the saved user not the created user

this issue has nothing to do with exercises. using subdocuments is the way of using a nosql database. it is totally fine to do it like this. if you do not like it then you can use another collection for the exercises. you will have the userId in every exercise (document) you will create (this is how they do on relational database)

Alright, thanks for the reply.

By the way, is it not necessary to worry about duplicate exercises? If it’s not I’ll just make it a property of userSchema again.

@Sky020 @yakhousam How about this for the issue @yakhousam mentioned?

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id
      });
    } else {
      const user = new User({
        username: username
      });

      user.save((err, user) => {
        if (err) {
          console.log(err);
        }
        console.log(`user ${user.username} saved to database!`);
      });

      res.json({
        username: user.username,
        _id: user._id
      });
    }
  });

Also, I’ll ask again: is it not necessary to worry about duplicate exercises ? If it’s not I’ll just make it a property of userSchema again. Thanks in advance for replies/help.

you do not handle the error properly. if there is an error you will still return the json and I believe user.username will throw an error. rather than this, you should return an error. something like this return res.status(500).send("cannot add exercise")

if it passes the test then it is not necessary to fix this

I do not understand what you mean.
you can do this, in this case you can put anything you want on exercise

const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: [ ],
});

you can do this. in this case it will be a nested path that mean it is not a subdocument. you can’t use mongoose model method on it and there will not be an _id

const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: [
    {
      description: String,
      duration: Number,
      date: { type: Date, default: Date.now },
    },
  ],
});

or you can do this. in this case it will be a subdocument. you can use on it mongoose model methods and it will be saved with an _id

const exerciseShema = new Schema({
  description: String,
  duration: Number,
  date: { type: Date, default: Date.now },
});
const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: [exerciseShema],
});

for me I prefer the last one

The code I showed was from the new-user route.

And the tests still fail. I get this message:

You can POST to /api/exercise/add with form data userId=_id, description, duration, and optionally date. If no date is supplied, the current date will be used. The response returned will be the user object with the exercise fields added.

You can make a GET request to /api/exercise/log with a parameter of userId=_id to retrieve a full exercise log of any user. The returned response will be the user object with a log array of all the exercises added. Each log item has the description, duration, and date properties. (Test timed out)

A request to a user's log (/api/exercise/log) returns an object with a count property representing the number of exercises returned. (Test timed out)

You can add from, to and limit parameters to a /api/exercise/log request to retrieve part of the log of any user. from and to are dates in yyyy-mm-dd format. limit is an integer of how many logs to send back. (Test timed out)

And all four tests after the first three fail (the first three tests pass).

Current code for new-user:

app.post("/api/exercise/new-user", (req, res) => {
  const username = req.body.username;

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id
      });
    } else {
      const user = new User({
        username: username
      });

      user.save((err, user) => {
        if (err) {
          console.log(err);
          return res.status(500).send("unable to add user");
        }
        console.log(`user ${user.username} saved to database!`);
      });

      res.json({
        username: user.username,
        _id: user._id
      });
    }
  });
});

like this you are returning the new user before saving it. what if it fail to save the new user?. you should move your res.json into your user.save function. you shouldn’t return the new user unless you are sure it is saved to the database

If no date is supplied, the current date will be used. The response returned will be the user object with the exercise fields added.
if you fixed the currentdate issue I mentioned before, I believe the response they expect is in form
{ userid, username, description, duration, date}
it expect to return the exercise added not the whole array of exercises

@Sky020 @yakhousam Then how about this for the schema?

const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  description: { type: String },
  duration: { type: Number },
  date: { type: Date }
});

Take out the array and just have exercise data fields individually.

Actually, never mind about taking the array out. I need the number of exercises for the log route, which seems to require an array to work with. So I’ll return a response with just the exercise fields for the add route but keep the array.

Now add works but log still doesn’t work.

Message from the tests:

// running tests
You can make a GET request to /api/exercise/log with a parameter of userId=_id to retrieve a full exercise log of any user. The returned response will be the user object with a log array of all the exercises added. Each log item has the description, duration, and date properties. (Test timed out)

A request to a user's log (/api/exercise/log) returns an object with a count property representing the number of exercises returned. (Test timed out)

You can add from, to and limit parameters to a /api/exercise/log request to retrieve part of the log of any user. from and to are dates in yyyy-mm-dd format. limit is an integer of how many logs to send back. (Test timed out)
// tests completed

The last three tests still fail but the first four pass now.

Full code:

const express = require("express");
const app = express();
const cors = require("cors");
require("dotenv").config();
const path = require("path");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");

mongoose.connect(process.env.DB_URI, { useNewUrlParser: true, useUnifiedTopology: true });

const Schema = mongoose.Schema;
const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: { type: Array }
});

const User = mongoose.model("user", userSchema);

app.use(cors());
app.use(express.static("public"));
app.get("/", (req, res) => {
  res.sendFile(path.join(`${__dirname}`, "/views/index.html"));
});

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post("/api/exercise/new-user", (req, res) => {
  const username = req.body.username;

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id
      });
    } else {
      const user = new User({
        username: username
      });

      user.save((err, user) => {
        if (err) {
          console.log(err);
          return res.status(500).send("unable to add user");
        }
        console.log(`user ${user.username} saved to database!`);

        res.json({
          username: user.username,
          _id: user._id
        });
      });
    }
  });
});

app.post("/api/exercise/add", (req, res) => {
  const userId = req.body.userId;
  const description = req.body.description;
  const duration = Number(req.body.duration);
  const currentDate = new Date();

  // use date provided by user or use current date
  const reqDate = new Date(req.body.date);
  const date = reqDate.toDateString() ? reqDate.toDateString() : currentDate.toDateString();
  User.findByIdAndUpdate(userId, {
    $push: {
      exercises: {
        description: description,
        duration: duration,
        date: date
      }
    }
  }, { new: true, useFindAndModify: false }, (err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id,
        description: description,
        duration: duration,
        date: date
      });
    }
  });
});

app.get("/api/exercise/users", (req, res) => {
  User.find({}, (err, foundUsers) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUsers.length === 0) {
      res.json({ error: "No users in database" });
    }

    res.json(foundUsers.map(user => {
      const { __v, ...rest } = user._doc;
      return rest;
    }));
  });
});

app.get("/api/exercise/log", (req, res) => {
  const userId = req.params.userId;
  const fromDate = new Date(req.params.from);
  const toDate = new Date(req.params.to);
  const logLimit = Number(req.params.limit);

  const isLeapYear = year => {
    if (year % 4 === 0) {
      if (year % 100 === 0) {
        if (year % 400 === 0) {
          return true;
        }
      }
    }
    return false;
  };

  const isDateValid = date => {
    // more than 29 days in February during leap year
    // or more than 28 days in February during common year
    // is invalid date and a negative date is invalid
    if (isLeapYear(date.getFullYear()) && (date.getMonth() + 1) === 2) {
      if (date.getDate() > 29) {
        return false;
      }
    } else {
      if (date.getDate() > 28) {
        return false;
      }
    }

    if (date.getDate() < 1) {
      return false;
    }

    // more than 31 days in these months is invalid
    // January, March, May, July, August, October, December
    if ((date.getMonth() + 1) === 1 || (date.getMonth() + 1) === 3 || (date.getMonth() + 1) === 7 ||
    (date.getMonth() + 1) === 8 || (date.getMongth() + 1) === 10 || (date.getMonth() + 1) === 12) {
      if (date.getDate() > 31) {
        return false;
      }
      // more than 30 days in these months is invalid
      // April, June, September, October, December
    } else if ((date.getMonth() + 1) === 4 || (date.getMonth() + 1) === 6 ||
    (date.getMonth() + 1) === 9 || (date.getMonth() + 1) === 11) {
      if (date.getDate() > 30) {
        return false;
      }
    }

    if ((date.getMonth() + 1) < 0 || (date.getMonth() + 1) > 12) {
      return false;
    }

    return true;
  };

  User.findById(userId).limit(logLimit).exec((err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      let filteredExercises = [];
      if (isDateValid(fromDate) && isDateValid(toDate)) {
        filteredExercises = foundUser.exercises.map(exercise => {
          if (!(exercise.date >= fromDate && exercise.date <= toDate)) {
            return false;
          }
          return true;
        });

        res.json({
          exercises: filteredExercises,
          count: filteredExercises.length + 1
        });
      } else {
        console.log("from date and/or to date is/are invalid");
      }
    }
  });
});

const listener = app.listen(process.env.PORT || 3000, () => {
  console.log("Your app is listening on port " + listener.address().port);
});

if you console log all this variables you will see that they are all undefined
it is req.query not req.params. you are looking for the query
you should sanitize your query otherwise if the query is not provided you will have new Date(undefined) that is invalid date. same thing for logLimit you will get NaN

an Id is unique, findById return only one document. why do apply a limit on it?
a classic findById will work here.

if fromDate or toDate is not provided this condition will be false and you will return nothing. you should return all the exercises
``

they named exercises array “log”. they expect you to return something like this

{
  "_id": ,
  "username": "",
  "count": ,
  "log": [
    {
      "description": "",
      "duration": "",
      "date": ""
    },
    {
      "description": "",
      "duration": "",
      "date": ""
    }
  ]
}
1 Like

The user stories ask for a log limit and a date range for the logs. The userId is unique so there can only be one, but that userId may have more than exercise log corresponding to it. The app visitor would choose a from date, a to date and a log limit to filter out the logs for a given user.

Am I doing the limiting part wrong?

if we had a separate collection for exercises then yes you could use find().limit but here we have the exercises as an array. so the limit will be the array length. you will slice the array according to the limit

So would this be good then?

const slicedExercises = filteredExercises.slice(0, logLimit);

This is what I have for log now:

app.get("/api/exercise/log", (req, res) => {
  const userId = req.query.userId;
  const fromDate = new Date(req.query.from);
  const toDate = new Date(req.query.to);
  const logLimit = Number(req.query.limit);

  const isLeapYear = year => {
    if (year % 4 === 0) {
      if (year % 100 === 0) {
        if (year % 400 === 0) {
          return true;
        }
      }
    }
    return false;
  };

  const isDateValid = date => {
    if (date) {
      // more than 29 days in February during leap year
    // or more than 28 days in February during common year
    // is invalid date and a negative date is invalid
      if (isLeapYear(date.getFullYear()) && (date.getMonth() + 1) === 2) {
        if (date.getDate() > 29) {
          return false;
        }
      } else {
        if (date.getDate() > 28) {
          return false;
        }
      }

      if (date.getDate() < 1) {
        return false;
      }

      // more than 31 days in these months is invalid
      // January, March, May, July, August, October, December
      if ((date.getMonth() + 1) === 1 || (date.getMonth() + 1) === 3 || (date.getMonth() + 1) === 7 ||
    (date.getMonth() + 1) === 8 || (date.getMongth() + 1) === 10 || (date.getMonth() + 1) === 12) {
        if (date.getDate() > 31) {
          return false;
        }
      // more than 30 days in these months is invalid
      // April, June, September, October, December
      } else if ((date.getMonth() + 1) === 4 || (date.getMonth() + 1) === 6 ||
    (date.getMonth() + 1) === 9 || (date.getMonth() + 1) === 11) {
        if (date.getDate() > 30) {
          return false;
        }
      }

      if ((date.getMonth() + 1) < 0 || (date.getMonth() + 1) > 12) {
        return false;
      }

      return true;
    }
    return false;
  };

  User.findById(userId, (err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      let filteredExercises = [];
      if (isDateValid(fromDate) && isDateValid(toDate)) {
        filteredExercises = foundUser.exercises.map(exercise => {
          if (!(exercise.date >= fromDate && exercise.date <= toDate)) {
            return false;
          }
          return true;
        });

        const slicedExercises = filteredExercises.slice(0, logLimit);

        res.json({
          username: foundUser.username,
          _id: foundUser._id,
          log: slicedExercises,
          count: slicedExercises.length + 1
        });
      } else {
        console.log("from date and/or to date is/are invalid");
      }
    }
  });
});

I got all but the very last test to pass. The date format for the last one to pass has to be yyyy-mm-dd. I’ll work on that next.

Full code for now:

const express = require("express");
const app = express();
const cors = require("cors");
require("dotenv").config();
const path = require("path");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");

mongoose.connect(process.env.DB_URI, { useNewUrlParser: true, useUnifiedTopology: true });

const Schema = mongoose.Schema;
const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: { type: Array }
});

const User = mongoose.model("user", userSchema);

app.use(cors());
app.use(express.static("public"));
app.get("/", (req, res) => {
  res.sendFile(path.join(`${__dirname}`, "/views/index.html"));
});

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post("/api/exercise/new-user", (req, res) => {
  const username = req.body.username;

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id
      });
    } else {
      const user = new User({
        username: username
      });

      user.save((err, user) => {
        if (err) {
          console.log(err);
          return res.status(500).send("unable to add user");
        }
        console.log(`user ${user.username} saved to database!`);

        res.json({
          username: user.username,
          _id: user._id
        });
      });
    }
  });
});

app.post("/api/exercise/add", (req, res) => {
  const userId = req.body.userId;
  const description = req.body.description;
  const duration = Number(req.body.duration);
  const currentDate = new Date();

  // use date provided by user or use current date
  const reqDate = new Date(req.body.date);
  const date = reqDate ? reqDate.toDateString() : currentDate.toDateString();
  User.findByIdAndUpdate(userId, {
    $push: {
      exercises: {
        description: description,
        duration: duration,
        date: date
      }
    }
  }, { new: true, useFindAndModify: false }, (err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id,
        description: description,
        duration: duration,
        date: date
      });
    }
  });
});

app.get("/api/exercise/users", (req, res) => {
  User.find({}, (err, foundUsers) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUsers.length === 0) {
      res.json({ error: "No users in database" });
    }

    res.json(foundUsers.map(user => {
      const { __v, ...rest } = user._doc;
      return rest;
    }));
  });
});

app.get("/api/exercise/log", (req, res) => {
  const userId = req.query.userId;
  const fromDate = (req.query.from) ? new Date(req.query.from) : undefined;
  const toDate = (req.query.to) ? new Date(req.query.to) : undefined;
  const logLimit = (req.query.limit) ? Number(req.query.limit) : undefined;

  const isLeapYear = year => {
    if (year % 4 === 0) {
      if (year % 100 === 0) {
        if (year % 400 === 0) {
          return true;
        }
      }
    }
    return false;
  };

  const isDateValid = date => {
    // more than 29 days in February during leap year
    // or more than 28 days in February during common year
    // is invalid date and a negative date is invalid
    if (isLeapYear(date.getFullYear()) && (date.getMonth() + 1) === 2) {
      if (date.getDate() > 29) {
        return false;
      }
    } else {
      if (date.getDate() > 28) {
        return false;
      }
    }

    if (date.getDate() < 1) {
      return false;
    }

    // more than 31 days in these months is invalid
    // January, March, May, July, August, October, December
    if ((date.getMonth() + 1) === 1 || (date.getMonth() + 1) === 3 || (date.getMonth() + 1) === 7 ||
  (date.getMonth() + 1) === 8 || (date.getMongth() + 1) === 10 || (date.getMonth() + 1) === 12) {
      if (date.getDate() > 31) {
        return false;
      }
    // more than 30 days in these months is invalid
    // April, June, September, October, December
    } else if ((date.getMonth() + 1) === 4 || (date.getMonth() + 1) === 6 ||
  (date.getMonth() + 1) === 9 || (date.getMonth() + 1) === 11) {
      if (date.getDate() > 30) {
        return false;
      }
    }

    if ((date.getMonth() + 1) < 0 || (date.getMonth() + 1) > 12) {
      return false;
    }

    return true;
  };

  User.findById(userId, (err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUser) {
      if (!logLimit && !fromDate && !toDate) {
        res.json({
          username: foundUser.username,
          _id: foundUser._id,
          log: foundUser.exercises,
          count: foundUser.exercises.length + 1
        });
      } else {
        let filteredExercises = [];
        if (isDateValid(fromDate) && isDateValid(toDate)) {
          filteredExercises = foundUser.exercises.map(exercise => {
            if (!(exercise.date >= fromDate && exercise.date <= toDate)) {
              return false;
            }
            return true;
          });

          let slicedExercises = [];
          if (logLimit) {
            slicedExercises = filteredExercises.slice(0, logLimit);
          } else {
            slicedExercises = filteredExercises.slice(0);
          }

          res.json({
            username: foundUser.username,
            _id: foundUser._id,
            log: slicedExercises,
            count: slicedExercises.length + 1
          });
        } else {
          console.log("from date and/or to date is/are invalid");
        }
      }
    }
  });
});

const listener = app.listen(process.env.PORT || 3000, () => {
  console.log("Your app is listening on port " + listener.address().port);
});
2 Likes

@yakhousam @Sky020 I had one test that left that wasn’t passing before but now it’s all of the last four again.

The reason that I was trying to fix the “Invalid Date” issue that cropped up. Some of the logs in the database have “Invalid Date” for the date property. I can’t figure out how to fix that.

Current code so far:

const express = require("express");
const app = express();
const cors = require("cors");
require("dotenv").config();
const path = require("path");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const moment = require("moment");

mongoose.connect(process.env.DB_URI, { useNewUrlParser: true, useUnifiedTopology: true });

const Schema = mongoose.Schema;
const userSchema = new Schema({
  username: { type: String, unique: true }, // can't have someone register more than once
  exercises: { type: Array }
});

const User = mongoose.model("user", userSchema);

app.use(cors());
app.use(express.static("public"));
app.get("/", (req, res) => {
  res.sendFile(path.join(`${__dirname}`, "/views/index.html"));
});

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post("/api/exercise/new-user", (req, res) => {
  const username = req.body.username;

  User.findOne({ username: username }, (err, foundUser) => {
    if (err) {
      console.log(err);
    }

    if (foundUser) {
      res.json({
        username: foundUser.username,
        _id: foundUser._id
      });
    } else {
      const user = new User({
        username: username
      });

      user.save((err, user) => {
        if (err) {
          console.log(err);
          return res.status(500).send(`error: ${err}`);
        }
        console.log(`user ${user.username} saved to database!`);

        res.json({
          username: user.username,
          _id: user._id
        });
      });
    }
  });
});

const isLeapYear = year => {
  if (year % 4 === 0) {
    if (year % 100 === 0) {
      if (year % 400 === 0) {
        return true;
      }
    }
  }
  return false;
};

const isDateValid = date => {
  const dateObj = new Date(date);
  // more than 29 days in February during leap year
  // or more than 28 days in February during common year
  // is invalid date and a negative date is invalid
  if (isLeapYear(dateObj.getFullYear()) && (dateObj.getMonth() + 1) === 2) {
    if (date.getDate() > 29) {
      return false;
    }
  } else {
    if (dateObj.getDate() > 28) {
      return false;
    }
  }

  if (dateObj.getDate() < 1) {
    return false;
  }

  // more than 31 days in these months is invalid
  // January, March, May, July, August, October, December
  if ((dateObj.getMonth() + 1) === 1 || (dateObj.getMonth() + 1) === 3 || (dateObj.getMonth() + 1) === 7 ||
  (dateObj.getMonth() + 1) === 8 || (dateObj.getMongth() + 1) === 10 || (dateObj.getMonth() + 1) === 12) {
    if (date.getDate() > 31) {
      return false;
    }
    // more than 30 days in these months is invalid
    // April, June, September, October, December
  } else if ((dateObj.getMonth() + 1) === 4 || (dateObj.getMonth() + 1) === 6 ||
  (dateObj.getMonth() + 1) === 9 || (dateObj.getMonth() + 1) === 11) {
    if (date.getDate() > 30) {
      return false;
    }
  }

  if ((dateObj.getMonth() + 1) < 0 || (dateObj.getMonth() + 1) > 12) {
    return false;
  }

  return true;
};

app.post("/api/exercise/add", (req, res) => {
  const userId = req.body.userId;
  const description = req.body.description;
  const duration = Number(req.body.duration);
  const currentDate = new Date();

  // use date provided by user or use current date
  let reqDate;
  if (req.body.date !== "" || req.body.date !== undefined) {
    reqDate = new Date(req.body.date);
  }

  const date = reqDate ? reqDate.toDateString() : currentDate.toDateString();
  if (moment(date, true).isValid()) {
    User.findByIdAndUpdate(userId, {
      $push: {
        exercises: {
          description: description,
          duration: duration,
          date: date
        }
      }
    }, { new: true, useFindAndModify: false }, (err, foundUser) => {
      if (err) {
        console.log(err);
        res.json({ error: err });
      }

      if (foundUser) {
        res.json({
          username: foundUser.username,
          _id: foundUser._id,
          description: description,
          duration: duration,
          date: date
        });
      }
    });
  }
});

app.get("/api/exercise/users", (req, res) => {
  User.find({}, (err, foundUsers) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    if (foundUsers.length === 0) {
      res.json({ error: "No users in database" });
    }

    res.json(foundUsers.map(user => {
      const { __v, ...rest } = user._doc;
      return rest;
    }));
  });
});

app.get("/api/exercise/log", (req, res) => {
  const userId = req.query.userId;
  const fromDateStr = req.query.from ? req.query.from : undefined;
  const toDateStr = req.query.to ? req.query.to : undefined;
  const logLimit = req.query.limit ? Number(req.query.limit) : undefined;

  User.findById(userId, (err, foundUser) => {
    if (err) {
      console.log(err);
      res.json({ error: err });
    }

    let filteredExercises = [];
    const fromDateObj = new Date(fromDateStr);
    const toDateObj = new Date(toDateStr);
    if (foundUser) {
      // when a log limit, a from date, nor a to date is provided
      if (!logLimit && !fromDateStr && !toDateStr) {
        res.json({
          username: foundUser.username,
          _id: foundUser._id,
          log: foundUser.exercises,
          count: foundUser.exercises.length + 1
        });
        // when all three are provided
      } else if (logLimit !== undefined && fromDateStr !== undefined && toDateStr !== undefined) {
        if (moment(fromDateStr, "YYYY-MM-DD", true).isValid() &&
            moment(toDateStr, "YYYY-MM-DD", true).isValid()) {
          if (isDateValid(fromDateStr) && isDateValid(toDateStr)) {
            filteredExercises = foundUser.exercises.filter(exercise => {
              if (!(exercise.date >= fromDateObj && exercise.date <= toDateObj)) {
                return false;
              }
              return true;
            });

            let slicedExercises = [];
            if (logLimit < filteredExercises.length - 1) {
              slicedExercises = filteredExercises.slice(0, logLimit);
            } else {
              slicedExercises = filteredExercises.slice(0);
            }

            res.json({
              username: foundUser.username,
              _id: foundUser._id,
              log: slicedExercises,
              count: slicedExercises.length + 1
            });
          }
        }
        // when only a log limit is provided
      } else if (logLimit !== undefined && !toDateStr && !fromDateStr) {
        const slicedExercises = foundUser.exercises.slice(0, logLimit);

        res.json({
          username: foundUser.username,
          _id: foundUser._id,
          log: slicedExercises,
          count: slicedExercises.length + 1
        });
        // when a log limit and a from date is provided but a to date is not
      } else if (logLimit !== undefined && !toDateStr && fromDateStr !== undefined) {
        if (isDateValid(fromDateStr)) {
          filteredExercises = foundUser.exercises.filter(exercise => {
            if (!(exercise.date >= fromDateObj)) {
              return false;
            }
            return true;
          });

          const slicedExercises = filteredExercises.slice(0, logLimit);

          res.json({
            username: foundUser.username,
            _id: foundUser._id,
            log: slicedExercises,
            count: slicedExercises.length + 1
          });
        }
        // when a log limit is not provided but a to date and a from date are
      } else if (!logLimit && toDateStr !== undefined && fromDateStr !== undefined) {
        if (isDateValid(fromDateStr) && isDateValid(toDateStr)) {
          filteredExercises = foundUser.exercises.filter(exercise => {
            if (!(exercise.date >= fromDateObj && exercise.date <= toDateObj)) {
              return false;
            }
            return true;
          });

          res.json({
            username: foundUser.username,
            _id: foundUser._id,
            log: filteredExercises,
            count: filteredExercises.length + 1
          });
        }
      }
    }
  });
});

const listener = app.listen(process.env.PORT || 3000, () => {
  console.log(`Your app is listening on port ${listener.address().port}`);
});

Also, since the comparisons involving the invalid Date object may be causing the last test to fail too: how do I check if the string value for a Date is “Invalid Date”? Any help appreciated. Thanks.

I can see that you installed moment.js. so something like this will work

  const reqDate = req.body.date
  const isValid = moment(reqDate, "YYYY-MM-DD").isValid()
  const date = isValid ? reqDate : moment().format("YYYY-MM-DD")
  

we read the query date. if it is valid, we use it otherwise we use moment date.
you will also need to wipe your user collection in your mongo atlas to remove all the invalid dates saved before and start from an empty user collection.

Thanks.

I think I’ll go back to the version of the code where only the last test was failing and then try adding the code you gave.

Edit: For add it just has to be in the Mon Jan 1 1990 format, where “Mon” is the first three letters of the name of the day, “Jan” is the first three letters of the month name, then the date, and then the year. The log route where the exercise logs are filtered by from and to dates is where the “YYYY-MM-DD” format is required.