From d2ad32e6d67f1851d7a893bb70af6678520719f4 Mon Sep 17 00:00:00 2001 From: newtbot Date: Sun, 21 Jan 2024 03:58:03 +0800 Subject: [PATCH] added api page added login added api.ejs added middleware for authorization check --- api.MD | 2 +- consumerWebsite/database/model/apiKeyModel.js | 1 + consumerWebsite/database/model/userModel.js | 20 + consumerWebsite/functions/apiDatabase.js | 144 ++--- consumerWebsite/functions/bcrypt.js | 3 + consumerWebsite/middleware/authChecker.js | 34 +- consumerWebsite/modules/app.js | 8 +- consumerWebsite/public/css/all.css | 12 + consumerWebsite/public/css/api.css | 526 ++++++++++++++++++ consumerWebsite/public/css/style.css | 15 + consumerWebsite/public/images/apilogo.png | Bin 0 -> 24374 bytes consumerWebsite/public/js/api.js | 88 +++ consumerWebsite/public/js/app.js | 61 +- consumerWebsite/public/profile.html | 193 ------- consumerWebsite/routes/api_routes.js | 6 +- consumerWebsite/routes/auth.js | 56 ++ consumerWebsite/routes/render.js | 10 + consumerWebsite/routes/user.js | 128 ++--- consumerWebsite/views/api.ejs | 160 ++++++ consumerWebsite/views/bot.ejs | 1 + consumerWebsite/views/index.ejs | 1 + consumerWebsite/views/logintop.ejs | 21 +- consumerWebsite/views/profile.ejs | 64 +++ consumerWebsite/views/signuplogin.ejs | 11 +- consumerWebsite/views/top.ejs | 100 ++-- .../public/js/contact.js => contact.js | 3 +- webserver/middleware/apiKey.js | 2 +- 27 files changed, 1229 insertions(+), 441 deletions(-) create mode 100644 consumerWebsite/public/css/api.css create mode 100644 consumerWebsite/public/images/apilogo.png create mode 100644 consumerWebsite/public/js/api.js delete mode 100644 consumerWebsite/public/profile.html create mode 100644 consumerWebsite/routes/auth.js create mode 100644 consumerWebsite/views/api.ejs create mode 100644 consumerWebsite/views/profile.ejs rename consumerWebsite/public/js/contact.js => contact.js (94%) diff --git a/api.MD b/api.MD index 1100817..b655bed 100644 --- a/api.MD +++ b/api.MD @@ -174,4 +174,4 @@ http://localhost/api/v0/sensor-data/data?week=1&sensorid=1&locationid=1&page=2&p curl localhost/api/v0/user/register -H "Content-Type: application/json" -X POST -d '{"username": "testuser123", "password": "thisisthesystemuserpasswordnoob", "email": "testuser123@ecosaver.com", "address": "Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}' -curl localhost/api/v0/apikey/new -H "Content-Type: application/json" -X POST -d '{"userid": "2", "permission": "canRead"}' +curl localhost/api/v0/apikey/new -H "Content-Type: application/json" -X POST -d '{"userid": "5", "permission": "canRead"}' diff --git a/consumerWebsite/database/model/apiKeyModel.js b/consumerWebsite/database/model/apiKeyModel.js index f644a14..2604d2f 100644 --- a/consumerWebsite/database/model/apiKeyModel.js +++ b/consumerWebsite/database/model/apiKeyModel.js @@ -61,6 +61,7 @@ const apikeyModel = sequelize.define( timestamps: true, } ); +apikeyModel.belongsTo(userModel); module.exports = { apikeyModel }; diff --git a/consumerWebsite/database/model/userModel.js b/consumerWebsite/database/model/userModel.js index 561890c..fb4cbc2 100644 --- a/consumerWebsite/database/model/userModel.js +++ b/consumerWebsite/database/model/userModel.js @@ -19,6 +19,26 @@ const userModel = sequelize.define( isNumeric: true, }, }, + firstname: { + type: DataTypes.STRING, + allowNull: false, + length: 60, + validate: { + notEmpty: true, + len: [1, 60], + isAlpha: true, // will only allow letters + }, + }, + lastname: { + type: DataTypes.STRING, + allowNull: false, + length: 60, + validate: { + notEmpty: true, + len: [1, 60], + isAlpha: true, // will only allow letters + }, + }, username: { type: DataTypes.STRING, allowNull: false, diff --git a/consumerWebsite/functions/apiDatabase.js b/consumerWebsite/functions/apiDatabase.js index fcee670..f011846 100644 --- a/consumerWebsite/functions/apiDatabase.js +++ b/consumerWebsite/functions/apiDatabase.js @@ -3,45 +3,58 @@ const { apikeyModel } = require("../database/model/apikeyModel.js"); const { userModel } = require("../database/model/userModel.js"); const { Op, Sequelize } = require("sequelize"); const { generateUUID } = require("../functions/generateUUID.js"); -const { hashPassword , comparePassword , hashAPIKey } = require("../functions/bcrypt.js"); +const { + hashPassword, + comparePassword, + hashAPIKey, +} = require("../functions/bcrypt.js"); -//helper function +//getuser +//api/v0/user/me +async function getUserID(userid) { + //console.log(userid); + //console.log(userid.id); + let userRes = await userModel.findByPk(userid.id, { + attributes: { + exclude: ["password"], + }, + }); + + if (!userRes) return false; + return userRes; + +} -//api/v0/user/register + +//api/v0/auth/register /* Registering new user 1) req.body is taken from html form or wtv 2) bcrpyt and hash the password on the server side 3) pass to db */ async function addUser(user) { - console.log(user); //hash password let hash = await hashPassword(user.password); - const addRes = await userModel.create({ + const addRes = await userModel.create({ + firstname: user.firstname, + lastname: user.lastname, username: user.username, password: hash, email: user.email, address: user.address, phone: user.phone, }); - if (addRes){ - return true; - } - else{ - return false; - } -} - -//add token to db -async function addToken(userid , token) { - console.log(userid); - console.log(token); - + if (addRes) { + return true; + } else { + return false; + } } +//api/v0/auth/login async function loginUser(user) { //look up username or email in db const userRes = await userModel.findOne({ @@ -55,44 +68,19 @@ async function loginUser(user) { }, ], }, - }) - //if user exists - if (userRes){ - //compare password - let match = await comparePassword(user.password, userRes.password); - if (match){ - console.log(userRes.id); - console.log(userRes.username); + }); + // Make sure user exists + if (!userRes) return false; + + // Compare passwords + let match = await comparePassword(user.password, userRes.password); + if (!match) return false; + //console.log('loginUser', userRes.id, userRes.username); - //generate token - let token = await generateUUID(); + //generate token + let token = await addAPIKey(userRes.id, "auto-generated"); - //add to db - addToken(userRes.id, token); - - - //sucessful login - /* - 1) generate token - 2) store in db and localstorage (maybe hash it?) - 3) return userid and username and token and store in localstorage - */ - return { token: token, userid: userRes.id, username: userRes.username }; - } - else { - return false; - } - } - - else{ - return false; - } -} - - -async function getAPIKey() { - const apikey = await apikeyModel.findAll(); - return apikey; + return { token: token, userid: userRes.id, username: userRes.username }; } /* @@ -103,27 +91,47 @@ async function getAPIKey() { 5) you give the user rowid-uuidv4 6) store in database */ + +//can be used for api key or token. Both are the same logic async function addAPIKey(userId, permission) { - let token = await generateUUID(); - let usertoken = userId + "-" + token; - let apikey = await hashAPIKey(token); + let hashtoken = await generateUUID(); + let apikey = await hashAPIKey(hashtoken); - console.log(token); - console.log(apikey); + let token = await apikeyModel.create({ + userid: userId, + apikey: apikey, + permission: permission, + }); - await apikeyModel.create({ - userid: userId, - apikey: apikey, - permission: permission - }); - - - //user token with - - return usertoken; + //user token with - tokenid is table id + return token.id + "-" + hashtoken; } +//api/v0/user/logout +async function deleteUserToken(token) { + //get row id + let splitAuthToken = token.split("-"); + let rowid = splitAuthToken[0]; + + //console.log(rowid); + + //delete from db + let delRes = await apikeyModel.destroy({ + where: { + id: rowid, + }, + }); + + if (!delRes) return false; + return true; + + + +} module.exports = { + getUserID, addUser, loginUser, addAPIKey, + deleteUserToken, }; diff --git a/consumerWebsite/functions/bcrypt.js b/consumerWebsite/functions/bcrypt.js index bf644e0..f0b72a3 100644 --- a/consumerWebsite/functions/bcrypt.js +++ b/consumerWebsite/functions/bcrypt.js @@ -21,6 +21,8 @@ bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) { }); */ + +//hash for pass or token lol doesnt matter async function hashPassword(password) { return await bcrypt.hash(password, saltRounds); } @@ -29,6 +31,7 @@ async function hashAPIKey(apikey) { return await bcrypt.hash(apikey, saltRounds); } +//can be used to compare password or token async function comparePassword(password, hash) { return await bcrypt.compare(password, hash); } diff --git a/consumerWebsite/middleware/authChecker.js b/consumerWebsite/middleware/authChecker.js index 343cd14..b12ffe2 100644 --- a/consumerWebsite/middleware/authChecker.js +++ b/consumerWebsite/middleware/authChecker.js @@ -1,4 +1,30 @@ -/*v -1) check if token proided by JSON req is valid against db -2) if valid its passed to next() -*/ \ No newline at end of file +const { apikeyModel } = require("../database/model/apiKeyModel"); +const { userModel } = require("../database/model/userModel"); +const { comparePassword } = require("../functions/bcrypt"); + +async function auth(req, res, next){ + try{ + // let user = await Auth.checkToken({token: req.header('auth-token')}); + let authToken = req.header('auth-token'); + let splitAuthToken = authToken.split('-'); + let rowid = splitAuthToken[0]; + let suppliedToken = splitAuthToken.slice(1).join('-'); + + //get from db + let token = await apikeyModel.findByPk(rowid, {include: userModel}); + + //compare + let isMatch = await comparePassword(suppliedToken, token.apikey); + if (!isMatch) return false; + + //else do logic + //pass hashed token to req.token (IMPORTANT ITS NOT PASSED TO CLIENT) + req.token = token + req.user = await token.getUser(); + next(); + }catch(error){ + next(error); + } +} + +module.exports = { auth }; diff --git a/consumerWebsite/modules/app.js b/consumerWebsite/modules/app.js index 94d6c02..3b3d8ba 100644 --- a/consumerWebsite/modules/app.js +++ b/consumerWebsite/modules/app.js @@ -13,17 +13,17 @@ app.set("views", path.join(__dirname, "../views")); app.set("view engine", "ejs"); // Have express server static content( images, CSS, browser JS) from the public -// local folder. app.use(express.static(path.join(__dirname, "../public"))); //middleware logic ( called by next() ) -//add token middeware upon login to validate routes that require token +const auth = require("../middleware/authChecker"); + //route logic -app.use("/api/v0", require("../routes/api_routes")); //consumerWebsite\routes\api_routes.js +app.use("/api/v0", require("../routes/api_routes")); //render logic -app.use("/", require("../routes/render")); //consumerWebsite\routes\render.js +app.use("/", require("../routes/render")); // Catch 404 and forward to error handler. If none of the above routes are // used, this is what will be called. diff --git a/consumerWebsite/public/css/all.css b/consumerWebsite/public/css/all.css index 4e624d8..d3173e3 100644 --- a/consumerWebsite/public/css/all.css +++ b/consumerWebsite/public/css/all.css @@ -3801,3 +3801,15 @@ .fas { font-family: 'Font Awesome 5 Free'; font-weight: 900; } + + .contact-right p { + /* Your CSS styles here */ + color: #4e3914; + font-size: 16px; + /* Add any other styles you need */ +} + + +.card-text { + color: #000000; +} diff --git a/consumerWebsite/public/css/api.css b/consumerWebsite/public/css/api.css new file mode 100644 index 0000000..d3d44b1 --- /dev/null +++ b/consumerWebsite/public/css/api.css @@ -0,0 +1,526 @@ +@charset "utf-8"; + +/* RESET +----------------------------------------------------------------------------------------*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size:100%; + font: inherit; + vertical-align: baseline; +} +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { + display: block; +} +img, embed, object, video { max-width: 100%; } +.ie6 img.full, .ie6 object.full, .ie6 embed, .ie6 video { width: 100%; } + +/* BASE +----------------------------------------------------------------------------------------*/ + +*{ + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + transition: all 0.3s ease; +} +html, +body{ + position:relative; + min-height: 100%; + height: 100%; + -webkit-backface-visibility: hidden; + font-family: 'Roboto', sans-serif; +} +strong{ + font-weight: 500; +} +i{ + font-style: italic; +} +.overflow-hidden{ + position: relative; + overflow: hidden; +} +.content a{ + color: #00a8e3; + text-decoration: none; +} +.content a:hover{ + text-decoration: underline; +} +.scroll-to-link{ + cursor: pointer; +} +p, .content ul, .content ol{ + font-size: 14px; + color: #ffffff; + margin-bottom: 16px; + line-height: 1.6; + font-weight: 300; +} +.content h1:first-child{ + font-size: 1.333em; + color: #034c8f; + padding-top: 2.5em; + text-transform: uppercase; + border-top: 1px solid rgba(255,255,255,0.3); + border-top-width: 0; + margin-top: 0; + margin-bottom: 1.3em; + clear: both; +} + +code, +pre{ + font-family: 'Source Code Pro', monospace; +} +.higlighted{ + background-color: rgba(0,0,0,0.05); + padding: 3px; + border-radius: 3px; +} + +/* LEFT-MENU +----------------------------------------------------------------------------------------*/ + +.left-menu{ + position: fixed; + z-index: 3; + top: 0; + left: 0; + bottom: 0; + width: 300px; + box-sizing: border-box; + background-color: #f4f5f8; + overflow-x: hidden; + font-size: 18px; +} +.left-menu .content-infos { + position: relative; + padding: 12px 13.25%; + margin-bottom: 20px; +} +.left-menu .info { + position: relative; + font-size: 14px; + margin-top: 5px; + color: #777A7A; +} +.left-menu .info b { + font-weight: 500; + color: #034c8f; +} +.content-logo{ + position: relative; + display: block; + width: 100%; + box-sizing: border-box; + padding: 1.425em 11.5%; + padding-right: 0; +} +.content-logo img{ + display: inline-block; + max-width: 70%; + vertical-align: middle; +} +.content-logo span{ + display: inline-block; + margin-left: 10px; + vertical-align: middle; + color: #323F4C; + font-size: 1.1em; +} +.content-menu{ + margin: 2em auto 2em; + padding: 0 0 100px; +} +.content-menu ul{ + list-style: none; + margin: 0; + padding: 0; + line-height: 28px; +} +.content-menu ul li{ + list-style: none; + margin: 0; + padding: 0; + line-height: 0; +} +.content-menu ul li:hover, +.content-menu ul li.active{ + background-color:#DCDEE9; +} +.content-menu ul li:hover a, +.content-menu ul li.active a{ + color: #00a8e3; +} +@media (hover: none) { + .content-menu ul li:not(.active):hover { + background-color: inherit; + } + .content-menu ul li:not(.active):hover a { + color: #777A7A; + } +} +.content-menu ul li a{ + padding: 12px 13.25%; + color: #777A7A; + letter-spacing: 0.025em; + line-height: 1.1; + display: block; + text-transform: capitalize; +} + +/* CONTENT-PAGE +----------------------------------------------------------------------------------------*/ + +.content-page { + position: relative; + box-sizing: border-box; + margin-left: 300px; + z-index: 2; + background-color: #fff; + min-height: 100%; + padding-bottom: 1px; +} +.content-code{ + width: 50%; + position: absolute; + right: 0; + top: 0; + bottom: 0; + background-color: #323f4c; + border-color: #323f4c; +} +.content { + position: relative; + z-index: 30; +} +.content h1, +.content h2, +.content h3, +.content h4, +.content h5, +.content h6, +.content p, +.content table, +.content aside, +.content dl, +.content ul, +.content ol, +.content .central-overflow-x { + margin-right: 50%; + padding: 0 28px; + box-sizing: border-box; + display: block; + max-width: 680px; +} +.content .central-overflow-x { + margin-right: calc(50% + 28px); + margin-left: 28px; + padding: 0; + overflow-y: hidden; + max-width: 100%; + display: block; +} +.content p .central-overflow-x { + margin-right: 0; + margin-left: 0; +} +.break-word { + word-break: break-word; + overflow-wrap: break-word; + word-wrap: break-word; +} +.content ul, +.content ol { + padding: 0 44px; +} +.content h2, +.content h3, +.content h4, +.content h5, +.content h6 { + font-size: 15px; + margin-top: 2.5em; + margin-bottom: 0.8em; + color: #034c8f; + text-transform: uppercase; +} +.content h2{ + font-size: 1.333em; +} +.content h4{ + color: #00a8e3; + margin-top: 0; + text-transform: none; + font-size: 14px; + margin-bottom: 0.2em; +} +.content-page .content p, +.content-page .content pre { + max-width: 680px; +} +.content pre, +.content blockquote { + background-color: #323f4c; + border-color: #323f4c; + color: #fff; + padding: 0 28px 2em; + margin: 0; + width: 50%; + float: right; + clear: right; + box-sizing: border-box; +} +.content pre code, .content pre { + font-size: 12px; + line-height: 1.5; +} +.content blockquote, +.content pre, +.content pre code{ + padding-top: 0; + margin-top: 0; +} +.content pre code{ + margin-top: -2em; +} +.content table { + font-size: 0.825em; + margin-bottom: 1.5em; + border-collapse: collapse; + border-spacing: 0; +} +.content table tr:last-child { + border-bottom: 1px solid #ccc; +} +.content table th { + font-size: 0.925em; + padding: 5px 18px 5px 0; + border-bottom: 1px solid #ccc; + vertical-align: bottom; + text-align: left; + line-height: 1.6; +} +.content table td { + padding: 5px 18px 5px 0; + text-align: left; + vertical-align: top; + line-height: 1.6; + font-family: 'Roboto', sans-serif; + font-weight: 300; + color: #777A7A; +} + + +/* burger-menu-icon +----------------------------------------------------------------------------------------*/ +.burger-menu-icon { + background-color: transparent; + border: none; + cursor: pointer; + display: inline-block; + vertical-align: middle; + padding: 0; + position: absolute; + right: 26px; + top: 26px; + display: none; +} +.burger-menu-icon .line { + fill: none; + stroke: #000; + stroke-width: 6; + transition: stroke-dasharray 600ms cubic-bezier(0.4, 0, 0.2, 1), + stroke-dashoffset 600ms cubic-bezier(0.4, 0, 0.2, 1); +} +.burger-menu-icon .line1 { + stroke-dasharray: 60 207; + stroke-width: 6; +} +.burger-menu-icon .line2 { + stroke-dasharray: 60 60; + stroke-width: 6; +} +.burger-menu-icon .line3 { + stroke-dasharray: 60 207; + stroke-width: 6; +} +html.menu-opened .burger-menu-icon .line1 { + stroke-dasharray: 90 207; + stroke-dashoffset: -134; + stroke-width: 6; +} +html.menu-opened .burger-menu-icon .line2 { + stroke-dasharray: 1 60; + stroke-dashoffset: -30; + stroke-width: 6; +} +html.menu-opened .burger-menu-icon .line3 { + stroke-dasharray: 90 207; + stroke-dashoffset: -134; + stroke-width: 6; +} + + +/* ONE CONTENT COLUMN VERSION +----------------------------------------------------------------------------------------*/ + +body.one-content-column-version .content h1, +body.one-content-column-version .content h2, +body.one-content-column-version .content h3, +body.one-content-column-version .content h4, +body.one-content-column-version .content h5, +body.one-content-column-version .content h6, +body.one-content-column-version .content p, +body.one-content-column-version .content table, +body.one-content-column-version .content ul, +body.one-content-column-version .content ol, +body.one-content-column-version .content aside, +body.one-content-column-version .content dl, +body.one-content-column-version .content ul, +body.one-content-column-version .content ol { + margin-right: 0; + max-width: 100%; +} +body.one-content-column-version .content-page .content p, +body.one-content-column-version .content-page .content pre { + max-width: 100%; +} +body.one-content-column-version .content-page { + background-color: #323f4c; +} +body.one-content-column-version .content h1:first-child, +body.one-content-column-version .content h2, +body.one-content-column-version .content h3, +body.one-content-column-version .content h4, +body.one-content-column-version .content h5, +body.one-content-column-version .content h6 { + color: #59C3C3; +} +body.one-content-column-version p { + color: #D6F0F0; +} +body.one-content-column-version .content table td { + color: #D6F0F0; +} +body.one-content-column-version .content thead { + color: #417179; +} + +/* RESPONSIVE +----------------------------------------------------------------------------------------*/ + +@media only screen and (max-width:980px){ + .content h1, .content h2, .content h3, .content h4, .content h5, .content h6, .content p, .content table, .content ul, .content ol, .content aside, .content dl, .content ul, .content ol { + margin-right: 0; + } + .content .central-overflow-x { + margin: 0; + padding: 0 28px; + } + .content-code{ + display: none; + } + .content pre, .content blockquote { + margin: 20px 0; + padding: 28px; + display: block; + width: auto; + float: none; + } + .content pre code { + margin-top: 0; + } +} + +@media only screen and (max-width:680px){ + html { + scroll-padding-top: 83px; + } + html.menu-opened { + overflow: hidden; + } + .left-menu { + position: relative; + width: auto; + } + .left-menu .content-menu { + position: fixed; + width: 400px; + max-width: 90vw; + z-index: 3; + top: 0; + bottom: 0; + right: -405px; + left: auto; + background-color: #fff; + margin: 0; + overflow-x: hidden; + padding-top: 83px; + padding-bottom: 20px; + } + .left-menu .content-menu ul { + position: relative; + } + .left-menu .mobile-menu-closer { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2; + background-color: rgba(50, 63, 76, .5); + opacity: 0; + visibility: hidden; + } + html.menu-opened .left-menu .mobile-menu-closer { + opacity: 1; + visibility: visible; + } + html.menu-opened .left-menu .content-menu { + right: 0; + } + .left-menu .content-logo { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 4; + background-color: #f4f5f8; + } + .content-logo .logo { + margin-right: 65px; + } + .content-page{ + margin-left: 0; + padding-top: 83px; + } + .burger-menu-icon { + display: block; + } +} + +/* BROWSER AND NON-SEMANTIC STYLING +----------------------------------------------------------------------------------------*/ + +.cf:before, .cf:after { content: ""; display: block; } +.cf:after { clear: both; } +.ie6 .cf { zoom: 1 } \ No newline at end of file diff --git a/consumerWebsite/public/css/style.css b/consumerWebsite/public/css/style.css index d707ecb..ecab1bf 100644 --- a/consumerWebsite/public/css/style.css +++ b/consumerWebsite/public/css/style.css @@ -73,6 +73,21 @@ button.btn-secondary:hover{ color: #ffffff; border-radius: 4.8px; } +.btn-outline-danger{ + width: 78px; + height: 40px; + padding-top: 8px; + font-size: 16px; + +} +.btn-outline-info{ + width: 78px ; + height: 40px ; + padding-top: 8px; + font-size: 16px; + +} + .navbar-expand-lg.top-nav .navbar-nav .dropdown-menu{ margin: 0px; box-shadow: 3px 5px 15px rgba(0,0,0, .15); diff --git a/consumerWebsite/public/images/apilogo.png b/consumerWebsite/public/images/apilogo.png new file mode 100644 index 0000000000000000000000000000000000000000..37634fbc4348c716568e0be5e241512e1f6792cf GIT binary patch literal 24374 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 literal 0 HcmV?d00001 diff --git a/consumerWebsite/public/js/api.js b/consumerWebsite/public/js/api.js new file mode 100644 index 0000000..594b411 --- /dev/null +++ b/consumerWebsite/public/js/api.js @@ -0,0 +1,88 @@ + +var elements = []; + +[].forEach.call(document.querySelectorAll('.scroll-to-link'), function (div) { + div.onclick = function (e) { + e.preventDefault(); + var target = this.dataset.target; + document.getElementById(target).scrollIntoView({ behavior: 'smooth' }); + var elems = document.querySelectorAll(".content-menu ul li"); + [].forEach.call(elems, function (el) { + el.classList.remove("active"); + }); + this.classList.add("active"); + return false; + }; +}); + +document.getElementById('button-menu-mobile').onclick = function (e) { + e.preventDefault(); + document.querySelector('html').classList.toggle('menu-opened'); +} +document.querySelector('.left-menu .mobile-menu-closer').onclick = function (e) { + e.preventDefault(); + document.querySelector('html').classList.remove('menu-opened'); +} + +function debounce (func) { + var timer; + return function (event) { + if (timer) clearTimeout(timer); + timer = setTimeout(func, 100, event); + }; +} + +function calculElements () { + var totalHeight = 0; + elements = []; + [].forEach.call(document.querySelectorAll('.content-section'), function (div) { + var section = {}; + section.id = div.id; + totalHeight += div.offsetHeight; + section.maxHeight = totalHeight - 25; + elements.push(section); + }); + onScroll(); +} + +function onScroll () { + var scroll = window.pageYOffset; + console.log('scroll', scroll, elements) + for (var i = 0; i < elements.length; i++) { + var section = elements[i]; + if (scroll <= section.maxHeight) { + var elems = document.querySelectorAll(".content-menu ul li"); + [].forEach.call(elems, function (el) { + el.classList.remove("active"); + }); + var activeElems = document.querySelectorAll(".content-menu ul li[data-target='" + section.id + "']"); + [].forEach.call(activeElems, function (el) { + el.classList.add("active"); + }); + break; + } + } + if (window.innerHeight + scroll + 5 >= document.body.scrollHeight) { // end of scroll, last element + var elems = document.querySelectorAll(".content-menu ul li"); + [].forEach.call(elems, function (el) { + el.classList.remove("active"); + }); + var activeElems = document.querySelectorAll(".content-menu ul li:last-child"); + [].forEach.call(activeElems, function (el) { + el.classList.add("active"); + }); + } +} + +calculElements(); +window.onload = () => { + calculElements(); +}; +window.addEventListener("resize", debounce(function (e) { + e.preventDefault(); + calculElements(); +})); +window.addEventListener('scroll', function (e) { + e.preventDefault(); + onScroll(); +}); \ No newline at end of file diff --git a/consumerWebsite/public/js/app.js b/consumerWebsite/public/js/app.js index d2687d6..1e3d235 100644 --- a/consumerWebsite/public/js/app.js +++ b/consumerWebsite/public/js/app.js @@ -134,7 +134,6 @@ app.api = (function (app) { complete: function (res, text) { callback( text !== "success" ? res.statusText : null, - //console.log(res.responseText), JSON.parse(res.responseText), res.status ); @@ -151,8 +150,9 @@ app.auth = (function (app) { localStorage.setItem("APIToken", token); } - function setUserId(userId) { - localStorage.setItem("userId", userId); + function setUserId(userid) { + console.log("userid", userid); + localStorage.setItem("userid", userid); } function setUsername(username) { @@ -185,25 +185,39 @@ app.auth = (function (app) { */ function logOut(callback) { - localStorage.removeItem("APIToken"); - localStorage.removeItem("userId"); - localStorage.removeItem("username"); + //call logout route + $.ajax({ + type: "DELETE", + url: "/api/v0/user/logout", + headers: { + "auth-token": app.auth.getToken(), + }, + contentType: "application/json; charset=utf-8", + dataType: "json", + complete: function (res, text) { + callback( + text !== "success" ? res.statusText : null, + JSON.parse(res.responseText), + res.status + ); + }, + }); - //remove token from db NOT the api key. + localStorage.removeItem("APIToken"); + localStorage.removeItem("userid"); + localStorage.removeItem("username"); callback(); } function forceLogin() { - $.holdReady(true); - app.auth.isLoggedIn(function (error, isLoggedIn) { - if (error || !isLoggedIn) { - app.auth.logOut(function () {}); - location.replace(`/login`); - } else { - $.holdReady(false); - } - }); - } + app.auth.isLoggedIn(function (error, isLoggedIn) { + if (error || !isLoggedIn) { + app.auth.logOut(function () { + location.replace(`/login`); + }); + } + }); + } function logInRedirect() { window.location.href = @@ -215,6 +229,18 @@ app.auth = (function (app) { window.location.href = location.href.replace(location.replace(`/`)) || "/"; } + //if isLoggedin is true, redirect user away from login / register page + function redirectIfLoggedIn() { + $.holdReady(true); + app.auth.isLoggedIn(function (error, isLoggedIn) { + if (error || isLoggedIn) { + location.replace(`/`); + } else { + $.holdReady(false); + } + }); + } + return { getToken: getToken, setToken: setToken, @@ -226,6 +252,7 @@ app.auth = (function (app) { forceLogin, logInRedirect, homeRedirect, + redirectIfLoggedIn, }; })(app); diff --git a/consumerWebsite/public/profile.html b/consumerWebsite/public/profile.html deleted file mode 100644 index 2d4e66f..0000000 --- a/consumerWebsite/public/profile.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - N & LW Lawn Care - Landscaping Bootstrap4 HTML5 Responsive Template - - - - - - - - - - - - -
-
-

Profile -

-
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-

Profile Settings

-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
Find us
-

Blk 645 Jalan Tenaga

-

+65 90064959

-

Leongdingxuan@gmail.com

-
- -
-
Quick links
- - - -
-
-
News
- - - - -
-
-
-
-
-

All Rights Reserved. © 2023 EcoSaver -

-
-
- - - - - - - \ No newline at end of file diff --git a/consumerWebsite/routes/api_routes.js b/consumerWebsite/routes/api_routes.js index 4d110fe..4edf988 100644 --- a/consumerWebsite/routes/api_routes.js +++ b/consumerWebsite/routes/api_routes.js @@ -1,10 +1,14 @@ 'use strict'; const router = require('express').Router(); +const { auth } = require("../middleware/authChecker") -router.use('/user', require('./user')); + +router.use('/auth', require('./auth')); router.use('/apikey', require('./apikey')); +router.use('/user', auth ,require('./user')); + module.exports = router; diff --git a/consumerWebsite/routes/auth.js b/consumerWebsite/routes/auth.js new file mode 100644 index 0000000..e5e41e2 --- /dev/null +++ b/consumerWebsite/routes/auth.js @@ -0,0 +1,56 @@ +const { addUser, loginUser } = require("../functions/apiDatabase.js"); + +const express = require("express"); +const router = express.Router(); + +// /user/register +router.post("/register", async (req, res, next) => { + try { + console.log(req.body); + let Res = await addUser(req.body); + if (Res == false) { + let error = new Error("UserRegFailed"); + error.message = "The user failed to be craated"; + error.status = 400; + return next(error); + } + else{ + return res.json({ + message: "User created successfully", + }); + } + } catch (error) { + console.error(error); + next(error); + } +}); + +//login +router.post("/login", async (req, res, next) => { + try { + let Res = await loginUser(req.body); + if (Res == false) { + let error = new Error("User Login Failed"); + error.status = 400; + return next(error); + } + else{ + //pass res back to form to be set in local storage + console.log("my res" , Res); + return res.json({ + message: "User login successfully", + token: Res.token, + userid: Res.userid, + username: Res.username, + }); + + } + } catch (error) { + console.error(error); + next(error); + } +}); + + +module.exports = router; + diff --git a/consumerWebsite/routes/render.js b/consumerWebsite/routes/render.js index 642731d..5631e42 100644 --- a/consumerWebsite/routes/render.js +++ b/consumerWebsite/routes/render.js @@ -59,4 +59,14 @@ router.get('/login', function(req, res, next) { +//api doc +router.get('/api', function(req, res, next) { + res.render('api'); +}); + +//profile page +router.get('/profile', function(req, res, next) { + res.render('profile'); +}); + module.exports = router; diff --git a/consumerWebsite/routes/user.js b/consumerWebsite/routes/user.js index f9025ff..5df5304 100644 --- a/consumerWebsite/routes/user.js +++ b/consumerWebsite/routes/user.js @@ -1,100 +1,58 @@ -const { addUser, loginUser } = require("../functions/apiDatabase.js"); +const { getUserID , deleteUserToken } = require("../functions/apiDatabase.js"); const express = require("express"); const router = express.Router(); -// /user/register -router.post("/register", async (req, res, next) => { - try { - let Res = await addUser(req.body); - if (Res == false) { - let error = new Error("UserRegFailed"); - error.message = "The user failed to be craated"; - error.status = 400; - return next(error); - } - else{ - return res.json({ - message: "User created successfully", - }); +//api/v0/user/me +//getbyid +router.get("/me", async function (req, res, next) { + try { + + //console.log(req.user); + let user = await getUserID(req.user); + if (!user) { + let error = new Error("User not found"); + error.status = 400; + return next(error); + } + res.json({ + user: user, + }); + + } catch (error) { + next(error); } - } catch (error) { - console.error(error); - next(error); - } }); -//login -router.post("/login", async (req, res, next) => { - try { - let Res = await loginUser(req.body); - if (Res == false) { - let error = new Error("User Login Failed"); - error.status = 400; - return next(error); - } - else{ - //pass res back to form to be set in local storage - console.log(Res); - return res.json({ - message: "User login successfully", - token: Res.token, - userId: Res.userid, - username: Res.username, - }); +//logout +router.delete('/logout', async function(req, res, next){ + try{ + /* + let authToken = req.header('auth-token'); + let userDel = await deleteUserToken(authToken); + if (!userDel) { + let error = new Error("User not found"); + error.status = 400; + return next(error); + } + */ + //destroy method call on seq object + req.token.destroy(); + // DO NOT CALL THIS!!! IT WILL DESTROY USERMODEL SEQ OBJ + //req.user.destroy(); + res.json({ + message: "User logged out successfully", + }); + }catch(error){ + next(error); } - } catch (error) { - console.error(error); - next(error); - } -}); + }); + //update //delete -//getbyid - -module.exports = router; - -/* - -curl localhost/api/v0/user/register -H "Content-Type: application/json" -X POST -d '{"username": -"testuser123", "password": "thisisthesystemuserpasswordnoob", "email": "testuser123@ecosaver.com", "address": -"Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}' -'use strict'; - -const router = require('express').Router(); -const {User} = require('../models/user'); - -router.get('/', async function(req, res, next){ - try{ - return res.json({ - results: await User[req.query.detail ? "listDetail" : "list"]() - }); - }catch(error){ - next(error); - } -}); - -router.get('/me', async function(req, res, next){ - try{ - - return res.json(await User.get({uid: req.user.uid})); - }catch(error){ - next(error); - } -}); - -router.get('/:uid', async function(req, res, next){ - try{ - return res.json({ - results: await User.get(req.params.uid), - }); - }catch(error){ - next(error); - } -}); module.exports = router; -*/ + diff --git a/consumerWebsite/views/api.ejs b/consumerWebsite/views/api.ejs new file mode 100644 index 0000000..db3d1b7 --- /dev/null +++ b/consumerWebsite/views/api.ejs @@ -0,0 +1,160 @@ + + + <%- include('top') %> + + +
+ +
+
+ + +
+
+ + +
+
+
+

Get started

+

+ The Westeros API provides programmatic access to read Game of Thrones data. Retrieve a character, provide an oauth connexion, retrieve a familly, filter them, etc. +

+

+ To use this API, you need an API key. Please contact us at jon.snow@nightswatch.wes to get your own API key. +

+
+
+

get characters

+

+ To get characters you need to make a POST call to the following url :
+ http://api.westeros.com/character/get +

+
+

QUERY PARAMETERS

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
secret_keyStringYour API key.
searchString(optional) A search word to find character by name.
houseString + (optional) a string array of houses: +
aliveBoolean + (optional) a boolean to filter alived characters +
genderString + (optional) a string to filter character by gender:
m: male
f: female +
offsetInteger(optional - default: 0) A cursor for use in pagination. Pagination starts offset the specified offset.
limitInteger(optional - default: 10) A limit on the number of objects to be returned, between 1 and 100.
+
+
+

Errors

+

+ The Westeros API uses the following error codes: +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Error CodeMeaning
X000 + Some parameters are missing. This error appears when you don't pass every mandatory parameters. +
X001 + Unknown or unvalid secret_key. This error appears if you use an unknow API key or if your API key expired. +
X002 + Unvalid secret_key for this domain. This error appears if you use an API key non specified for your domain. Developper or Universal API keys doesn't have domain checker. +
X003 + Unknown or unvalid user token. This error appears if you use an unknow user token or if the user token expired. +
+
+
+
+ + + + + diff --git a/consumerWebsite/views/bot.ejs b/consumerWebsite/views/bot.ejs index 898fd63..af81d8c 100644 --- a/consumerWebsite/views/bot.ejs +++ b/consumerWebsite/views/bot.ejs @@ -85,4 +85,5 @@ + diff --git a/consumerWebsite/views/index.ejs b/consumerWebsite/views/index.ejs index f1a31a7..d1ff853 100644 --- a/consumerWebsite/views/index.ejs +++ b/consumerWebsite/views/index.ejs @@ -1,6 +1,7 @@ <%- include('top') %> +