Start RESTful server

This commit is contained in:
fkereki
2018-05-20 21:26:37 -04:00
parent aad2bcab51
commit 20b67d95a7
6 changed files with 277 additions and 34 deletions
+11 -29
View File
@@ -5,7 +5,8 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "flow-remove-types src/ -d out/", "build": "flow-remove-types src/ -d out/",
"buildWithMaps": "flow-remove-types src/ -d out/ --pretty --sourcemaps", "buildWithMaps":
"flow-remove-types src/ -d out/ --pretty --sourcemaps",
"start": "npm run build && node out/doroundmath.js", "start": "npm run build && node out/doroundmath.js",
"start-db": "npm run build && node out/dbaccess.js", "start-db": "npm run build && node out/dbaccess.js",
"nodemon": "nodemon --watch src --delay 1 --exec npm start", "nodemon": "nodemon --watch src --delay 1 --exec npm start",
@@ -19,11 +20,7 @@
"author": "Federico Kereki", "author": "Federico Kereki",
"license": "ISC", "license": "ISC",
"babel": { "babel": {
"presets": [ "presets": ["env", "node", "flow"]
"env",
"node",
"flow"
]
}, },
"eslintConfig": { "eslintConfig": {
"parserOptions": { "parserOptions": {
@@ -35,37 +32,22 @@
"node": true, "node": true,
"es6": true "es6": true
}, },
"extends": [ "extends": ["eslint:recommended", "plugin:flowtype/recommended"],
"eslint:recommended", "plugins": ["babel", "flowtype"],
"plugin:flowtype/recommended"
],
"plugins": [
"babel",
"flowtype"
],
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"no-var": "error", "no-var": "error",
"prefer-const": "error" "prefer-const": "error",
"flowtype/no-types-missing-file-annotation": 0
} }
}, },
"eslintIgnore": [ "eslintIgnore": ["**/out/*.js"],
"**/out/*.js"
],
"flow-coverage-report": { "flow-coverage-report": {
"concurrentFiles": 1, "concurrentFiles": 1,
"excludeGlob": [ "excludeGlob": ["node_modules/**"],
"node_modules/**" "includeGlob": ["src/**/*.js"],
],
"includeGlob": [
"src/**/*.js"
],
"threshold": 90, "threshold": 90,
"type": [ "type": ["text", "html", "json"]
"text",
"html",
"json"
]
}, },
"prettier": { "prettier": {
"tabWidth": 4, "tabWidth": 4,
+23 -5
View File
@@ -4,33 +4,51 @@
const express = require("express"); const express = require("express");
const app = express(); const app = express();
const jwt = require("jsonwebtoken"); const jwt = require("jsonwebtoken");
const bodyParser = require("body-parser");
const validateUser = require("./validate_user.js");
const SECRET_JWT_KEY = "modernJSbook"; const SECRET_JWT_KEY = "modernJSbook";
const bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }));
app.get("/public", (req, res) => { app.get("/public", (req, res) => {
res.send("the /public endpoint needs no token!"); res.send("the /public endpoint needs no token!");
}); });
app.post("/gettoken", (req, res) => {
validateUser(req.body.user, req.body.password, (idErr, userid) => {
if (idErr !== null) {
res.status(401).send(idErr);
} else {
jwt.sign(
{ userid },
SECRET_JWT_KEY,
{ algorithm: "HS256", expiresIn: "1h" },
(err, token) => res.status(200).send(token)
);
}
});
});
app.use((req, res, next) => { app.use((req, res, next) => {
// First check for the Authorization header // First check for the Authorization header
const authHeader = req.headers.authorization; const authHeader = req.headers.authorization;
if (!authHeader.startsWith("Bearer ")) { if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).send("No token specified"); return res.status(401).send("No token specified");
} }
// Now validate the token itself // Now validate the token itself
const token = authHeader.split(" ")[1]; const token = authHeader.split(" ")[1];
jwt.verify(token, SECRET_JWT_KEY, function(err, decoded) { jwt.verify(token, SECRET_JWT_KEY, (err, decoded) => {
if (err) { if (err) {
// Token bad formed, or expired, or other problem // Token bad formed, or expired, or other problem
return res.status(403).send("Token expired or not valid"); return res.status(403).send("Token expired or not valid");
} else { } else {
req.decoded = decoded; // Token OK; get the user id from it
// Everything OK, keep processing the request req.userid = decoded.userid;
// Keep processing the request
next(); next();
} }
}); });
+20
View File
@@ -0,0 +1,20 @@
/* @flow */
"use strict";
const mariaSQL = require("mariasql");
const { promisify } = require("util");
const DB_HOST = "127.0.0.1";
const DB_USER = "fkereki";
const DB_PASS = "modernJS!!";
const DB_SCHEMA = "world";
const getDbConnection = (host, user, password, db) => {
const dbConn = new mariaSQL({ host, user, password, db });
dbConn.query = promisify(dbConn.query);
return dbConn;
};
const dbConn = getDbConnection(DB_HOST, DB_USER, DB_PASS, DB_SCHEMA);
module.exports = dbConn;
+105
View File
@@ -0,0 +1,105 @@
/* @flow */
"use strict";
/* eslint-disable */
const getRegion = async (
res: any,
dbConn: any,
country: ?string,
id: ?string
) => {
console.log("COUNTRY", country, "ID", id, typeof id);
let sqlQuery = "";
if (country == null) {
sqlQuery = `
SELECT rr.*, cc.countryName
FROM regions rr
JOIN countries cc
ON cc.countryCode=rr.countryCode
ORDER BY cc.countryCode, rr.regionCode
`;
} else if (id == null) {
sqlQuery = `
SELECT rr.*, cc.countryName
FROM regions rr
JOIN countries cc
ON cc.countryCode=rr.countryCode
WHERE rr.countryCode="${country}"
ORDER BY rr.regionCode
`;
} else {
sqlQuery = `
SELECT rr.*, cc.countryName
FROM regions rr
JOIN countries cc
ON cc.countryCode=rr.countryCode
WHERE rr.countryCode="${country}"
AND rr.regionCode="${id}"
`;
}
try {
const regions = await dbConn.query(sqlQuery);
res
.status(200)
.set("Content-Type", "application/json")
.send(JSON.stringify(regions));
} catch (e) {
res.status(500).send("Server error");
}
};
const deleteRegion = async (
res: any,
dbConn: any,
country: string,
region: string
) => {
const sqlCities = `
SELECT 1 FROM cities
WHERE countryCode="${country}
AND regionCode="${region}
LIMIT 1"
`;
try {
const cities = await dbConn.query(sqlCities);
if (cities.length > 0) {
res.status(403).send("Cannot delete a region with cities");
} else {
const deleteRegion = `
DELETE FROM regions
WHERE countryCode="${country}
AND regionCode="${region}
`;
const result = await dbConn.query(deleteRegion);
if (result.affectedRows > 0) {
res.status(204).send("Region deleted");
} else {
res.status(404).send("Region not found");
}
}
} catch (e) {
res.status(500).send("Server error");
}
};
const putRegion = async (
res: any,
dbConn: any,
country: string,
id: string,
region: any
) => {
res.status(200).send("NOTHING DOING NOW...");
};
const postRegion = async (res: any, dbConn: any, region: any) => {
res.status(200).send("NOTHING DOING NOW...");
};
module.exports = { getRegion, putRegion, deleteRegion, postRegion };
+95
View File
@@ -0,0 +1,95 @@
/* @flow */
"use strict";
const express = require("express");
const app = express();
const jwt = require("jsonwebtoken");
const bodyParser = require("body-parser");
const validateUser = require("./validate_user.js");
const dbConn = require("./restful_db.js");
const {
getRegion,
deleteRegion,
putRegion,
postRegion
} = require("./restful_regions.js");
const SECRET_JWT_KEY = "modernJSbook";
app.use(bodyParser.urlencoded({ extended: false }));
app.post("/gettoken", (req, res) => {
validateUser(req.body.user, req.body.password, (idErr, userid) => {
if (idErr !== null) {
res.status(401).send(idErr);
} else {
jwt.sign(
{ userid },
SECRET_JWT_KEY,
{ algorithm: "HS256", expiresIn: "1h" },
(err, token) => res.status(200).send(token)
);
}
});
});
/*
app.use((req, res, next) => {
// First check for the Authorization header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).send("No token specified");
}
// Now validate the token itself
const token = authHeader.split(" ")[1];
jwt.verify(token, SECRET_JWT_KEY, (err, decoded) => {
if (err) {
// Token bad formed, or expired, or other problem
return res.status(403).send("Token expired or not valid");
} else {
// Token OK; get the user id from it
req.userid = decoded.userid;
// Keep processing the request
next();
}
});
});
*/
// START ROUTING FOR REGIONS
app.get("/regions/", (req, res) => getRegion(res, dbConn));
app.get("/regions/:country/", (req, res) =>
getRegion(res, dbConn, req.params.country)
);
app.get("/regions/:country/:region/", (req, res) =>
getRegion(res, dbConn, req.params.country, req.params.region)
);
app.delete("/regions/:country/:region", (req, res) =>
deleteRegion(res, dbConn, req.params.country, req.params.region)
);
app.put("/regions/:country/:region", (req, res) =>
putRegion(res, dbConn, req.params.country, req.params.region)
);
app.post("/regions", (req, res) => postRegion(res, dbConn));
// END OF ROUTING FOR REGIONS
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error("Error....", err.message);
res.status(500).send("INTERNAL SERVER ERROR");
});
app.listen(8080, () =>
console.log("Mini JWT server ready, at http://localhost:8080/!")
);
+23
View File
@@ -0,0 +1,23 @@
/* @flow */
"use strict";
/*
In real life, validateUser could check a database,
look into an Active Directory, call another service,
etc. -- but for this demo, let's keep it quite
simple and only accept a single hardcoded user.
*/
const validateUser = (
userName: string,
password: string,
callback: (?string, ?string) => void
) => {
if (userName === "fkereki" && password === "modernjsbook") {
callback(null, "fkereki"); // OK, send userName back
} else {
callback("Not valid user", null);
}
};
module.exports = validateUser;