diff --git a/backend/.env b/backend/.env
new file mode 100755
index 0000000..2b89305
--- /dev/null
+++ b/backend/.env
@@ -0,0 +1,4 @@
+DB_HOST=mysql
+DB_USER=root
+DB_PASSWORD=D7Ze0lwV9hMrNQHdz1Q8yi0MIQuOO8
+DB_NAME=bikelane
\ No newline at end of file
diff --git a/backend/database.js b/backend/database.js
new file mode 100644
index 0000000..057734c
--- /dev/null
+++ b/backend/database.js
@@ -0,0 +1,142 @@
+import mysql from "mysql2";
+import dotenv from "dotenv";
+import { error } from "console";
+dotenv.config();
+
+// Create a MySQL connection pool using environment variables for configuration
+const pool = mysql
+ .createPool({
+ host: process.env.DB_HOST,
+ user: process.env.DB_USER,
+ password: process.env.DB_PASSWORD,
+ database: process.env.DB_NAME,
+ })
+ .promise();
+
+// Function to authenticate a user by username and password
+export async function loginUser(username, password) {
+ // Query the users table for a matching username and password
+ const [result] = await pool.query(
+ "SELECT * FROM users WHERE username = ? AND password = ?",
+ [username, password]
+ );
+
+ // If a user is found, return success and user data
+ if (result.length > 0) {
+ console.log("User found: ", result[0].username, " ", result[0].id);
+ return { success: true, user: result[0] };
+ } else {
+ // If no user is found, return failure message
+ console.error(`Invalid username or password!; ${result[0]}`);
+ return { success: false, message: "Invalid username or password" };
+ }
+}
+
+// Function to create a new user in the database
+export async function createUser(
+ username,
+ first_name,
+ last_name,
+ password,
+ email
+) {
+ try {
+ // Insert a new user record into the users table
+ const [result] = await pool.query(
+ "INSERT INTO users (username, first_name, last_name, password, email) VALUES (?, ?, ?, ?, ?)",
+ [username, first_name, last_name, password, email]
+ );
+
+ console.log("User created successfully!");
+ return { success: true, message: "User created successfully!" };
+ } catch (error) {
+ // Handle errors during user creation
+ console.log("Error creating user: ", error);
+ return { success: false, message: "Error creating user!" };
+ }
+}
+
+// Function to update an existing user's information
+export async function updateUser(
+ username,
+ first_name,
+ last_name,
+ password,
+ email
+) {
+ try {
+ // Update user details based on username
+ const [result] = await pool.query(
+ "UPDATE users SET first_name = ?, last_name = ?, password = ?, email = ? WHERE username = ?",
+ [first_name, last_name, password, email, username]
+ );
+ const resultOfquery = result.affectedRows;
+
+ // If a user was updated, return success
+ if (resultOfquery > 0) {
+ console.log("User updated successfully!");
+ return {
+ success: true,
+ message: "User updated successfully!",
+ resultOfquery: result,
+ };
+ }
+
+ // If no user was updated, return failure
+ if (resultOfquery === 0) {
+ console.log("Error updating user!");
+ return {
+ success: false,
+ message: "Error updating user!",
+ resultOfquery: null,
+ };
+ }
+ } catch (err) {}
+}
+
+// Function to delete a user from the database
+export async function deleteUser(
+ username,
+ first_name,
+ last_name,
+ password,
+ email
+) {
+ try {
+ // Delete user based on username and password
+ const [result] = await pool.query(
+ "DELETE FROM users WHERE username = ? AND password = ?",
+ [username, password]
+ );
+ const resultOfquery = result.affectedRows;
+
+ // If a user was deleted, return success
+ if (resultOfquery > 0) {
+ console.log("User deleted successfully!");
+ return {
+ success: true,
+ message: "User deleted successfully!",
+ resultOfquery: result,
+ };
+ }
+
+ // If no user was deleted, return failure
+ if (resultOfquery === 0) {
+ console.log("Error deleting user!");
+ return {
+ success: false,
+ message: "Error deleting user!",
+ resultOfquery: null,
+ };
+ }
+ } catch (err) {}
+}
+
+export async function getAllUsers() {
+ try {
+ const [data] = await pool.query("SELECT * FROM users;");
+ return { result: data, success: true };
+ } catch (err) {
+ return { result: err, success: false };
+ }
+}
\ No newline at end of file
diff --git a/backend/package-lock.json b/backend/package-lock.json
index e59197f..9f8afde 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -9,8 +9,11 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "cors": "^2.8.5",
+ "dotenv": "^17.2.0",
"ejs": "^3.1.10",
- "express": "^5.1.0"
+ "express": "^5.1.0",
+ "mysql2": "^3.14.2"
}
},
"node_modules/accepts": {
@@ -47,6 +50,15 @@
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -200,6 +212,19 @@
"node": ">=6.6.0"
}
},
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -217,6 +242,15 @@
}
}
},
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -226,6 +260,18 @@
"node": ">= 0.8"
}
},
+ "node_modules/dotenv": {
+ "version": "17.2.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz",
+ "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -431,6 +477,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -571,6 +626,12 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
"node_modules/jake": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
@@ -589,6 +650,36 @@
"node": ">=10"
}
},
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
+ "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -658,6 +749,38 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/mysql2": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.2.tgz",
+ "integrity": "sha512-YD6mZMeoypmheHT6b2BrVmQFvouEpRICuvPIREulx2OvP1xAxxeqkMQqZSTBefv0PiOBKGYFa2zQtY+gf/4eQw==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.6.3",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
+ "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru-cache": "^7.14.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
@@ -667,6 +790,15 @@
"node": ">= 0.6"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -834,6 +966,11 @@
"node": ">= 18"
}
},
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
@@ -927,6 +1064,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
diff --git a/backend/package.json b/backend/package.json
index a230873..82afbcd 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -12,7 +12,10 @@
"license": "ISC",
"description": "",
"dependencies": {
+ "cors": "^2.8.5",
+ "dotenv": "^17.2.0",
"ejs": "^3.1.10",
- "express": "^5.1.0"
+ "express": "^5.1.0",
+ "mysql2": "^3.14.2"
}
}
diff --git a/backend/server.js b/backend/server.js
index d50e81e..ad39d45 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,15 +1,46 @@
//statics
import express from "express";
+import cors from "cors";
const app = express();
const port = 5002;
+import {
+ loginUser,
+ createUser,
+ updateUser,
+ deleteUser,
+ getAllUsers,
+} from "./database.js";
//view engine ejs
app.set("view engine", "ejs");
+app.use(express.json());
+
+app.use(cors());
+
app.get("/", (req, res) => {
res.render("index.ejs");
});
+app.post("/api/login", async (req, res) => {
+ console.log(req.body);
+
+ loginUser(req.body.username, req.body.password)
+ .then((result) => {
+ if (result.success) {
+ res.status(200).json(result);
+ } else {
+ res.status(401).json(result);
+ }
+ })
+ .catch((err) => {
+ console.error("Error logging in:", err);
+ res
+ .status(500)
+ .json({ success: false, message: "Internal server error" });
+ });
+});
+
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 3d7ded3..9459cc7 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -1,35 +1,66 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import React, { useState } from "react";
+import "./App.css";
function App() {
- const [count, setCount] = useState(0)
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ try {
+ const response = await fetch("http://localhost:5002/api/login", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ username,
+ password,
+ }),
+ });
+
+ if (response.ok) {
+ console.log("Login successful");
+ // Handle successful login here
+ } else {
+ console.log("Login failed");
+ // Handle login error here
+ }
+ } catch (error) {
+ console.error("Login error:", error);
+ }
+ };
return (
<>
-
- Vite + React
-
-
-
- Edit src/App.tsx
and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
+
>
- )
+ );
}
-export default App
+export default App;
diff --git a/client/src/layout/header.tsx b/client/src/layout/header.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
new file mode 100644
index 0000000..e85b9c2
--- /dev/null
+++ b/node_modules/.package-lock.json
@@ -0,0 +1,38 @@
+{
+ "name": "bikelane",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/node_modules/cors/CONTRIBUTING.md b/node_modules/cors/CONTRIBUTING.md
new file mode 100644
index 0000000..591b09a
--- /dev/null
+++ b/node_modules/cors/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# contributing to `cors`
+
+CORS is a node.js package for providing a [connect](http://www.senchalabs.org/connect/)/[express](http://expressjs.com/) middleware that can be used to enable [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) with various options. Learn more about the project in [the README](README.md).
+
+## The CORS Spec
+
+[http://www.w3.org/TR/cors/](http://www.w3.org/TR/cors/)
+
+## Pull Requests Welcome
+
+* Include `'use strict';` in every javascript file.
+* 2 space indentation.
+* Please run the testing steps below before submitting.
+
+## Testing
+
+```bash
+$ npm install
+$ npm test
+```
+
+## Interactive Testing Harness
+
+[http://node-cors-client.herokuapp.com](http://node-cors-client.herokuapp.com)
+
+Related git repositories:
+
+* [https://github.com/TroyGoode/node-cors-server](https://github.com/TroyGoode/node-cors-server)
+* [https://github.com/TroyGoode/node-cors-client](https://github.com/TroyGoode/node-cors-client)
+
+## License
+
+[MIT License](http://www.opensource.org/licenses/mit-license.php)
diff --git a/node_modules/cors/HISTORY.md b/node_modules/cors/HISTORY.md
new file mode 100644
index 0000000..5762bce
--- /dev/null
+++ b/node_modules/cors/HISTORY.md
@@ -0,0 +1,58 @@
+2.8.5 / 2018-11-04
+==================
+
+ * Fix setting `maxAge` option to `0`
+
+2.8.4 / 2017-07-12
+==================
+
+ * Work-around Safari bug in default pre-flight response
+
+2.8.3 / 2017-03-29
+==================
+
+ * Fix error when options delegate missing `methods` option
+
+2.8.2 / 2017-03-28
+==================
+
+ * Fix error when frozen options are passed
+ * Send "Vary: Origin" when using regular expressions
+ * Send "Vary: Access-Control-Request-Headers" when dynamic `allowedHeaders`
+
+2.8.1 / 2016-09-08
+==================
+
+This release only changed documentation.
+
+2.8.0 / 2016-08-23
+==================
+
+ * Add `optionsSuccessStatus` option
+
+2.7.2 / 2016-08-23
+==================
+
+ * Fix error when Node.js running in strict mode
+
+2.7.1 / 2015-05-28
+==================
+
+ * Move module into expressjs organization
+
+2.7.0 / 2015-05-28
+==================
+
+ * Allow array of matching condition as `origin` option
+ * Allow regular expression as `origin` option
+
+2.6.1 / 2015-05-28
+==================
+
+ * Update `license` in package.json
+
+2.6.0 / 2015-04-27
+==================
+
+ * Add `preflightContinue` option
+ * Fix "Vary: Origin" header added for "*"
diff --git a/node_modules/cors/LICENSE b/node_modules/cors/LICENSE
new file mode 100644
index 0000000..fd10c84
--- /dev/null
+++ b/node_modules/cors/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2013 Troy Goode
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/cors/README.md b/node_modules/cors/README.md
new file mode 100644
index 0000000..732b847
--- /dev/null
+++ b/node_modules/cors/README.md
@@ -0,0 +1,243 @@
+# cors
+
+[![NPM Version][npm-image]][npm-url]
+[![NPM Downloads][downloads-image]][downloads-url]
+[![Build Status][travis-image]][travis-url]
+[![Test Coverage][coveralls-image]][coveralls-url]
+
+CORS is a node.js package for providing a [Connect](http://www.senchalabs.org/connect/)/[Express](http://expressjs.com/) middleware that can be used to enable [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) with various options.
+
+**[Follow me (@troygoode) on Twitter!](https://twitter.com/intent/user?screen_name=troygoode)**
+
+* [Installation](#installation)
+* [Usage](#usage)
+ * [Simple Usage](#simple-usage-enable-all-cors-requests)
+ * [Enable CORS for a Single Route](#enable-cors-for-a-single-route)
+ * [Configuring CORS](#configuring-cors)
+ * [Configuring CORS Asynchronously](#configuring-cors-asynchronously)
+ * [Enabling CORS Pre-Flight](#enabling-cors-pre-flight)
+* [Configuration Options](#configuration-options)
+* [Demo](#demo)
+* [License](#license)
+* [Author](#author)
+
+## Installation
+
+This is a [Node.js](https://nodejs.org/en/) module available through the
+[npm registry](https://www.npmjs.com/). Installation is done using the
+[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
+
+```sh
+$ npm install cors
+```
+
+## Usage
+
+### Simple Usage (Enable *All* CORS Requests)
+
+```javascript
+var express = require('express')
+var cors = require('cors')
+var app = express()
+
+app.use(cors())
+
+app.get('/products/:id', function (req, res, next) {
+ res.json({msg: 'This is CORS-enabled for all origins!'})
+})
+
+app.listen(80, function () {
+ console.log('CORS-enabled web server listening on port 80')
+})
+```
+
+### Enable CORS for a Single Route
+
+```javascript
+var express = require('express')
+var cors = require('cors')
+var app = express()
+
+app.get('/products/:id', cors(), function (req, res, next) {
+ res.json({msg: 'This is CORS-enabled for a Single Route'})
+})
+
+app.listen(80, function () {
+ console.log('CORS-enabled web server listening on port 80')
+})
+```
+
+### Configuring CORS
+
+```javascript
+var express = require('express')
+var cors = require('cors')
+var app = express()
+
+var corsOptions = {
+ origin: 'http://example.com',
+ optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
+}
+
+app.get('/products/:id', cors(corsOptions), function (req, res, next) {
+ res.json({msg: 'This is CORS-enabled for only example.com.'})
+})
+
+app.listen(80, function () {
+ console.log('CORS-enabled web server listening on port 80')
+})
+```
+
+### Configuring CORS w/ Dynamic Origin
+
+```javascript
+var express = require('express')
+var cors = require('cors')
+var app = express()
+
+var whitelist = ['http://example1.com', 'http://example2.com']
+var corsOptions = {
+ origin: function (origin, callback) {
+ if (whitelist.indexOf(origin) !== -1) {
+ callback(null, true)
+ } else {
+ callback(new Error('Not allowed by CORS'))
+ }
+ }
+}
+
+app.get('/products/:id', cors(corsOptions), function (req, res, next) {
+ res.json({msg: 'This is CORS-enabled for a whitelisted domain.'})
+})
+
+app.listen(80, function () {
+ console.log('CORS-enabled web server listening on port 80')
+})
+```
+
+If you do not want to block REST tools or server-to-server requests,
+add a `!origin` check in the origin function like so:
+
+```javascript
+var corsOptions = {
+ origin: function (origin, callback) {
+ if (whitelist.indexOf(origin) !== -1 || !origin) {
+ callback(null, true)
+ } else {
+ callback(new Error('Not allowed by CORS'))
+ }
+ }
+}
+```
+
+### Enabling CORS Pre-Flight
+
+Certain CORS requests are considered 'complex' and require an initial
+`OPTIONS` request (called the "pre-flight request"). An example of a
+'complex' CORS request is one that uses an HTTP verb other than
+GET/HEAD/POST (such as DELETE) or that uses custom headers. To enable
+pre-flighting, you must add a new OPTIONS handler for the route you want
+to support:
+
+```javascript
+var express = require('express')
+var cors = require('cors')
+var app = express()
+
+app.options('/products/:id', cors()) // enable pre-flight request for DELETE request
+app.del('/products/:id', cors(), function (req, res, next) {
+ res.json({msg: 'This is CORS-enabled for all origins!'})
+})
+
+app.listen(80, function () {
+ console.log('CORS-enabled web server listening on port 80')
+})
+```
+
+You can also enable pre-flight across-the-board like so:
+
+```javascript
+app.options('*', cors()) // include before other routes
+```
+
+### Configuring CORS Asynchronously
+
+```javascript
+var express = require('express')
+var cors = require('cors')
+var app = express()
+
+var whitelist = ['http://example1.com', 'http://example2.com']
+var corsOptionsDelegate = function (req, callback) {
+ var corsOptions;
+ if (whitelist.indexOf(req.header('Origin')) !== -1) {
+ corsOptions = { origin: true } // reflect (enable) the requested origin in the CORS response
+ } else {
+ corsOptions = { origin: false } // disable CORS for this request
+ }
+ callback(null, corsOptions) // callback expects two parameters: error and options
+}
+
+app.get('/products/:id', cors(corsOptionsDelegate), function (req, res, next) {
+ res.json({msg: 'This is CORS-enabled for a whitelisted domain.'})
+})
+
+app.listen(80, function () {
+ console.log('CORS-enabled web server listening on port 80')
+})
+```
+
+## Configuration Options
+
+* `origin`: Configures the **Access-Control-Allow-Origin** CORS header. Possible values:
+ - `Boolean` - set `origin` to `true` to reflect the [request origin](http://tools.ietf.org/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS.
+ - `String` - set `origin` to a specific origin. For example if you set it to `"http://example.com"` only requests from "http://example.com" will be allowed.
+ - `RegExp` - set `origin` to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern `/example\.com$/` will reflect any request that is coming from an origin ending with "example.com".
+ - `Array` - set `origin` to an array of valid origins. Each origin can be a `String` or a `RegExp`. For example `["http://example1.com", /\.example2\.com$/]` will accept any request from "http://example1.com" or from a subdomain of "example2.com".
+ - `Function` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (which expects the signature `err [object], allow [bool]`) as the second.
+* `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`).
+* `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header.
+* `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed.
+* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted.
+* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted.
+* `preflightContinue`: Pass the CORS preflight response to the next handler.
+* `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`.
+
+The default configuration is the equivalent of:
+
+```json
+{
+ "origin": "*",
+ "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
+ "preflightContinue": false,
+ "optionsSuccessStatus": 204
+}
+```
+
+For details on the effect of each CORS header, read [this](http://www.html5rocks.com/en/tutorials/cors/) article on HTML5 Rocks.
+
+## Demo
+
+A demo that illustrates CORS working (and not working) using jQuery is available here: [http://node-cors-client.herokuapp.com/](http://node-cors-client.herokuapp.com/)
+
+Code for that demo can be found here:
+
+* Client: [https://github.com/TroyGoode/node-cors-client](https://github.com/TroyGoode/node-cors-client)
+* Server: [https://github.com/TroyGoode/node-cors-server](https://github.com/TroyGoode/node-cors-server)
+
+## License
+
+[MIT License](http://www.opensource.org/licenses/mit-license.php)
+
+## Author
+
+[Troy Goode](https://github.com/TroyGoode) ([troygoode@gmail.com](mailto:troygoode@gmail.com))
+
+[coveralls-image]: https://img.shields.io/coveralls/expressjs/cors/master.svg
+[coveralls-url]: https://coveralls.io/r/expressjs/cors?branch=master
+[downloads-image]: https://img.shields.io/npm/dm/cors.svg
+[downloads-url]: https://npmjs.org/package/cors
+[npm-image]: https://img.shields.io/npm/v/cors.svg
+[npm-url]: https://npmjs.org/package/cors
+[travis-image]: https://img.shields.io/travis/expressjs/cors/master.svg
+[travis-url]: https://travis-ci.org/expressjs/cors
diff --git a/node_modules/cors/lib/index.js b/node_modules/cors/lib/index.js
new file mode 100644
index 0000000..5475aec
--- /dev/null
+++ b/node_modules/cors/lib/index.js
@@ -0,0 +1,238 @@
+(function () {
+
+ 'use strict';
+
+ var assign = require('object-assign');
+ var vary = require('vary');
+
+ var defaults = {
+ origin: '*',
+ methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
+ preflightContinue: false,
+ optionsSuccessStatus: 204
+ };
+
+ function isString(s) {
+ return typeof s === 'string' || s instanceof String;
+ }
+
+ function isOriginAllowed(origin, allowedOrigin) {
+ if (Array.isArray(allowedOrigin)) {
+ for (var i = 0; i < allowedOrigin.length; ++i) {
+ if (isOriginAllowed(origin, allowedOrigin[i])) {
+ return true;
+ }
+ }
+ return false;
+ } else if (isString(allowedOrigin)) {
+ return origin === allowedOrigin;
+ } else if (allowedOrigin instanceof RegExp) {
+ return allowedOrigin.test(origin);
+ } else {
+ return !!allowedOrigin;
+ }
+ }
+
+ function configureOrigin(options, req) {
+ var requestOrigin = req.headers.origin,
+ headers = [],
+ isAllowed;
+
+ if (!options.origin || options.origin === '*') {
+ // allow any origin
+ headers.push([{
+ key: 'Access-Control-Allow-Origin',
+ value: '*'
+ }]);
+ } else if (isString(options.origin)) {
+ // fixed origin
+ headers.push([{
+ key: 'Access-Control-Allow-Origin',
+ value: options.origin
+ }]);
+ headers.push([{
+ key: 'Vary',
+ value: 'Origin'
+ }]);
+ } else {
+ isAllowed = isOriginAllowed(requestOrigin, options.origin);
+ // reflect origin
+ headers.push([{
+ key: 'Access-Control-Allow-Origin',
+ value: isAllowed ? requestOrigin : false
+ }]);
+ headers.push([{
+ key: 'Vary',
+ value: 'Origin'
+ }]);
+ }
+
+ return headers;
+ }
+
+ function configureMethods(options) {
+ var methods = options.methods;
+ if (methods.join) {
+ methods = options.methods.join(','); // .methods is an array, so turn it into a string
+ }
+ return {
+ key: 'Access-Control-Allow-Methods',
+ value: methods
+ };
+ }
+
+ function configureCredentials(options) {
+ if (options.credentials === true) {
+ return {
+ key: 'Access-Control-Allow-Credentials',
+ value: 'true'
+ };
+ }
+ return null;
+ }
+
+ function configureAllowedHeaders(options, req) {
+ var allowedHeaders = options.allowedHeaders || options.headers;
+ var headers = [];
+
+ if (!allowedHeaders) {
+ allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers
+ headers.push([{
+ key: 'Vary',
+ value: 'Access-Control-Request-Headers'
+ }]);
+ } else if (allowedHeaders.join) {
+ allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string
+ }
+ if (allowedHeaders && allowedHeaders.length) {
+ headers.push([{
+ key: 'Access-Control-Allow-Headers',
+ value: allowedHeaders
+ }]);
+ }
+
+ return headers;
+ }
+
+ function configureExposedHeaders(options) {
+ var headers = options.exposedHeaders;
+ if (!headers) {
+ return null;
+ } else if (headers.join) {
+ headers = headers.join(','); // .headers is an array, so turn it into a string
+ }
+ if (headers && headers.length) {
+ return {
+ key: 'Access-Control-Expose-Headers',
+ value: headers
+ };
+ }
+ return null;
+ }
+
+ function configureMaxAge(options) {
+ var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString()
+ if (maxAge && maxAge.length) {
+ return {
+ key: 'Access-Control-Max-Age',
+ value: maxAge
+ };
+ }
+ return null;
+ }
+
+ function applyHeaders(headers, res) {
+ for (var i = 0, n = headers.length; i < n; i++) {
+ var header = headers[i];
+ if (header) {
+ if (Array.isArray(header)) {
+ applyHeaders(header, res);
+ } else if (header.key === 'Vary' && header.value) {
+ vary(res, header.value);
+ } else if (header.value) {
+ res.setHeader(header.key, header.value);
+ }
+ }
+ }
+ }
+
+ function cors(options, req, res, next) {
+ var headers = [],
+ method = req.method && req.method.toUpperCase && req.method.toUpperCase();
+
+ if (method === 'OPTIONS') {
+ // preflight
+ headers.push(configureOrigin(options, req));
+ headers.push(configureCredentials(options, req));
+ headers.push(configureMethods(options, req));
+ headers.push(configureAllowedHeaders(options, req));
+ headers.push(configureMaxAge(options, req));
+ headers.push(configureExposedHeaders(options, req));
+ applyHeaders(headers, res);
+
+ if (options.preflightContinue) {
+ next();
+ } else {
+ // Safari (and potentially other browsers) need content-length 0,
+ // for 204 or they just hang waiting for a body
+ res.statusCode = options.optionsSuccessStatus;
+ res.setHeader('Content-Length', '0');
+ res.end();
+ }
+ } else {
+ // actual response
+ headers.push(configureOrigin(options, req));
+ headers.push(configureCredentials(options, req));
+ headers.push(configureExposedHeaders(options, req));
+ applyHeaders(headers, res);
+ next();
+ }
+ }
+
+ function middlewareWrapper(o) {
+ // if options are static (either via defaults or custom options passed in), wrap in a function
+ var optionsCallback = null;
+ if (typeof o === 'function') {
+ optionsCallback = o;
+ } else {
+ optionsCallback = function (req, cb) {
+ cb(null, o);
+ };
+ }
+
+ return function corsMiddleware(req, res, next) {
+ optionsCallback(req, function (err, options) {
+ if (err) {
+ next(err);
+ } else {
+ var corsOptions = assign({}, defaults, options);
+ var originCallback = null;
+ if (corsOptions.origin && typeof corsOptions.origin === 'function') {
+ originCallback = corsOptions.origin;
+ } else if (corsOptions.origin) {
+ originCallback = function (origin, cb) {
+ cb(null, corsOptions.origin);
+ };
+ }
+
+ if (originCallback) {
+ originCallback(req.headers.origin, function (err2, origin) {
+ if (err2 || !origin) {
+ next(err2);
+ } else {
+ corsOptions.origin = origin;
+ cors(corsOptions, req, res, next);
+ }
+ });
+ } else {
+ next();
+ }
+ }
+ });
+ };
+ }
+
+ // can pass either an options hash, an options delegate, or nothing
+ module.exports = middlewareWrapper;
+
+}());
diff --git a/node_modules/cors/package.json b/node_modules/cors/package.json
new file mode 100644
index 0000000..ff37d98
--- /dev/null
+++ b/node_modules/cors/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "cors",
+ "description": "Node.js CORS middleware",
+ "version": "2.8.5",
+ "author": "Troy Goode (https://github.com/troygoode/)",
+ "license": "MIT",
+ "keywords": [
+ "cors",
+ "express",
+ "connect",
+ "middleware"
+ ],
+ "repository": "expressjs/cors",
+ "main": "./lib/index.js",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "devDependencies": {
+ "after": "0.8.2",
+ "eslint": "2.13.1",
+ "express": "4.16.3",
+ "mocha": "5.2.0",
+ "nyc": "13.1.0",
+ "supertest": "3.3.0"
+ },
+ "files": [
+ "lib/index.js",
+ "CONTRIBUTING.md",
+ "HISTORY.md",
+ "LICENSE",
+ "README.md"
+ ],
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "scripts": {
+ "test": "npm run lint && nyc --reporter=html --reporter=text mocha --require test/support/env",
+ "lint": "eslint lib test"
+ }
+}
diff --git a/node_modules/object-assign/index.js b/node_modules/object-assign/index.js
new file mode 100644
index 0000000..0930cf8
--- /dev/null
+++ b/node_modules/object-assign/index.js
@@ -0,0 +1,90 @@
+/*
+object-assign
+(c) Sindre Sorhus
+@license MIT
+*/
+
+'use strict';
+/* eslint-disable no-unused-vars */
+var getOwnPropertySymbols = Object.getOwnPropertySymbols;
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+var propIsEnumerable = Object.prototype.propertyIsEnumerable;
+
+function toObject(val) {
+ if (val === null || val === undefined) {
+ throw new TypeError('Object.assign cannot be called with null or undefined');
+ }
+
+ return Object(val);
+}
+
+function shouldUseNative() {
+ try {
+ if (!Object.assign) {
+ return false;
+ }
+
+ // Detect buggy property enumeration order in older V8 versions.
+
+ // https://bugs.chromium.org/p/v8/issues/detail?id=4118
+ var test1 = new String('abc'); // eslint-disable-line no-new-wrappers
+ test1[5] = 'de';
+ if (Object.getOwnPropertyNames(test1)[0] === '5') {
+ return false;
+ }
+
+ // https://bugs.chromium.org/p/v8/issues/detail?id=3056
+ var test2 = {};
+ for (var i = 0; i < 10; i++) {
+ test2['_' + String.fromCharCode(i)] = i;
+ }
+ var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
+ return test2[n];
+ });
+ if (order2.join('') !== '0123456789') {
+ return false;
+ }
+
+ // https://bugs.chromium.org/p/v8/issues/detail?id=3056
+ var test3 = {};
+ 'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
+ test3[letter] = letter;
+ });
+ if (Object.keys(Object.assign({}, test3)).join('') !==
+ 'abcdefghijklmnopqrst') {
+ return false;
+ }
+
+ return true;
+ } catch (err) {
+ // We don't expect any of the above to throw, but better to be safe.
+ return false;
+ }
+}
+
+module.exports = shouldUseNative() ? Object.assign : function (target, source) {
+ var from;
+ var to = toObject(target);
+ var symbols;
+
+ for (var s = 1; s < arguments.length; s++) {
+ from = Object(arguments[s]);
+
+ for (var key in from) {
+ if (hasOwnProperty.call(from, key)) {
+ to[key] = from[key];
+ }
+ }
+
+ if (getOwnPropertySymbols) {
+ symbols = getOwnPropertySymbols(from);
+ for (var i = 0; i < symbols.length; i++) {
+ if (propIsEnumerable.call(from, symbols[i])) {
+ to[symbols[i]] = from[symbols[i]];
+ }
+ }
+ }
+ }
+
+ return to;
+};
diff --git a/node_modules/object-assign/license b/node_modules/object-assign/license
new file mode 100644
index 0000000..654d0bf
--- /dev/null
+++ b/node_modules/object-assign/license
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Sindre Sorhus (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/node_modules/object-assign/package.json b/node_modules/object-assign/package.json
new file mode 100644
index 0000000..503eb1e
--- /dev/null
+++ b/node_modules/object-assign/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "object-assign",
+ "version": "4.1.1",
+ "description": "ES2015 `Object.assign()` ponyfill",
+ "license": "MIT",
+ "repository": "sindresorhus/object-assign",
+ "author": {
+ "name": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "scripts": {
+ "test": "xo && ava",
+ "bench": "matcha bench.js"
+ },
+ "files": [
+ "index.js"
+ ],
+ "keywords": [
+ "object",
+ "assign",
+ "extend",
+ "properties",
+ "es2015",
+ "ecmascript",
+ "harmony",
+ "ponyfill",
+ "prollyfill",
+ "polyfill",
+ "shim",
+ "browser"
+ ],
+ "devDependencies": {
+ "ava": "^0.16.0",
+ "lodash": "^4.16.4",
+ "matcha": "^0.7.0",
+ "xo": "^0.16.0"
+ }
+}
diff --git a/node_modules/object-assign/readme.md b/node_modules/object-assign/readme.md
new file mode 100644
index 0000000..1be09d3
--- /dev/null
+++ b/node_modules/object-assign/readme.md
@@ -0,0 +1,61 @@
+# object-assign [](https://travis-ci.org/sindresorhus/object-assign)
+
+> ES2015 [`Object.assign()`](http://www.2ality.com/2014/01/object-assign.html) [ponyfill](https://ponyfill.com)
+
+
+## Use the built-in
+
+Node.js 4 and up, as well as every evergreen browser (Chrome, Edge, Firefox, Opera, Safari),
+support `Object.assign()` :tada:. If you target only those environments, then by all
+means, use `Object.assign()` instead of this package.
+
+
+## Install
+
+```
+$ npm install --save object-assign
+```
+
+
+## Usage
+
+```js
+const objectAssign = require('object-assign');
+
+objectAssign({foo: 0}, {bar: 1});
+//=> {foo: 0, bar: 1}
+
+// multiple sources
+objectAssign({foo: 0}, {bar: 1}, {baz: 2});
+//=> {foo: 0, bar: 1, baz: 2}
+
+// overwrites equal keys
+objectAssign({foo: 0}, {foo: 1}, {foo: 2});
+//=> {foo: 2}
+
+// ignores null and undefined sources
+objectAssign({foo: 0}, null, {bar: 1}, undefined);
+//=> {foo: 0, bar: 1}
+```
+
+
+## API
+
+### objectAssign(target, [source, ...])
+
+Assigns enumerable own properties of `source` objects to the `target` object and returns the `target` object. Additional `source` objects will overwrite previous ones.
+
+
+## Resources
+
+- [ES2015 spec - Object.assign](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign)
+
+
+## Related
+
+- [deep-assign](https://github.com/sindresorhus/deep-assign) - Recursive `Object.assign()`
+
+
+## License
+
+MIT © [Sindre Sorhus](https://sindresorhus.com)
diff --git a/node_modules/vary/HISTORY.md b/node_modules/vary/HISTORY.md
new file mode 100644
index 0000000..f6cbcf7
--- /dev/null
+++ b/node_modules/vary/HISTORY.md
@@ -0,0 +1,39 @@
+1.1.2 / 2017-09-23
+==================
+
+ * perf: improve header token parsing speed
+
+1.1.1 / 2017-03-20
+==================
+
+ * perf: hoist regular expression
+
+1.1.0 / 2015-09-29
+==================
+
+ * Only accept valid field names in the `field` argument
+ - Ensures the resulting string is a valid HTTP header value
+
+1.0.1 / 2015-07-08
+==================
+
+ * Fix setting empty header from empty `field`
+ * perf: enable strict mode
+ * perf: remove argument reassignments
+
+1.0.0 / 2014-08-10
+==================
+
+ * Accept valid `Vary` header string as `field`
+ * Add `vary.append` for low-level string manipulation
+ * Move to `jshttp` orgainzation
+
+0.1.0 / 2014-06-05
+==================
+
+ * Support array of fields to set
+
+0.0.0 / 2014-06-04
+==================
+
+ * Initial release
diff --git a/node_modules/vary/LICENSE b/node_modules/vary/LICENSE
new file mode 100644
index 0000000..84441fb
--- /dev/null
+++ b/node_modules/vary/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2014-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/vary/README.md b/node_modules/vary/README.md
new file mode 100644
index 0000000..cc000b3
--- /dev/null
+++ b/node_modules/vary/README.md
@@ -0,0 +1,101 @@
+# vary
+
+[![NPM Version][npm-image]][npm-url]
+[![NPM Downloads][downloads-image]][downloads-url]
+[![Node.js Version][node-version-image]][node-version-url]
+[![Build Status][travis-image]][travis-url]
+[![Test Coverage][coveralls-image]][coveralls-url]
+
+Manipulate the HTTP Vary header
+
+## Installation
+
+This is a [Node.js](https://nodejs.org/en/) module available through the
+[npm registry](https://www.npmjs.com/). Installation is done using the
+[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
+
+```sh
+$ npm install vary
+```
+
+## API
+
+
+
+```js
+var vary = require('vary')
+```
+
+### vary(res, field)
+
+Adds the given header `field` to the `Vary` response header of `res`.
+This can be a string of a single field, a string of a valid `Vary`
+header, or an array of multiple fields.
+
+This will append the header if not already listed, otherwise leaves
+it listed in the current location.
+
+
+
+```js
+// Append "Origin" to the Vary header of the response
+vary(res, 'Origin')
+```
+
+### vary.append(header, field)
+
+Adds the given header `field` to the `Vary` response header string `header`.
+This can be a string of a single field, a string of a valid `Vary` header,
+or an array of multiple fields.
+
+This will append the header if not already listed, otherwise leaves
+it listed in the current location. The new header string is returned.
+
+
+
+```js
+// Get header string appending "Origin" to "Accept, User-Agent"
+vary.append('Accept, User-Agent', 'Origin')
+```
+
+## Examples
+
+### Updating the Vary header when content is based on it
+
+```js
+var http = require('http')
+var vary = require('vary')
+
+http.createServer(function onRequest (req, res) {
+ // about to user-agent sniff
+ vary(res, 'User-Agent')
+
+ var ua = req.headers['user-agent'] || ''
+ var isMobile = /mobi|android|touch|mini/i.test(ua)
+
+ // serve site, depending on isMobile
+ res.setHeader('Content-Type', 'text/html')
+ res.end('You are (probably) ' + (isMobile ? '' : 'not ') + 'a mobile user')
+})
+```
+
+## Testing
+
+```sh
+$ npm test
+```
+
+## License
+
+[MIT](LICENSE)
+
+[npm-image]: https://img.shields.io/npm/v/vary.svg
+[npm-url]: https://npmjs.org/package/vary
+[node-version-image]: https://img.shields.io/node/v/vary.svg
+[node-version-url]: https://nodejs.org/en/download
+[travis-image]: https://img.shields.io/travis/jshttp/vary/master.svg
+[travis-url]: https://travis-ci.org/jshttp/vary
+[coveralls-image]: https://img.shields.io/coveralls/jshttp/vary/master.svg
+[coveralls-url]: https://coveralls.io/r/jshttp/vary
+[downloads-image]: https://img.shields.io/npm/dm/vary.svg
+[downloads-url]: https://npmjs.org/package/vary
diff --git a/node_modules/vary/index.js b/node_modules/vary/index.js
new file mode 100644
index 0000000..5b5e741
--- /dev/null
+++ b/node_modules/vary/index.js
@@ -0,0 +1,149 @@
+/*!
+ * vary
+ * Copyright(c) 2014-2017 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+'use strict'
+
+/**
+ * Module exports.
+ */
+
+module.exports = vary
+module.exports.append = append
+
+/**
+ * RegExp to match field-name in RFC 7230 sec 3.2
+ *
+ * field-name = token
+ * token = 1*tchar
+ * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
+ * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
+ * / DIGIT / ALPHA
+ * ; any VCHAR, except delimiters
+ */
+
+var FIELD_NAME_REGEXP = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/
+
+/**
+ * Append a field to a vary header.
+ *
+ * @param {String} header
+ * @param {String|Array} field
+ * @return {String}
+ * @public
+ */
+
+function append (header, field) {
+ if (typeof header !== 'string') {
+ throw new TypeError('header argument is required')
+ }
+
+ if (!field) {
+ throw new TypeError('field argument is required')
+ }
+
+ // get fields array
+ var fields = !Array.isArray(field)
+ ? parse(String(field))
+ : field
+
+ // assert on invalid field names
+ for (var j = 0; j < fields.length; j++) {
+ if (!FIELD_NAME_REGEXP.test(fields[j])) {
+ throw new TypeError('field argument contains an invalid header name')
+ }
+ }
+
+ // existing, unspecified vary
+ if (header === '*') {
+ return header
+ }
+
+ // enumerate current values
+ var val = header
+ var vals = parse(header.toLowerCase())
+
+ // unspecified vary
+ if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) {
+ return '*'
+ }
+
+ for (var i = 0; i < fields.length; i++) {
+ var fld = fields[i].toLowerCase()
+
+ // append value (case-preserving)
+ if (vals.indexOf(fld) === -1) {
+ vals.push(fld)
+ val = val
+ ? val + ', ' + fields[i]
+ : fields[i]
+ }
+ }
+
+ return val
+}
+
+/**
+ * Parse a vary header into an array.
+ *
+ * @param {String} header
+ * @return {Array}
+ * @private
+ */
+
+function parse (header) {
+ var end = 0
+ var list = []
+ var start = 0
+
+ // gather tokens
+ for (var i = 0, len = header.length; i < len; i++) {
+ switch (header.charCodeAt(i)) {
+ case 0x20: /* */
+ if (start === end) {
+ start = end = i + 1
+ }
+ break
+ case 0x2c: /* , */
+ list.push(header.substring(start, end))
+ start = end = i + 1
+ break
+ default:
+ end = i + 1
+ break
+ }
+ }
+
+ // final token
+ list.push(header.substring(start, end))
+
+ return list
+}
+
+/**
+ * Mark that a request is varied on a header field.
+ *
+ * @param {Object} res
+ * @param {String|Array} field
+ * @public
+ */
+
+function vary (res, field) {
+ if (!res || !res.getHeader || !res.setHeader) {
+ // quack quack
+ throw new TypeError('res argument is required')
+ }
+
+ // get existing header
+ var val = res.getHeader('Vary') || ''
+ var header = Array.isArray(val)
+ ? val.join(', ')
+ : String(val)
+
+ // set new header
+ if ((val = append(header, field))) {
+ res.setHeader('Vary', val)
+ }
+}
diff --git a/node_modules/vary/package.json b/node_modules/vary/package.json
new file mode 100644
index 0000000..028f72a
--- /dev/null
+++ b/node_modules/vary/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "vary",
+ "description": "Manipulate the HTTP Vary header",
+ "version": "1.1.2",
+ "author": "Douglas Christopher Wilson ",
+ "license": "MIT",
+ "keywords": [
+ "http",
+ "res",
+ "vary"
+ ],
+ "repository": "jshttp/vary",
+ "devDependencies": {
+ "beautify-benchmark": "0.2.4",
+ "benchmark": "2.1.4",
+ "eslint": "3.19.0",
+ "eslint-config-standard": "10.2.1",
+ "eslint-plugin-import": "2.7.0",
+ "eslint-plugin-markdown": "1.0.0-beta.6",
+ "eslint-plugin-node": "5.1.1",
+ "eslint-plugin-promise": "3.5.0",
+ "eslint-plugin-standard": "3.0.1",
+ "istanbul": "0.4.5",
+ "mocha": "2.5.3",
+ "supertest": "1.1.0"
+ },
+ "files": [
+ "HISTORY.md",
+ "LICENSE",
+ "README.md",
+ "index.js"
+ ],
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "scripts": {
+ "bench": "node benchmark/index.js",
+ "lint": "eslint --plugin markdown --ext js,md .",
+ "test": "mocha --reporter spec --bail --check-leaks test/",
+ "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
+ "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
+ }
+}
diff --git a/scheme.sql b/scheme.sql
new file mode 100644
index 0000000..cb6ba1b
--- /dev/null
+++ b/scheme.sql
@@ -0,0 +1,17 @@
+-- Table structure for the database
+CREATE TABLE users (
+ id INT PRIMARY KEY AUTO_INCREMENT,
+ username VARCHAR(50) NOT NULL UNIQUE,
+ first_name VARCHAR(50) NOT NULL,
+ last_name VARCHAR(50) NOT NULL,
+ email VARCHAR(100) NOT NULL UNIQUE,
+ password VARCHAR(255) NOT NULL,
+ created TIMESTAMP NOT NULL DEFAULT NOW()
+);
+
+-- Mock data for users
+INSERT INTO users (username, first_name, last_name, email, password)
+VALUES
+('test1', 'John', 'Doe', 'jdoe@example.com', '1test'),
+('t', 'John', 'Doe', 'd@example.com', 'g'),
+('test2', 'Alice', 'Smith', 'asmith@example.com', '2test');
\ No newline at end of file