diff --git a/.env b/.env
new file mode 100644
index 00000000..5c8ed308
--- /dev/null
+++ b/.env
@@ -0,0 +1,4 @@
+# DB #
+ DATABASE_URL = mongodb+srv://khalilazaiez:Jeuxgratuits123@cluster0.wcdot.mongodb.net/assignment1?retryWrites=true&w=majority
+ JWT_SECRET= SECRET#123
+ JWT_EXP= 10m
\ No newline at end of file
diff --git a/Helpers/error.js b/Helpers/error.js
new file mode 100644
index 00000000..3a6c7fb9
--- /dev/null
+++ b/Helpers/error.js
@@ -0,0 +1,20 @@
+class ErrorHandler extends Error {
+ constructor(statusCode, message) {
+ super();
+ this.statusCode = statusCode;
+ this.message = message;
+ }
+ }
+ const handleError = (err, res) => {
+ const { statusCode, message } = err;
+ res.status(statusCode).json({
+ status: "error",
+ statusCode,
+ message
+ });
+ };
+ module.exports = {
+ ErrorHandler,
+ handleError
+ }
+
\ No newline at end of file
diff --git a/app.js b/app.js
new file mode 100644
index 00000000..b13ba957
--- /dev/null
+++ b/app.js
@@ -0,0 +1,72 @@
+require("./config/config");
+require("./config/passportConfig");
+const { handleError } = require("./helpers/error");
+
+if (process.env.NODE_ENV !== "production") {
+ require("dotenv").config();
+}
+const express = require("express");
+const app = express();
+const bodyParser = require("body-parser");
+const cors = require("cors");
+const rtsIndex = require("./routes/index.router");
+const passport = require("passport");
+
+const mongoose = require("mongoose");
+mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true });
+const db = mongoose.connection;
+db.on("error", (error) => console.error(error));
+db.once("open", () => console.log("Connected to Mongoose"));
+
+// middleware
+app.use(bodyParser.json());
+app.use(cors());
+app.use("/api", rtsIndex);
+app.use(passport.initialize());
+app.use((err, req, res, next) => {
+ handleError(err, res);
+});
+
+app.listen(process.env.PORT || 3000);
+
+var userSchema = new mongoose.Schema({
+ email: {
+ type: String,
+ required: "Email can't be empty",
+ unique: true,
+ },
+ password: {
+ type: String,
+ required: "Password can't be empty",
+ minlength: [4, "Password must be atleast 4 character long"],
+ },
+ saltSecret: String,
+});
+
+// Custom validation for email
+userSchema.path("email").validate((val) => {
+ emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ return emailRegex.test(val);
+}, "Invalid e-mail.");
+
+// Events
+userSchema.pre("save", function(next) {
+ bcrypt.genSalt(10, (err, salt) => {
+ bcrypt.hash(this.password, salt, (err, hash) => {
+ this.password = hash;
+ this.saltSecret = salt;
+ next();
+ });
+ });
+});
+
+// Methods
+userSchema.methods.verifyPassword = function(password) {
+ return bcrypt.compareSync(password, this.password);
+};
+
+userSchema.methods.generateJwt = function() {
+ return jwt.sign({ _id: this._id }, process.env.JWT_SECRET, {
+ expiresIn: process.env.JWT_EXP,
+ });
+};
diff --git a/config/config.json b/config/config.json
new file mode 100644
index 00000000..126f49d0
--- /dev/null
+++ b/config/config.json
@@ -0,0 +1,14 @@
+{
+ "development": {
+ "PORT": 3000,
+ "MONGODB_URL": "mongodb+srv://khalilazaiez:Jeuxgratuits123@cluster0.wcdot.mongodb.net/assignment1?retryWrites=true&w=majority",
+ "JWT_SECRET": "SECRET#123",
+ "JWT_EXP": "10m"
+ },
+ "production": {
+ "PORT": 80,
+ "MONGODB_URL": "mongodb+srv://khalilazaiez:Jeuxgratuits123@cluster0.wcdot.mongodb.net/assignment1?retryWrites=true&w=majority",
+ "JWT_SECRET": "SECRET#123",
+ "JWT_EXP": "2m"
+ }
+}
diff --git a/config/jwtHelper.js b/config/jwtHelper.js
new file mode 100644
index 00000000..b0311f5e
--- /dev/null
+++ b/config/jwtHelper.js
@@ -0,0 +1,22 @@
+const jwt = require("jsonwebtoken");
+
+module.exports.verifyJwtToken = (req, res, next) => {
+ var token;
+ if ("authorization" in req.headers)
+ token = req.headers["authorization"].split(" ")[1];
+
+ if (!token)
+ return res.status(401).send({ auth: false, message: "No token provided." });
+ else {
+ jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
+ if (err)
+ return res
+ .status(500)
+ .send({ auth: false, message: "Token authentication failed." });
+ else {
+ req._id = decoded._id;
+ next();
+ }
+ });
+ }
+};
diff --git a/config/passportConfig.js b/config/passportConfig.js
new file mode 100644
index 00000000..80212d5c
--- /dev/null
+++ b/config/passportConfig.js
@@ -0,0 +1,20 @@
+const passport = require("passport");
+const localStrategy = require("passport-local").Strategy;
+const mongoose = require("mongoose");
+const User = require("../models/user.model");
+
+passport.use(
+ new localStrategy({ usernameField: "email" }, (username, password, done) => {
+ User.findOne({ email: username }, (err, user) => {
+ if (err) return done(err);
+ // unknown user
+ else if (!user)
+ return done(null, false, { message: "Email is not registered" });
+ // wrong password
+ else if (!user.verifyPassword(password))
+ return done(null, false, { message: "Wrong password." });
+ // authentication succeeded
+ else return done(null, user);
+ });
+ })
+);
diff --git a/controller/category.controller.js b/controller/category.controller.js
new file mode 100644
index 00000000..2c7c61d8
--- /dev/null
+++ b/controller/category.controller.js
@@ -0,0 +1,77 @@
+const mongoose = require("mongoose");
+const passport = require("passport");
+const _ = require("lodash");
+const Category = require("../models/category.model");
+const { handleError, ErrorHandler } = require("../helpers/error");
+
+module.exports.createCategory = async (req, res, next) => {
+ try{
+ var category = new Category();
+ category.name = req.body.name;
+ category.created_at = req.body.created_at;
+ category.updated_at = req.body.updated_at;
+ category.user_id = req.params.id;
+ category.save((err, doc) => {
+ if (!err) res.send(doc);
+ else {
+ return next(err);
+ }
+ });
+ }
+ catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+ }
+}
+ module.exports.getCategory = (req, res, next) => {
+ try{
+ Category .find({ user_id: req._id }, (err, category ) => {
+ if (category )
+ return res
+ .status(200)
+ .json({ status: true, category} );
+ else
+ return res.status(404).json({ status: false, message: "category not found" });
+ });
+ }
+ catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+ }
+}
+ module.exports.updateCategory = (req, res, next) => {
+ try{
+ Category .findOneAndUpdate(
+ { _id: req.params.id },
+ { $set: { name: req.body.name } },
+ { $set:{note:req.body.note}},
+ { $set:{image: req.body.image} },
+ function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ }
+ );
+ }catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+ }
+}
+
+ module.exports.deleteCategory = (req, res, next) => {
+ try{
+ Category .findOneAndDelete({ id: req.params.id }, function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ });
+ }catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+ }
+}
+
\ No newline at end of file
diff --git a/controller/item.controller.js b/controller/item.controller.js
new file mode 100644
index 00000000..f9e383d1
--- /dev/null
+++ b/controller/item.controller.js
@@ -0,0 +1,82 @@
+const mongoose = require("mongoose");
+const passport = require("passport");
+const _ = require("lodash");
+const Item = require("../models/Item.model");
+const { handleError, ErrorHandler } = require("../helpers/error");
+
+module.exports.createItem = async (req, res, next) => {
+ try{
+ var item = new Item();
+ item.name = req.body.name;
+ item.category_id = req.params.category_id;
+ item.list_id = req.body.list_id;
+ item.note = req.body.note;
+ item.image = req.body.image;
+ item.created_at = req.body.created_at;
+ item.updated_at = req.body.updated_at;
+ item.user_id = req.params.user_id;
+ item.save((err, doc) => {
+ if (!err) res.send(doc);
+ else {
+ return next(err);
+ }
+ });
+}
+catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+}
+}
+module.exports.getItems = (req, res, next) => {
+ try{
+ Item.find({ user_id: req._id }, (err, items) => {
+ if (items)
+ return res
+ .status(200)
+ .json({ status: true, items});
+
+ else
+ return res.status(404).json({ status: false, message: "item not found" });
+ });
+}
+catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+}
+}
+module.exports.updateItem = (req, res, next) => {
+ try{
+ Item.findOneAndUpdate(
+ { _id: req.params.id },
+ { $set: { name: req.body.name } },
+ { $set:{note:req.body.note}},
+ { $set:{image: req.body.image} },
+ function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ }
+ );
+}catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+}
+}
+module.exports.deleteItem = (req, res, next) => {
+ try{
+ Item.findOneAndDelete({ _id: req.params.id }, function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ });
+}
+catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+}
+}
+
diff --git a/controller/list.controller.js b/controller/list.controller.js
new file mode 100644
index 00000000..316ac30c
--- /dev/null
+++ b/controller/list.controller.js
@@ -0,0 +1,157 @@
+const mongoose = require("mongoose");
+const passport = require("passport");
+const _ = require("lodash");
+const List = require("../models/list.model");
+const Item = require("../models/Item.model");
+const { handleError, ErrorHandler } = require("../helpers/error");
+
+module.exports.createList = async (req, res, next) => {
+ try {
+ var list = new List();
+ list.id = req.body.id;
+ list.name = req.body.name;
+ list.created_at = req.body.created_at;
+ list.updated_at = req.body.updated_at;
+ list.user_id = req.params.id;
+ list.save((err, doc) => {
+ if (!err) res.send(doc);
+ else {
+ return next(err);
+ }
+ });
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+module.exports.getLists = (req, res, next) => {
+ try {
+ List.find((err, lists) => {
+ if (lists) return res.status(200).json({ status: true, lists });
+ else
+ return res
+ .status(404)
+ .json({ status: false, message: "list not found" });
+ });
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+module.exports.updateList = (req, res, next) => {
+ try {
+ List.findOneAndUpdate(
+ { _id: req.params.id },
+ { $set: { name: req.body.name } },
+ function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ }
+ );
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+module.exports.deleteList = (req, res, next) => {
+ try {
+ List.findOneAndDelete({ id: req.params.id }, function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ });
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+
+module.exports.createItemByList = async (req, res, next) => {
+ try {
+ Item.findOneAndUpdate(
+ { _id: req.params.id },
+ { $set: { list_id: req.params.list_id } },
+ (err, items) => {
+ if (items) return res.status(200).json({ status: true, items });
+ else
+ return res
+ .status(404)
+ .json({ status: false, message: "item not found" });
+ }
+ );
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+
+module.exports.getItemsByList = (req, res, next) => {
+ try {
+ Item.find({ list_id: req.params.list_id }, (err, items) => {
+ if (items) return res.status(200).json({ status: true, items });
+ else
+ return res
+ .status(404)
+ .json({ status: false, message: "item not found" });
+ });
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+
+module.exports.updateItemByList = (req, res, next) => {
+ try {
+ Item.findOneAndUpdate(
+ { _id: req.params.id },
+ { $set: { name: req.body.name } },
+ { $set: { note: req.body.note } },
+ { $set: { image: req.body.image } },
+ function(error, success) {
+ if (error) {
+ res.status(404).json({ status: false });
+ } else {
+ res.status(200).json({ status: true });
+ }
+ }
+ );
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
+
+module.exports.deleteItemByList = (req, res, next) => {
+ try {
+ Item.findOneAndUpdate(
+ { _id: req.params.id },
+ { $set: { list_id: null } },
+ (err, items) => {
+ if (items)
+ return res
+ .status(200)
+ .json({ status: true, message: "item deleted from the list" });
+ else
+ return res
+ .status(404)
+ .json({ status: false, message: "item not found" });
+ }
+ );
+ } catch (error) {
+ if (error.status === 500)
+ error = new ErrorHandler(500, "Internal server error");
+ next(error);
+ }
+};
diff --git a/controller/user.controller.js b/controller/user.controller.js
new file mode 100644
index 00000000..74f5c469
--- /dev/null
+++ b/controller/user.controller.js
@@ -0,0 +1,55 @@
+const mongoose = require("mongoose");
+const passport = require("passport");
+const _ = require("lodash");
+const User = mongoose.model("User");
+const { handleError, ErrorHandler } = require("../helpers/error");
+
+module.exports.signup = async (req, res, next) => {
+ try {
+ const user = await User.findOne({email: req.body.email})
+ if(user){
+ throw new ErrorHandler(422, "duplicate e-mail adress")
+ }
+ res.status(201).json(await User.create(req.body))
+ }
+ catch (error) {
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ if(error.status===401) error = new ErrorHandler(401,"Unauthorized")
+ next(error);
+ }
+}
+
+module.exports.login = (req, res, next) => {
+ try{
+ // call for passport authentication
+ passport.authenticate("local", (err, user, info) => {
+ // error from passport middleware
+ if (err) throw new ErrorHandler(400, "error from passport");
+ // registered user
+ else if (user) return res.status(200).json({ token: user.generateJwt() });
+ // unknown user or wrong password
+ else return res.status(422).json({status: false, info});
+ })(req, res);
+ }catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+ }
+}
+
+module.exports.getUser = (req, res, next) => {
+ try{
+ User.findOne({ _id: req._id }, (err, user) => {
+ if (!user)
+ return res
+ .status(404)
+ .json({ status: false, message: "User record not found." });
+ else
+ return res
+ .status(200)
+ .json({ status: true, user: _.pick(user, ["email"], ["username"]) });
+ });
+}catch(error){
+ if(error.status===500) error = new ErrorHandler(500,"Internal server error")
+ next(error)
+ }
+}
diff --git a/models/category.model.js b/models/category.model.js
new file mode 100644
index 00000000..fbda3f6a
--- /dev/null
+++ b/models/category.model.js
@@ -0,0 +1,20 @@
+const mongoose = require("mongoose");
+
+var categorySchema = new mongoose.Schema({
+ name: {
+ type: String,
+ required: "name can't be empty",
+ },
+ user_id: {
+ type: String,
+ },
+ created_at: {
+ type: String,
+ required: true,
+ },
+ updated_at: {
+ type: String,
+ required: true,
+ },
+ });
+module.exports = mongoose.model("Category", categorySchema);
diff --git a/models/item.model.js b/models/item.model.js
new file mode 100644
index 00000000..1804aed2
--- /dev/null
+++ b/models/item.model.js
@@ -0,0 +1,36 @@
+const mongoose = require("mongoose");
+
+var itemSchema = new mongoose.Schema({
+ name: {
+ type: String,
+ required: "name can't be empty",
+ },
+ category_id: {
+ type: String,
+ required: true
+ },
+ user_id: {
+ type: String,
+
+ },
+ list_id: {
+ type: String,
+ },
+ note: {
+ type: String,
+ required: true,
+ },
+ image: {
+ type: String,
+ required: true,
+ },
+ created_at: {
+ type: String,
+ required: true,
+ },
+ updated_at: {
+ type: String,
+ required: true,
+ },
+});
+module.exports = mongoose.model("Item", itemSchema);
diff --git a/models/list.model.js b/models/list.model.js
new file mode 100644
index 00000000..5b330adc
--- /dev/null
+++ b/models/list.model.js
@@ -0,0 +1,23 @@
+const mongoose = require("mongoose");
+
+var listSchema = new mongoose.Schema({
+ id:{
+ type: Number
+ },
+ name: {
+ type: String,
+ required: "name can't be empty",
+ },
+ user_id: {
+ type: String,
+ },
+ created_at: {
+ type: String,
+ required: true,
+ },
+ updated_at: {
+ type: String,
+ required: true,
+ },
+});
+module.exports = mongoose.model("List", listSchema);
diff --git a/models/user.model.js b/models/user.model.js
new file mode 100644
index 00000000..f156837f
--- /dev/null
+++ b/models/user.model.js
@@ -0,0 +1,51 @@
+const mongoose = require("mongoose");
+const bcrypt = require("bcryptjs");
+const jwt = require("jsonwebtoken");
+
+
+var userSchema = new mongoose.Schema({
+ email: {
+ type: String,
+ required: "Email can't be empty",
+ unique: true,
+ },
+ password: {
+ type: String,
+ required: "Password can't be empty",
+ minlength: [4, "Password must be atleast 4 character long"],
+ },
+ username: {
+ type: String,
+ required: "username can't be empty",
+ },
+ saltSecret: String,
+});
+
+// Custom validation for email
+userSchema.path("email").validate((val) => {
+ emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ return emailRegex.test(val);
+}, "Invalid e-mail.");
+
+// Events
+userSchema.pre("save", function(next) {
+ bcrypt.genSalt(10, (err, salt) => {
+ bcrypt.hash(this.password, salt, (err, hash) => {
+ this.password = hash;
+ this.saltSecret = salt;
+ next();
+ });
+ });
+});
+
+// Methods
+userSchema.methods.verifyPassword = function(password) {
+ return bcrypt.compareSync(password, this.password);
+};
+
+userSchema.methods.generateJwt = function() {
+ return jwt.sign({ _id: this._id }, process.env.JWT_SECRET, {
+ expiresIn: process.env.JWT_EXP,
+ });
+};
+module.exports = mongoose.model("User", userSchema);
diff --git a/node_modules/.bin/is-ci b/node_modules/.bin/is-ci
new file mode 100644
index 00000000..e79342ff
--- /dev/null
+++ b/node_modules/.bin/is-ci
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../is-ci/bin.js" "$@"
+ ret=$?
+else
+ node "$basedir/../is-ci/bin.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/is-ci.cmd b/node_modules/.bin/is-ci.cmd
new file mode 100644
index 00000000..e3276c08
--- /dev/null
+++ b/node_modules/.bin/is-ci.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\is-ci\bin.js" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/is-ci.ps1 b/node_modules/.bin/is-ci.ps1
new file mode 100644
index 00000000..3fe23403
--- /dev/null
+++ b/node_modules/.bin/is-ci.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../is-ci/bin.js" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../is-ci/bin.js" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/.bin/mime b/node_modules/.bin/mime
new file mode 100644
index 00000000..91e5e16a
--- /dev/null
+++ b/node_modules/.bin/mime
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../mime/cli.js" "$@"
+ ret=$?
+else
+ node "$basedir/../mime/cli.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/mime.cmd b/node_modules/.bin/mime.cmd
new file mode 100644
index 00000000..746a2798
--- /dev/null
+++ b/node_modules/.bin/mime.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\mime\cli.js" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/mime.ps1 b/node_modules/.bin/mime.ps1
new file mode 100644
index 00000000..a6f6f470
--- /dev/null
+++ b/node_modules/.bin/mime.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../mime/cli.js" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../mime/cli.js" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/.bin/nodemon b/node_modules/.bin/nodemon
new file mode 100644
index 00000000..439386d9
--- /dev/null
+++ b/node_modules/.bin/nodemon
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../nodemon/bin/nodemon.js" "$@"
+ ret=$?
+else
+ node "$basedir/../nodemon/bin/nodemon.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/nodemon.cmd b/node_modules/.bin/nodemon.cmd
new file mode 100644
index 00000000..2ef28880
--- /dev/null
+++ b/node_modules/.bin/nodemon.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\nodemon\bin\nodemon.js" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/nodemon.ps1 b/node_modules/.bin/nodemon.ps1
new file mode 100644
index 00000000..413e5cb8
--- /dev/null
+++ b/node_modules/.bin/nodemon.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../nodemon/bin/nodemon.js" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/.bin/nodetouch b/node_modules/.bin/nodetouch
new file mode 100644
index 00000000..1f7f0015
--- /dev/null
+++ b/node_modules/.bin/nodetouch
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../touch/bin/nodetouch.js" "$@"
+ ret=$?
+else
+ node "$basedir/../touch/bin/nodetouch.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/nodetouch.cmd b/node_modules/.bin/nodetouch.cmd
new file mode 100644
index 00000000..1f78c68a
--- /dev/null
+++ b/node_modules/.bin/nodetouch.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\touch\bin\nodetouch.js" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/nodetouch.ps1 b/node_modules/.bin/nodetouch.ps1
new file mode 100644
index 00000000..236659ce
--- /dev/null
+++ b/node_modules/.bin/nodetouch.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../touch/bin/nodetouch.js" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../touch/bin/nodetouch.js" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/.bin/nopt b/node_modules/.bin/nopt
new file mode 100644
index 00000000..e658aac4
--- /dev/null
+++ b/node_modules/.bin/nopt
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../nopt/bin/nopt.js" "$@"
+ ret=$?
+else
+ node "$basedir/../nopt/bin/nopt.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/nopt.cmd b/node_modules/.bin/nopt.cmd
new file mode 100644
index 00000000..c92ec036
--- /dev/null
+++ b/node_modules/.bin/nopt.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\nopt\bin\nopt.js" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/nopt.ps1 b/node_modules/.bin/nopt.ps1
new file mode 100644
index 00000000..68c40bf9
--- /dev/null
+++ b/node_modules/.bin/nopt.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../nopt/bin/nopt.js" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../nopt/bin/nopt.js" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/.bin/rc b/node_modules/.bin/rc
new file mode 100644
index 00000000..9e016267
--- /dev/null
+++ b/node_modules/.bin/rc
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../rc/cli.js" "$@"
+ ret=$?
+else
+ node "$basedir/../rc/cli.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/rc.cmd b/node_modules/.bin/rc.cmd
new file mode 100644
index 00000000..aedece9d
--- /dev/null
+++ b/node_modules/.bin/rc.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\rc\cli.js" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/rc.ps1 b/node_modules/.bin/rc.ps1
new file mode 100644
index 00000000..ac2cd2a5
--- /dev/null
+++ b/node_modules/.bin/rc.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../rc/cli.js" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../rc/cli.js" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/.bin/semver b/node_modules/.bin/semver
new file mode 100644
index 00000000..10497aa8
--- /dev/null
+++ b/node_modules/.bin/semver
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../semver/bin/semver" "$@"
+ ret=$?
+else
+ node "$basedir/../semver/bin/semver" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/node_modules/.bin/semver.cmd b/node_modules/.bin/semver.cmd
new file mode 100644
index 00000000..eb3aaa1e
--- /dev/null
+++ b/node_modules/.bin/semver.cmd
@@ -0,0 +1,17 @@
+@ECHO off
+SETLOCAL
+CALL :find_dp0
+
+IF EXIST "%dp0%\node.exe" (
+ SET "_prog=%dp0%\node.exe"
+) ELSE (
+ SET "_prog=node"
+ SET PATHEXT=%PATHEXT:;.JS;=;%
+)
+
+"%_prog%" "%dp0%\..\semver\bin\semver" %*
+ENDLOCAL
+EXIT /b %errorlevel%
+:find_dp0
+SET dp0=%~dp0
+EXIT /b
diff --git a/node_modules/.bin/semver.ps1 b/node_modules/.bin/semver.ps1
new file mode 100644
index 00000000..a3315ffc
--- /dev/null
+++ b/node_modules/.bin/semver.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
+
+$exe=""
+if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
+ # Fix case when both the Windows and Linux builds of Node
+ # are installed in the same directory
+ $exe=".exe"
+}
+$ret=0
+if (Test-Path "$basedir/node$exe") {
+ & "$basedir/node$exe" "$basedir/../semver/bin/semver" $args
+ $ret=$LASTEXITCODE
+} else {
+ & "node$exe" "$basedir/../semver/bin/semver" $args
+ $ret=$LASTEXITCODE
+}
+exit $ret
diff --git a/node_modules/@sindresorhus/is/dist/index.d.ts b/node_modules/@sindresorhus/is/dist/index.d.ts
new file mode 100644
index 00000000..e94d30b7
--- /dev/null
+++ b/node_modules/@sindresorhus/is/dist/index.d.ts
@@ -0,0 +1,132 @@
+///
+///
+///
+///
+///
+declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
+declare type Primitive = null | undefined | string | number | boolean | Symbol;
+export interface ArrayLike {
+ length: number;
+}
+export interface Class {
+ new (...args: any[]): T;
+}
+declare type DomElement = object & {
+ nodeType: 1;
+ nodeName: string;
+};
+declare type NodeStream = object & {
+ pipe: Function;
+};
+export declare const enum TypeName {
+ null = "null",
+ boolean = "boolean",
+ undefined = "undefined",
+ string = "string",
+ number = "number",
+ symbol = "symbol",
+ Function = "Function",
+ GeneratorFunction = "GeneratorFunction",
+ AsyncFunction = "AsyncFunction",
+ Observable = "Observable",
+ Array = "Array",
+ Buffer = "Buffer",
+ Object = "Object",
+ RegExp = "RegExp",
+ Date = "Date",
+ Error = "Error",
+ Map = "Map",
+ Set = "Set",
+ WeakMap = "WeakMap",
+ WeakSet = "WeakSet",
+ Int8Array = "Int8Array",
+ Uint8Array = "Uint8Array",
+ Uint8ClampedArray = "Uint8ClampedArray",
+ Int16Array = "Int16Array",
+ Uint16Array = "Uint16Array",
+ Int32Array = "Int32Array",
+ Uint32Array = "Uint32Array",
+ Float32Array = "Float32Array",
+ Float64Array = "Float64Array",
+ ArrayBuffer = "ArrayBuffer",
+ SharedArrayBuffer = "SharedArrayBuffer",
+ DataView = "DataView",
+ Promise = "Promise",
+ URL = "URL"
+}
+declare function is(value: unknown): TypeName;
+declare namespace is {
+ const undefined: (value: unknown) => value is undefined;
+ const string: (value: unknown) => value is string;
+ const number: (value: unknown) => value is number;
+ const function_: (value: unknown) => value is Function;
+ const null_: (value: unknown) => value is null;
+ const class_: (value: unknown) => value is Class;
+ const boolean: (value: unknown) => value is boolean;
+ const symbol: (value: unknown) => value is Symbol;
+ const numericString: (value: unknown) => boolean;
+ const array: (arg: any) => arg is any[];
+ const buffer: (input: unknown) => input is Buffer;
+ const nullOrUndefined: (value: unknown) => value is null | undefined;
+ const object: (value: unknown) => value is object;
+ const iterable: (value: unknown) => value is IterableIterator;
+ const asyncIterable: (value: unknown) => value is AsyncIterableIterator;
+ const generator: (value: unknown) => value is Generator;
+ const nativePromise: (value: unknown) => value is Promise;
+ const promise: (value: unknown) => value is Promise;
+ const generatorFunction: (value: unknown) => value is GeneratorFunction;
+ const asyncFunction: (value: unknown) => value is Function;
+ const boundFunction: (value: unknown) => value is Function;
+ const regExp: (value: unknown) => value is RegExp;
+ const date: (value: unknown) => value is Date;
+ const error: (value: unknown) => value is Error;
+ const map: (value: unknown) => value is Map;
+ const set: (value: unknown) => value is Set;
+ const weakMap: (value: unknown) => value is WeakMap