Started Swagger
This commit is contained in:
@@ -34,7 +34,7 @@ app.post("/gettoken", (req, res) => {
|
|||||||
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 || !authHeader.startsWith("Bearer ")) {
|
if (!authHeader || !authHeader.startsWith("Bearer")) {
|
||||||
return res.status(401).send("No token specified");
|
return res.status(401).send("No token specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,9 +75,8 @@ const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`);
|
|||||||
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
|
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
|
||||||
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
|
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
|
||||||
|
|
||||||
https.createServer({ ca, cert, key }, app).listen(8443);
|
https.createServer({ ca, cert, key }, app);
|
||||||
|
|
||||||
and remove the following line if HTTPS is used
|
|
||||||
*/
|
*/
|
||||||
app.listen(8080, () =>
|
app.listen(8080, () =>
|
||||||
console.log("Routing ready at http://localhost:8080")
|
console.log("Routing ready at http://localhost:8080")
|
||||||
|
|||||||
Generated
+736
File diff suppressed because it is too large
Load Diff
+31
-10
@@ -5,8 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "flow-remove-types src/ -d out/",
|
"build": "flow-remove-types src/ -d out/",
|
||||||
"buildWithMaps":
|
"buildWithMaps": "flow-remove-types src/ -d out/ --pretty --sourcemaps",
|
||||||
"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",
|
||||||
@@ -16,12 +15,16 @@
|
|||||||
"flow-coverage": "flow-coverage-report",
|
"flow-coverage": "flow-coverage-report",
|
||||||
"eslint": "eslint src",
|
"eslint": "eslint src",
|
||||||
"testNoCoverage": "jest out/",
|
"testNoCoverage": "jest out/",
|
||||||
"test": "jest out/ --coverage --no-cache"
|
"test": "jest out/ --coverage --no-cache",
|
||||||
|
"newman": "newman run postman_collection.json"
|
||||||
},
|
},
|
||||||
"author": "Federico Kereki",
|
"author": "Federico Kereki",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": ["env", "flow"]
|
"presets": [
|
||||||
|
"env",
|
||||||
|
"flow"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
@@ -34,8 +37,14 @@
|
|||||||
"es6": true,
|
"es6": true,
|
||||||
"jest": true
|
"jest": true
|
||||||
},
|
},
|
||||||
"extends": ["eslint:recommended", "plugin:flowtype/recommended"],
|
"extends": [
|
||||||
"plugins": ["babel", "flowtype"],
|
"eslint:recommended",
|
||||||
|
"plugin:flowtype/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel",
|
||||||
|
"flowtype"
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-var": "error",
|
"no-var": "error",
|
||||||
@@ -43,13 +52,23 @@
|
|||||||
"flowtype/no-types-missing-file-annotation": 0
|
"flowtype/no-types-missing-file-annotation": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslintIgnore": ["**/out/*.js"],
|
"eslintIgnore": [
|
||||||
|
"**/out/*.js"
|
||||||
|
],
|
||||||
"flow-coverage-report": {
|
"flow-coverage-report": {
|
||||||
"concurrentFiles": 1,
|
"concurrentFiles": 1,
|
||||||
"excludeGlob": ["node_modules/**"],
|
"excludeGlob": [
|
||||||
"includeGlob": ["src/**/*.js"],
|
"node_modules/**"
|
||||||
|
],
|
||||||
|
"includeGlob": [
|
||||||
|
"src/**/*.js"
|
||||||
|
],
|
||||||
"threshold": 90,
|
"threshold": 90,
|
||||||
"type": ["text", "html", "json"]
|
"type": [
|
||||||
|
"text",
|
||||||
|
"html",
|
||||||
|
"json"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
@@ -65,6 +84,7 @@
|
|||||||
"mariasql": "^0.2.6",
|
"mariasql": "^0.2.6",
|
||||||
"morgan": "^1.9.0",
|
"morgan": "^1.9.0",
|
||||||
"node-mocks-http": "^1.7.0",
|
"node-mocks-http": "^1.7.0",
|
||||||
|
"swagger-ui-express": "^3.0.9",
|
||||||
"winston": "^3.0.0-rc6"
|
"winston": "^3.0.0-rc6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -80,6 +100,7 @@
|
|||||||
"flow-remove-types": "^1.2.3",
|
"flow-remove-types": "^1.2.3",
|
||||||
"flow-typed": "^2.4.0",
|
"flow-typed": "^2.4.0",
|
||||||
"jest": "^23.1.0",
|
"jest": "^23.1.0",
|
||||||
|
"newman": "^3.9.4",
|
||||||
"nodemon": "^1.17.5",
|
"nodemon": "^1.17.5",
|
||||||
"prettier": "^1.13.3"
|
"prettier": "^1.13.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "056f86ef-903d-4d01-9f6c-9d83998db7c2",
|
||||||
|
"name": "Restful server testing for regions",
|
||||||
|
"description": "This test is for /regions ",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
},
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Test Delete",
|
||||||
|
"description": "",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get JWT",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "f5ba5984-536c-461b-8ac9-53369abd1386",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response is long enough\", () => ",
|
||||||
|
" pm.expect(pm.response.text()).to.have.lengthOf.above(40)); ",
|
||||||
|
" ",
|
||||||
|
"pm.test(\"Response has three parts\", () => ",
|
||||||
|
" pm.expect(pm.response.text().split(\".\")).to.have.lengthOf(3));",
|
||||||
|
" ",
|
||||||
|
"pm.environment.set(\"token\", responseBody); // for later scripts",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "urlencoded",
|
||||||
|
"urlencoded": [
|
||||||
|
{
|
||||||
|
"key": "user",
|
||||||
|
"value": "fkereki",
|
||||||
|
"description": "",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "password",
|
||||||
|
"value": "modernjsbook",
|
||||||
|
"description": "",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:8443/gettoken",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"gettoken"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "The HTTP `GET` request method is meant to retrieve data from a server. The data\nis identified by a unique URI (Uniform Resource Identifier). \n\nA `GET` request can pass parameters to the server using \"Query String \nParameters\". For example, in the following request,\n\n> http://example.com/hi/there?hand=wave\n\nThe parameter \"hand\" has the value \"wave\".\n\nThis endpoint echoes the HTTP headers, request parameters and the complete\nURI requested."
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Delete non-existing region",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "706fe7f8-7417-48a9-b03d-0677eee4aad9",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Status code is 404 baby!!\", () => ",
|
||||||
|
" pm.response.to.have.status(404));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "DELETE",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:8443/regions/zz/99",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"regions",
|
||||||
|
"zz",
|
||||||
|
"99"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test Get",
|
||||||
|
"description": "",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Get JWT",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "f5ba5984-536c-461b-8ac9-53369abd1386",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Response is long enough\", () => ",
|
||||||
|
" pm.expect(pm.response.text()).to.have.lengthOf.above(40)); ",
|
||||||
|
" ",
|
||||||
|
"pm.test(\"Response has three parts\", () => ",
|
||||||
|
" pm.expect(pm.response.text().split(\".\")).to.have.lengthOf(3));",
|
||||||
|
" ",
|
||||||
|
"pm.environment.set(\"token\", responseBody); // for later scripts",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "urlencoded",
|
||||||
|
"urlencoded": [
|
||||||
|
{
|
||||||
|
"key": "user",
|
||||||
|
"value": "fkereki",
|
||||||
|
"description": "",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "password",
|
||||||
|
"value": "modernjsbook",
|
||||||
|
"description": "",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:8443/gettoken",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"gettoken"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "The HTTP `GET` request method is meant to retrieve data from a server. The data\nis identified by a unique URI (Uniform Resource Identifier). \n\nA `GET` request can pass parameters to the server using \"Query String \nParameters\". For example, in the following request,\n\n> http://example.com/hi/there?hand=wave\n\nThe parameter \"hand\" has the value \"wave\".\n\nThis endpoint echoes the HTTP headers, request parameters and the complete\nURI requested."
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get /regions/uy",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "4a0fe82d-a640-42c1-b451-f7e6a5e3705e",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Answer should be JSON\", () => {",
|
||||||
|
" pm.response.to.be.success;",
|
||||||
|
" pm.response.to.have.jsonBody(); ",
|
||||||
|
"});",
|
||||||
|
" ",
|
||||||
|
"pm.test(\"Answer should have at least 19 regions\", () => {",
|
||||||
|
" const regions = JSON.parse(pm.response.text());",
|
||||||
|
" pm.expect(regions).to.have.lengthOf.at.least(19);",
|
||||||
|
"});",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{token}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "",
|
||||||
|
"value": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:8443/regions/uy",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"regions",
|
||||||
|
"uy"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Attempt getting all regions of UY\n"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get /regions/uy/10",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "c8a43643-d266-4e1a-9576-7647a544b796",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test(\"Answer is valid, JSON\", function () {",
|
||||||
|
" pm.response.to.be.success;",
|
||||||
|
" pm.response.to.have.jsonBody(); ",
|
||||||
|
" ",
|
||||||
|
" const jsonData = pm.response.json();",
|
||||||
|
" ",
|
||||||
|
" pm.test(\"Answer has a single region\", ",
|
||||||
|
" () => pm.expect(jsonData).to.have.lengthOf(1));",
|
||||||
|
" ",
|
||||||
|
" pm.test(\"Country code is UY\", ",
|
||||||
|
" () => pm.expect(jsonData[0].countryCode).to.equal(\"UY\"));",
|
||||||
|
"",
|
||||||
|
" pm.test(\"Region code is 11\", ",
|
||||||
|
" () => pm.expect(jsonData[0].regionCode).to.equal(\"11\"));",
|
||||||
|
"",
|
||||||
|
" pm.test(\"Region name is Paysandu\", ",
|
||||||
|
" () => pm.expect(jsonData[0].regionName).to.equal(\"Paysandu\"));",
|
||||||
|
"});",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{token}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {},
|
||||||
|
"url": {
|
||||||
|
"raw": "localhost:8443/regions/uy/11",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"regions",
|
||||||
|
"uy",
|
||||||
|
"11"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Attempt getting a single region of UY\n"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "prerequest",
|
||||||
|
"script": {
|
||||||
|
"id": "bd5384f0-4651-4701-a733-e4e2c12aba7a",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"id": "4af046c3-048a-4a41-b070-32481a04d094",
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"title": "Swagger Petstore",
|
||||||
|
"license": {
|
||||||
|
"name": "MIT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"host": "petstore.swagger.io",
|
||||||
|
"basePath": "/v1",
|
||||||
|
"schemes": ["http"],
|
||||||
|
"consumes": ["application/json"],
|
||||||
|
"produces": ["application/json"],
|
||||||
|
"paths": {
|
||||||
|
"/pets": {
|
||||||
|
"get": {
|
||||||
|
"summary": "List all pets",
|
||||||
|
"operationId": "listPets",
|
||||||
|
"tags": ["pets"],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query",
|
||||||
|
"description":
|
||||||
|
"How many items to return at one time (max 100)",
|
||||||
|
"required": false,
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "An paged array of pets",
|
||||||
|
"headers": {
|
||||||
|
"x-next": {
|
||||||
|
"type": "string",
|
||||||
|
"description":
|
||||||
|
"A link to the next page of responses"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Pets"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "unexpected error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"summary": "Create a pet",
|
||||||
|
"operationId": "createPets",
|
||||||
|
"tags": ["pets"],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Null response"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "unexpected error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/pets/{petId}": {
|
||||||
|
"get": {
|
||||||
|
"summary": "Info for a specific pet",
|
||||||
|
"operationId": "showPetById",
|
||||||
|
"tags": ["pets"],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "petId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The id of the pet to retrieve",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description":
|
||||||
|
"Expected response to a valid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Pets"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "unexpected error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"Pet": {
|
||||||
|
"required": ["id", "name"],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Pets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Pet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Error": {
|
||||||
|
"required": ["code", "message"],
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -48,10 +48,11 @@ const getRegion = async (
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.set("Connection", "close");
|
||||||
|
|
||||||
const regions = await dbConn.query(sqlQuery);
|
const regions = await dbConn.query(sqlQuery);
|
||||||
if (regions.length > 0 || region === null) {
|
if (regions.length > 0 || region === null) {
|
||||||
res
|
res.status(200)
|
||||||
.status(200)
|
|
||||||
.set("Content-Type", "application/json")
|
.set("Content-Type", "application/json")
|
||||||
.send(JSON.stringify(regions));
|
.send(JSON.stringify(regions));
|
||||||
} else {
|
} else {
|
||||||
@@ -69,6 +70,8 @@ const deleteRegion = async (
|
|||||||
region: string
|
region: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
|
res.set("Connection", "close");
|
||||||
|
|
||||||
const sqlCities = `
|
const sqlCities = `
|
||||||
SELECT 1 FROM cities
|
SELECT 1 FROM cities
|
||||||
WHERE countryCode="${country}"
|
WHERE countryCode="${country}"
|
||||||
@@ -103,6 +106,8 @@ const postRegion = async (
|
|||||||
country: string,
|
country: string,
|
||||||
name: string
|
name: string
|
||||||
) => {
|
) => {
|
||||||
|
res.set("Connection", "close");
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return res.status(400).send("Missing name");
|
return res.status(400).send("Missing name");
|
||||||
}
|
}
|
||||||
@@ -136,8 +141,7 @@ const postRegion = async (
|
|||||||
|
|
||||||
const result = await dbConn.query(sqlAddRegion);
|
const result = await dbConn.query(sqlAddRegion);
|
||||||
if (result.info.affectedRows > 0) {
|
if (result.info.affectedRows > 0) {
|
||||||
res
|
res.status(201)
|
||||||
.status(201)
|
|
||||||
.header("Location", `/regions/${country}/${newId}`)
|
.header("Location", `/regions/${country}/${newId}`)
|
||||||
.send("Region created");
|
.send("Region created");
|
||||||
} else {
|
} else {
|
||||||
@@ -155,6 +159,8 @@ const putRegion = async (
|
|||||||
region: string,
|
region: string,
|
||||||
name: string
|
name: string
|
||||||
) => {
|
) => {
|
||||||
|
res.set("Connection", "close");
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return res.status(400).send("Missing name");
|
return res.status(400).send("Missing name");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const app = express();
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
const cors = require("cors");
|
||||||
|
const swaggerUi = require("swagger-ui-express");
|
||||||
|
const swaggerDocument = require("../restful.json");
|
||||||
|
|
||||||
|
const dbConn = require("./restful_db.js");
|
||||||
|
const validateUser = require("./validate_user.js");
|
||||||
|
|
||||||
|
const SECRET_JWT_KEY = "modernJSbook";
|
||||||
|
|
||||||
|
const https = require("https");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const keysPath = path.join(__dirname, "../../certificates");
|
||||||
|
const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`);
|
||||||
|
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
|
||||||
|
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
|
||||||
|
|
||||||
|
https.createServer({ ca, cert, key }, app);
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
|
||||||
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
||||||
|
|
||||||
|
app.get("/", (req, res) =>
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.set("Connection", "close")
|
||||||
|
.send("Ready")
|
||||||
|
);
|
||||||
|
|
||||||
|
app.post("/gettoken", (req, res) => {
|
||||||
|
validateUser(req.body.user, req.body.password, (idErr, userid) => {
|
||||||
|
res.set("Connection", "close");
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
res.set("Connection", "close");
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
getRegion,
|
||||||
|
deleteRegion,
|
||||||
|
postRegion,
|
||||||
|
putRegion
|
||||||
|
} = require("./restful_regions.js");
|
||||||
|
|
||||||
|
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.post("/regions/:country", (req, res) =>
|
||||||
|
postRegion(res, dbConn, req.params.country, req.body.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
app.put("/regions/:country/:region", (req, res) =>
|
||||||
|
putRegion(
|
||||||
|
res,
|
||||||
|
dbConn,
|
||||||
|
req.params.country,
|
||||||
|
req.params.region,
|
||||||
|
req.body.name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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(8443, () =>
|
||||||
|
console.log("Server ready at http://localhost:8443")
|
||||||
|
);
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
openapi: "3.0.0"
|
||||||
|
info:
|
||||||
|
title: World Data RESTful API
|
||||||
|
description: "This is a RESTful API to access world data, including countries, regions, and cities."
|
||||||
|
version: "0.0.1"
|
||||||
|
servers:
|
||||||
|
- url: http://127.0.0.1:8443
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: "token"
|
||||||
|
description: "Get a JWT for authorization"
|
||||||
|
- name: "countries"
|
||||||
|
description: "Access the world countries"
|
||||||
|
- name: "regions"
|
||||||
|
description: "Access the regions of countries"
|
||||||
|
- name: "cities"
|
||||||
|
description: "Access the world cities"
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/gettoken:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "token"
|
||||||
|
summary: "Get a token to authorize future requests"
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- user
|
||||||
|
- password
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A valid token to use for other requests
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
/regions:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Get all regions of all countries"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Add a region to a given country"
|
||||||
|
consumes:
|
||||||
|
- "application/x-www-form-urlencoded"
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
parameters:
|
||||||
|
- in: formData
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: The new region's name.
|
||||||
|
- in: header
|
||||||
|
name: "Authorization: Bearer"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: Authorization Token
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: "OK, created"
|
||||||
|
400:
|
||||||
|
description: "Name missing, region not created"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
403:
|
||||||
|
description: "Country not found, region not created"
|
||||||
|
409:
|
||||||
|
description: "Other failure, region not created"
|
||||||
|
|
||||||
|
/regions/{country}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Get all regions of a given country"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) whose regions are required"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
/regions/{country}/{id}:
|
||||||
|
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Get a specific region of a given country"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) of the region"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: "Region (id) that is required"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Delete a specific region of a given country"
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) of the region"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: "Region (id) that is to be deleted"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: "OK, region was deleted"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Region does not exist"
|
||||||
|
405:
|
||||||
|
description: "Region has cities, cannot be deleted"
|
||||||
|
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Update a specific region of a given country"
|
||||||
|
consumes:
|
||||||
|
- "application/x-www-form-urlencoded"
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) of the region"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: "Region (id) that is to be deleted"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: formData
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: The region's new name.
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: "OK, region was updated"
|
||||||
|
400:
|
||||||
|
description: "Name missing, region not updated"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
JwtAuth:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
|
||||||
|
bearerAuth: # arbitrary name for the security scheme
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
openapi: "2.0.0"
|
||||||
|
info:
|
||||||
|
description: "This is a RESTful API to access world data, including countries, regions, and cities."
|
||||||
|
version: "1.0.0"
|
||||||
|
title: "World Data API"
|
||||||
|
|
||||||
|
host: "localhost:8443"
|
||||||
|
tags:
|
||||||
|
- name: "token"
|
||||||
|
description: "Get a JWT for authorization"
|
||||||
|
- name: "countries"
|
||||||
|
description: "Access the world countries"
|
||||||
|
- name: "regions"
|
||||||
|
description: "Access the regions of countries"
|
||||||
|
- name: "cities"
|
||||||
|
description: "Access the world cities"
|
||||||
|
schemes:
|
||||||
|
- "http"
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/gettoken:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "token"
|
||||||
|
summary: "Get a token to authorize future requests"
|
||||||
|
consumes:
|
||||||
|
- "application/x-www-form-urlencoded"
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
parameters:
|
||||||
|
- in: formData
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: A person's name.
|
||||||
|
- in: formData
|
||||||
|
name: password
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: A person's favorite number.
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: A valid token to use for other requests
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
/regions:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Get all regions of all countries"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Add a region to a given country"
|
||||||
|
consumes:
|
||||||
|
- "application/x-www-form-urlencoded"
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
parameters:
|
||||||
|
- in: formData
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: The new region's name.
|
||||||
|
- in: header
|
||||||
|
name: "Authorization: Bearer"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: Authorization Token
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: "OK, created"
|
||||||
|
400:
|
||||||
|
description: "Name missing, region not created"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
403:
|
||||||
|
description: "Country not found, region not created"
|
||||||
|
409:
|
||||||
|
description: "Other failure, region not created"
|
||||||
|
|
||||||
|
/regions/{country}:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Get all regions of a given country"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) whose regions are required"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
/regions/{country}/{id}:
|
||||||
|
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Get a specific region of a given country"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) of the region"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: "Region (id) that is required"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Delete a specific region of a given country"
|
||||||
|
description: ""
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) of the region"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: "Region (id) that is to be deleted"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: "OK, region was deleted"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Region does not exist"
|
||||||
|
405:
|
||||||
|
description: "Region has cities, cannot be deleted"
|
||||||
|
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- "regions"
|
||||||
|
summary: "Update a specific region of a given country"
|
||||||
|
consumes:
|
||||||
|
- "application/x-www-form-urlencoded"
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
parameters:
|
||||||
|
- name: country
|
||||||
|
in: path
|
||||||
|
description: "Country (id) of the region"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
description: "Region (id) that is to be deleted"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
- in: formData
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description: The region's new name.
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: "OK, region was updated"
|
||||||
|
400:
|
||||||
|
description: "Name missing, region not updated"
|
||||||
|
401:
|
||||||
|
description: "No token provided"
|
||||||
|
404:
|
||||||
|
description: "Country not found"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
JwtAuth:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: Authorization
|
||||||
|
|
||||||
|
bearerAuth: # arbitrary name for the security scheme
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
Generated
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user