nodeMyGPNode/nativeskeletonMongo/routes/router.js
"use strict";
/*
* check if routed handler function exists
* if yes call it, else complain
*/
const handlers = require("../controllers/handlers"); // handlers module
const requestHandlers = { // application urls here
GET: {
"/": handlers.home,
"/cities": handlers.cities,
"/about": handlers.other,
"/contact": handlers.other,
"/contacts": handlers.contacts,
"/login": handlers.login,
"/logout": handlers.logout,
"/notfound": handlers.notfound,
"js": handlers.js,
"css": handlers.css,
"png": handlers.png,
"svg": handlers.svg,
"ico": handlers.ico
},
POST: {
"/contact": handlers.receiveContacts,
"/login": handlers.verifyLogin
}
}
module.exports = {
route(req, res, bodydata) {
let urls = req.url.split("?"); // separate query string from url
req.url = urls[0]; // clean url
let arr = req.url.split("."); // filename with suffix
let ext = arr[arr.length - 1]; // get suffix
if (typeof requestHandlers[req.method][req.url] === 'function') { // look for route
requestHandlers[req.method][req.url](req, res, bodydata); // if found, call
} else if (typeof requestHandlers[req.method][ext] === "function") { // if css, js, png, or
requestHandlers[req.method][ext](req, res); // get that
} else { // else
requestHandlers.GET["/notfound"](req, res); // use notfound
}
}
}
nodeMyGPNode/nativeskeletonMongo/controllers/handlers.js
'use strict';
/*
* handlers.js
* Requesthandlers to be called by the router mechanism
*/
const bcrypt = require('bcryptjs'); // hashing sw
const fs = require("fs"); // file system access
const httpStatus = require("http-status-codes"); // http sc
const cookie = require("../controllers/sess"); // session cookies
const lib = require("../controllers/libWebUtil"); // home grown utilities
const nmlPlate = require("../controllers/myTemplater"); // home grown templater
const models = require("../models/inputOutput"); // models are datahandlers
const isLoggedIn = async function (req, res) {
let name = cookie.get(req, res); // read cookie
if (name) { // logged in?
cookie.set(req, res, name); // if logged in, extend
return name;
} else {
return false;
}
};
const getAndServe = async function (res, path, contentType) { // asynchronous
let obj;
let args = [...arguments]; // arguments to array
let myargs = args.slice(3); // dump first three
// if more they are
// data for template
await fs.readFile(path, function(err, data) { // awaits async read
if (err) {
console.log(`Not found file: ${path}`);
} else {
res.writeHead(httpStatus.OK, { // yes, write header
"Content-Type": contentType
});
// call templater
while( typeof (obj = myargs.shift()) !== 'undefined' ) {
if (obj.cities)
data = nmlPlate.doMoreMagic(data, obj); // inject var data to html
else
data = nmlPlate.doTheMagic(data, obj); // inject var data to html
}
res.write(data);
res.end();
}
});
};
module.exports = {
async home(req, res) {
let name = await isLoggedIn(req, res);
if (!name)
name = '';
let path = "views/index.html";
let content = "text/html; charset=utf-8";
getAndServe(res, path, content, {welcome: name});
},
login(req, res) {
let path = "views/login.html";
let content = "text/html; charset=utf-8";
getAndServe(res, path, content, {msg: 'Login required'});
},
other(req, res) {
let path = "views" + req.url + ".html";
let content = "text/html; charset=utf-8";
getAndServe(res, path, content);
},
js(req, res) {
let path = "public/javascripts" + req.url;
let content = "application/javascript; charset=utf-8";
getAndServe(res, path, content);
},
css(req, res) {
let path = "public/stylesheets" + req.url;
let content = "text/css; charset=utf-8";
getAndServe(res, path, content);
},
png(req, res) {
let path = "public/images" + req.url;
let content = "image/png";
getAndServe(res, path, content);
},
svg(req, res) {
let path = "public" + req.url;
let content = "image/svg+xml";
getAndServe(res, path, content);
},
ico(req, res) {
let path ="public" + req.url;
let content = "image/x-icon";
getAndServe(res, path, content);
},
notfound(req, res) {
console.log(`Handler 'notfound' was called for route ${req.url}`);
res.end();
},
async cities(req, res) {
let r = await models.showCities(req, res);
let content = "text/html; charset=utf-8";
let path = "views/displayCities.html";
getAndServe(res, path, content, {cities: r, a: 'right aside', b: 'left aside'}); // extra arg for templater
},
async contacts(req, res) {
if (! await isLoggedIn(req, res)) {
res.writeHead(httpStatus.MOVED_TEMPORARILY, { // write header
"Location": '/login'
});
res.end();
}
let r = await models.showContacts(req, res);
let content = "text/html; charset=utf-8";
let path = "views/displayContacts.html";
getAndServe(res, path, content, {contacts: r, a: 'right aside', b: 'left aside'}); // extra arg for templater
},
async receiveContacts(req, res, data) {
let obj = lib.makeWebArrays(req, data); // home made GET and POST objects
await models.updContacts(obj);
res.writeHead(httpStatus.MOVED_TEMPORARILY, { // write header
"Location": '/'
});
res.end();
},
async verifyLogin (req, res, data) {
let obj = lib.makeWebArrays(req, data); // home made GET and POST objects
let r = await models.verify(obj);
if (r.length == 1 && await bcrypt.compare(obj.POST.password, ''+r[0].password)) {
cookie.set(req, res, '' + r[0].name); // create login cookie
res.writeHead(httpStatus.MOVED_TEMPORARILY, { // write header
"Location": '/'
});
res.end();
} else {
res.writeHead(httpStatus.MOVED_TEMPORARILY, { // write header
"Location": '/logout'
});
res.end();
}
},
async logout (req, res) {
cookie.unset(req, res); // unset login cookie
res.writeHead(httpStatus.MOVED_TEMPORARILY, { // write header
"Location": '/'
});
res.end();
}
}
nodeMyGPNode/nativeskeletonMongo/models/inputOutput.js
'use strict';
/*
* models
* functions for data manipulation
*/
const bcrypt = require('bcryptjs');
const dbmod = require('./dbMod.js');
const dbw = "world";
const dbc = "nodecontacts";
module.exports = {
async updContacts(obj) {
const db = await dbmod.connect(dbc);
let hashed = await bcrypt.hash(obj.POST.password, 10);
let o = {name: obj.POST.name, email: obj.POST.email, phone: obj.POST.phone, password: hashed};
let key = {email: obj.POST.email};
await db.collection('user').updateOne(key, {"$set": o}, {upsert: true});
dbmod.close();
},
async showContacts () {
const db = await dbmod.connect(dbc);
const rows = await db.collection("user").find().toArray();
dbmod.close();
return rows;
},
async showCities () {
const db = await dbmod.connect(dbw);
const rows = await db.collection("city").find({countrycode: "DNK"}).toArray();
dbmod.close();
return rows;
},
async verify(obj) {
const db = await dbmod.connect(dbc);
let o = { email: obj.POST.email };
const rows = await db.collection('user').find(o).toArray();
return rows;
}
}
nodeMyGPNode/nativeskeletonMongo/models/dbMod.js
'use strict';
const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url);
module.exports = {
connect: async function (dbname) {
await client.connect();
const db = client.db(dbname);
return db;
},
close: async function() {
client.close();
}
}
nodeMyGPNode/nativeskeletonMongo/models/City.js
'use strict';
module.exports = class City {
constructor(name, countrycode, district, population) {
this.name = name;
this.countrycode = countrycode;
this.population = population;
this.district = district;
}
toString() {
return `<tr><td>${this.name}</td>
<td>${this.countrycode}</td>
<td>${this.district}</td>
<td>${this.population}</td></tr>`;
}
};
nodeMyGPNode/nativeskeletonMongo/views/displayCities.html
<!doctype html>
<html>
<head>
<meta charset='utf-8'/>
<title class='title'></title>
<link rel='stylesheet' href='index.css'/>
<link rel='icon' type='image/svg+xml' href='favicon.svg'/>
<script type='module' src='contacts.js'></script>
</head>
<body>
<header id='menu'></header>
<main class='otherpage'>
<aside><42 b 24></aside>
<section>
<h2>Cities</h2>
<div class='nml42'><42 cities 24></div>
</section>
<aside><42 a 24></aside>
</main>
<footer>
<article id="cpryear"></article>
</footer>
</body>
</html>
On your CLI do npm test
to start the server.
Then go to your browser and test the menu items.
Check the browser screen as well as the console log in each case.