Compare commits

...

15 Commits

Author SHA1 Message Date
radii 58729f00d9 explicitly listen to all IPv4 interfaces 2025-06-19 20:23:01 +10:00
radii d42811f152 remove outdated packages lock 2025-06-18 23:44:10 +10:00
radii 6e84cd0613 fix: send username as well in response when insert an exercise 2023-03-29 16:27:02 +11:00
radii 3334c14beb fix: return duration in Number format 2023-03-29 16:18:16 +11:00
radii e4bf128b29 Impl: from, to and limit query parameters in fetching exercise logs 2023-03-29 16:02:03 +11:00
radii cac28d59ed Imp: filter out __v 2023-03-29 14:59:24 +11:00
radii d84f12f4ea Imp: factored out date processing code 2023-03-29 14:57:58 +11:00
radii 33356d1fa5 fix: renamed logs sub-collection to log as per fcc test requirements 2023-03-29 14:51:08 +11:00
radii cfa55c020f Imp: store date in ticks and return as string and more
Rename log sub-collection to logs
2023-03-29 14:47:36 +11:00
radii ed0c3ac118 Impl: API: fetch user's exercise logs 2023-03-29 13:41:49 +11:00
radii 58c2e2f94e Imp: check if input date is valid in submitted exercise log 2023-03-29 13:16:23 +11:00
radii f223697b88 Impl: Adding exercise logs to DB 2023-03-29 13:06:13 +11:00
radii a318b8ffec Impl: GET users list 2023-03-29 02:34:09 +11:00
radii 0327b36031 Impl: creating users
Duplicate `usernames` allowed, user `_id` is unique and used to identify accounts
2023-03-29 01:56:48 +11:00
radii 2c5e138e9a Scaffold 2023-03-28 16:31:44 +11:00
3 changed files with 216 additions and 780 deletions
+195 -11
View File
@@ -1,18 +1,202 @@
const express = require('express')
const app = express()
const cors = require('cors')
require('dotenv').config()
const express = require("express");
const app = express();
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
app.use(cors())
app.use(express.static('public'))
app.get('/', (req, res) => {
res.sendFile(__dirname + '/views/index.html')
const PORT = process.env.PORT || 16749;
const MONGODB_URI = process.env.MONGODB_URI || "";
mongoose.connect(MONGODB_URI, {
// @ts-ignore
useNewUrlParser: true,
useUnifiedTopology: true,
});
const exerciseLogsSchema = new mongoose.Schema({
username: { type: String, required: true },
log: [
{
description: { type: String, required: true },
duration: { type: Number, required: true },
date: { type: Number, required: true },
},
],
});
const exerciseLogs = mongoose.model("ExerciseLogs", exerciseLogsSchema);
app.use(cors());
app.use(express.static("public"));
app.use(express.urlencoded({ extended: false }));
app.get("/", (req, res) => {
res.sendFile(__dirname + "/views/index.html");
});
app.post("/api/users", (req, res) => {
const username = req.body.username;
const exerciseLogsModel = new exerciseLogs({ username: username });
exerciseLogsModel
.save()
.then((user) => {
if (user) return res.json({ username: user.username, _id: user._id });
res.status(500);
res.json({ error: `Error creating user: ${username}` });
})
.catch((err) => {
res.status(500);
return res.json({
error: `Error type 2 while creating user: ${username}`,
});
});
});
const listener = app.listen(process.env.PORT || 3000, () => {
console.log('Your app is listening on port ' + listener.address().port)
})
app.get("/api/users", (req, res) => {
exerciseLogs
.find()
.select({ username: true, _id: true })
.exec()
.then((users) => {
res.json(users);
})
.catch((err) => {
res.sendStatus(500);
res.json({ error: `Error while retrieving users list:\n${err}` });
});
});
const getDateInTicks = (date) => {
// current time in tick if no date specified
if (!date) date = new Date().getTime();
// Convert String to Number if date specified in ticks
if (!Number.isNaN(Number(date))) date = Number(date);
// get date ticks from ticks Number or date String
date = new Date(date).getTime();
// date will be NaN if invalid date String is provided
return date;
};
app.post("/api/users/:_id/exercises", (req, res) => {
const _id = req.params._id;
const description = req.body.description;
const duration = Number(req.body.duration);
let date = getDateInTicks(req.body.date);
if (Number.isNaN(Number(date))) {
res.status(400);
return res.json({ error: `Input date: '${req.body.date}' is not valid` });
}
exerciseLogs
.findById(_id)
.select({ username: 1 })
.then((user) => {
if (user) {
exerciseLogs
.updateOne(
{ _id: _id },
{
$push: {
log: {
description: description,
duration: duration,
date: date,
},
},
},
{ runValidators: true }
)
.then((excLogs) => {
if (excLogs.modifiedCount) {
res.json({
_id: _id,
username: user.username,
description: description,
duration: duration,
date: new Date(date).toDateString(),
});
} else {
res.json(500);
res.json({
error: `Failed to add exercise logs to database:\n${excLogs}`,
});
}
})
.catch((err) => {
res.status(500);
res.json({
error: `Failed to add exercise logs to database:\n${err}`,
});
});
} else {
res.status(403);
res.json({ error: `No user with id: ${_id} exists` });
}
})
.catch((err) => {
res.status(500);
res.json({ error: "Failed to check user in database" });
});
});
app.get("/api/users/:_id/logs", (req, res) => {
const _id = req.params._id;
const limit = req.query.limit
? Math.trunc(Number(req.query.limit))
: Number.MAX_SAFE_INTEGER;
if (Number.isNaN(limit)) {
res.status(400);
return res.json({
error: `Query key 'limit''s value '${req.query.limit}' is not a number`,
});
}
const from = req.query.from ? getDateInTicks(req.query.from) : 0;
if (Number.isNaN(from)) {
res.status(400);
return res.json({
error: `Query key 'from''s value '${req.query.from}' is invalid for date`,
});
}
const to = req.query.to
? getDateInTicks(req.query.to)
: Number.MAX_SAFE_INTEGER;
if (Number.isNaN(to)) {
res.status(400);
return res.json({
error: `Query key 'to''s value '${req.query.to}' is invalid for date`,
});
}
exerciseLogs
.findById(_id)
.select({ "log._id": false, __v: false })
.then((excLogs) => {
if (excLogs) {
let excLogsJson = excLogs.toJSON();
excLogsJson.log = excLogsJson.log.filter(
(log) => log.date >= from && log.date <= to
);
excLogsJson.log.sort((a, b) => a.date - b.date);
excLogsJson.log = excLogsJson.log.slice(0, limit);
excLogsJson.count = excLogsJson.log.length;
excLogsJson.log = excLogsJson.log.map((log) => {
log.date = new Date(log.date).toDateString();
return log;
});
return res.json(excLogsJson);
}
res.status(400);
res.json({ error: `No user with id: '${_id}' exists` });
})
.catch((err) => {
res.status(400);
res.json({ error: `Input user id: '${_id}' is malformed` });
});
});
const listener = app.listen(PORT, "0.0.0.0", () => {
console.log(
`Your app is listening on interface ${
listener.address().address
} and port ${listener.address().port}`
);
});
-749
View File
@@ -1,749 +0,0 @@
{
"name": "fcc-exercise-tracker",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "fcc-exercise-tracker",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.16.4"
}
},
"node_modules/accepts": {
"version": "1.3.7",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"license": "MIT"
},
"node_modules/body-parser": {
"version": "1.19.0",
"license": "MIT",
"dependencies": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/bytes": {
"version": "3.1.0",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-disposition": {
"version": "0.5.3",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.4",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.4.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"license": "MIT"
},
"node_modules/cors": {
"version": "2.8.5",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "2.6.9",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "1.1.2",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/destroy": {
"version": "1.0.4",
"license": "MIT"
},
"node_modules/dotenv": {
"version": "8.2.0",
"license": "BSD-2-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "1.0.2",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.17.1",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.1.2",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/http-errors": {
"version": "1.7.2",
"license": "MIT",
"dependencies": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.3",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"license": "MIT"
},
"node_modules/methods": {
"version": "1.1.2",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.44.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.27",
"license": "MIT",
"dependencies": {
"mime-db": "1.44.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.2",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.6",
"license": "MIT",
"dependencies": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.7.0",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.6"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.4.0",
"license": "MIT",
"dependencies": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"license": "MIT"
},
"node_modules/send": {
"version": "0.17.1",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.1",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.14.1",
"license": "MIT",
"dependencies": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.1.1",
"license": "ISC"
},
"node_modules/statuses": {
"version": "1.5.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/toidentifier": {
"version": "1.0.0",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
}
},
"dependencies": {
"accepts": {
"version": "1.3.7",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"array-flatten": {
"version": "1.1.1"
},
"body-parser": {
"version": "1.19.0",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"bytes": {
"version": "3.1.0"
},
"content-disposition": {
"version": "0.5.3",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4"
},
"cookie": {
"version": "0.4.0"
},
"cookie-signature": {
"version": "1.0.6"
},
"cors": {
"version": "2.8.5",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"debug": {
"version": "2.6.9",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2"
},
"destroy": {
"version": "1.0.4"
},
"dotenv": {
"version": "8.2.0"
},
"ee-first": {
"version": "1.1.1"
},
"encodeurl": {
"version": "1.0.2"
},
"escape-html": {
"version": "1.0.3"
},
"etag": {
"version": "1.8.1"
},
"express": {
"version": "4.17.1",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"finalhandler": {
"version": "1.1.2",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2"
},
"fresh": {
"version": "0.5.2"
},
"http-errors": {
"version": "1.7.2",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.3"
},
"ipaddr.js": {
"version": "1.9.1"
},
"media-typer": {
"version": "0.3.0"
},
"merge-descriptors": {
"version": "1.0.1"
},
"methods": {
"version": "1.1.2"
},
"mime": {
"version": "1.6.0"
},
"mime-db": {
"version": "1.44.0"
},
"mime-types": {
"version": "2.1.27",
"requires": {
"mime-db": "1.44.0"
}
},
"ms": {
"version": "2.0.0"
},
"negotiator": {
"version": "0.6.2"
},
"object-assign": {
"version": "4.1.1"
},
"on-finished": {
"version": "2.3.0",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3"
},
"path-to-regexp": {
"version": "0.1.7"
},
"proxy-addr": {
"version": "2.0.6",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0"
},
"range-parser": {
"version": "1.2.1"
},
"raw-body": {
"version": "2.4.0",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"safe-buffer": {
"version": "5.1.2"
},
"safer-buffer": {
"version": "2.1.2"
},
"send": {
"version": "0.17.1",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"ms": {
"version": "2.1.1"
}
}
},
"serve-static": {
"version": "1.14.1",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"setprototypeof": {
"version": "1.1.1"
},
"statuses": {
"version": "1.5.0"
},
"toidentifier": {
"version": "1.0.0"
},
"type-is": {
"version": "1.6.18",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"unpipe": {
"version": "1.0.0"
},
"utils-merge": {
"version": "1.0.1"
},
"vary": {
"version": "1.1.2"
}
}
}
+21 -20
View File
@@ -1,22 +1,23 @@
{
"name": "fcc-exercise-tracker",
"version": "0.1.0",
"description": "A REST API project, part of Free Code Camp's curriculum",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"dotenv": "^8.2.0",
"express": "^4.16.4",
"cors": "^2.8.5"
},
"repository": {
"url": "https://github.com/freeCodeCamp/boilerplate-project-exercisetracker"
},
"license": "MIT",
"keywords": [
"node",
"express"
]
"name": "@radii-fcc-backend/fcc-exercise-tracker",
"version": "0.1.0",
"description": "A REST API project, part of Free Code Camp's curriculum",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.16.4",
"mongoose": "^7.0.3"
},
"repository": {
"url": "https://github.com/freeCodeCamp/boilerplate-project-exercisetracker"
},
"license": "AGPL-3.0-or-later",
"keywords": [
"node",
"express"
]
}