@@ -1,5 +1,5 @@ | |||
{ | |||
"name": "ml-curd_api", | |||
"name": "ml-crud_api", | |||
"version": "1.0.0", | |||
"lockfileVersion": 1, | |||
"requires": true, | |||
@@ -123,6 +123,14 @@ | |||
"@types/node": "*" | |||
} | |||
}, | |||
"@types/morgan": { | |||
"version": "1.9.0", | |||
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.0.tgz", | |||
"integrity": "sha512-warrzirh5dlTMaETytBTKR886pRXwr+SMZD87ZE13gLMR8Pzz69SiYFkvoDaii78qGP1iyBIUYz5GiXyryO//A==", | |||
"requires": { | |||
"@types/express": "*" | |||
} | |||
}, | |||
"@types/node": { | |||
"version": "14.0.5", | |||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz", | |||
@@ -168,7 +176,8 @@ | |||
"arg": { | |||
"version": "4.1.3", | |||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", | |||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" | |||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", | |||
"dev": true | |||
}, | |||
"argparse": { | |||
"version": "1.0.10", | |||
@@ -190,6 +199,14 @@ | |||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", | |||
"dev": true | |||
}, | |||
"basic-auth": { | |||
"version": "2.0.1", | |||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", | |||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", | |||
"requires": { | |||
"safe-buffer": "5.1.2" | |||
} | |||
}, | |||
"bl": { | |||
"version": "2.2.0", | |||
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", | |||
@@ -239,7 +256,8 @@ | |||
"buffer-from": { | |||
"version": "1.1.1", | |||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", | |||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" | |||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", | |||
"dev": true | |||
}, | |||
"builtin-modules": { | |||
"version": "1.1.1", | |||
@@ -344,7 +362,8 @@ | |||
"diff": { | |||
"version": "4.0.2", | |||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", | |||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" | |||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", | |||
"dev": true | |||
}, | |||
"ee-first": { | |||
"version": "1.1.1", | |||
@@ -544,7 +563,8 @@ | |||
"make-error": { | |||
"version": "1.3.6", | |||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", | |||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" | |||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", | |||
"dev": true | |||
}, | |||
"media-typer": { | |||
"version": "0.3.0", | |||
@@ -652,6 +672,25 @@ | |||
"resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", | |||
"integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" | |||
}, | |||
"morgan": { | |||
"version": "1.10.0", | |||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", | |||
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", | |||
"requires": { | |||
"basic-auth": "~2.0.1", | |||
"debug": "2.6.9", | |||
"depd": "~2.0.0", | |||
"on-finished": "~2.3.0", | |||
"on-headers": "~1.0.2" | |||
}, | |||
"dependencies": { | |||
"depd": { | |||
"version": "2.0.0", | |||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", | |||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" | |||
} | |||
} | |||
}, | |||
"mpath": { | |||
"version": "0.7.0", | |||
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", | |||
@@ -697,6 +736,11 @@ | |||
"ee-first": "1.1.1" | |||
} | |||
}, | |||
"on-headers": { | |||
"version": "1.0.2", | |||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", | |||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" | |||
}, | |||
"once": { | |||
"version": "1.4.0", | |||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | |||
@@ -890,12 +934,14 @@ | |||
"source-map": { | |||
"version": "0.6.1", | |||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", | |||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" | |||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", | |||
"dev": true | |||
}, | |||
"source-map-support": { | |||
"version": "0.5.19", | |||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", | |||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", | |||
"dev": true, | |||
"requires": { | |||
"buffer-from": "^1.0.0", | |||
"source-map": "^0.6.0" | |||
@@ -947,6 +993,7 @@ | |||
"version": "8.10.1", | |||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.1.tgz", | |||
"integrity": "sha512-bdNz1L4ekHiJul6SHtZWs1ujEKERJnHs4HxN7rjTyyVOFf3HaJ6sLqe6aPG62XTzAB/63pKRh5jTSWL0D7bsvw==", | |||
"dev": true, | |||
"requires": { | |||
"arg": "^4.1.0", | |||
"diff": "^4.0.1", | |||
@@ -1034,7 +1081,8 @@ | |||
"yn": { | |||
"version": "3.1.1", | |||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", | |||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" | |||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", | |||
"dev": true | |||
} | |||
} | |||
} |
@@ -17,8 +17,10 @@ | |||
"@typegoose/typegoose": "^7.1.3", | |||
"@types/express": "^4.17.6", | |||
"@types/mongoose": "^5.7.21", | |||
"@types/morgan": "^1.9.0", | |||
"express": "^4.17.1", | |||
"mongoose": "^5.9.16", | |||
"morgan": "^1.10.0", | |||
"typescript": "^3.9.3" | |||
} | |||
} |
@@ -1,40 +1,12 @@ | |||
import express from "express"; | |||
import { Application, NextFunction, Request, Response, json as jsonBodyParser } from "express"; | |||
import { Application, Request, Response, json as jsonBodyParser } from "express"; | |||
// Trying to import this using import doesn't work at all for some reason. This way does though. | |||
const morgan = require('morgan'); | |||
import { Product, ProductModel } from "./db/models"; | |||
import { QueryOperators, connect as connectDB } from "./db/connection"; | |||
import { post } from "@typegoose/typegoose"; | |||
/** | |||
* Middleware for the server to have a simple authentication system. | |||
* Checks headers to see if "X-Token" is set to "SECRET_KEY". If it isn't, then the server will return a 401 due to lack of authentication. | |||
* @param req - request object | |||
* @param res - response object | |||
* @param next - next function in the chain | |||
*/ | |||
function checkAuthorization(req: Request, res: Response, next: NextFunction) { | |||
const token: string | undefined = req.get("X-Token"); | |||
if (token === "SECRET_KEY") { | |||
next(); | |||
} else { | |||
res.status(401).end(); // Return 401 Unauthorized due to no authentication | |||
} | |||
} | |||
/** | |||
* Converts the integer parameters in the API requests | |||
* @param param - the string parameter passed by the user | |||
* @throws Error if the string cannot be converted into a positive integer and thus can't be used to filter the database query. | |||
*/ | |||
function parseParamInt(param: string): number { | |||
const int = parseInt(param, 10); | |||
if ( isNaN(int) || int < 0 ) { | |||
throw new Error("Param cannot be processed into a valid number"); | |||
} | |||
return int; | |||
} | |||
import * as helpers from "./route.helpers"; | |||
/** | |||
@@ -56,10 +28,10 @@ async function getProductsEndpoint(req: Request, res: Response): Promise<void> { | |||
let priceTo: number = -1; | |||
try { | |||
if (typeof priceFromStr === "string") { | |||
priceFrom = parseParamInt(priceFromStr); | |||
priceFrom = helpers.parseParamInt(priceFromStr); | |||
} | |||
if (typeof priceToStr === "string") { | |||
priceTo = parseParamInt(priceToStr); | |||
priceTo = helpers.parseParamInt(priceToStr); | |||
} | |||
} catch(error) { | |||
// Error processing numbers | |||
@@ -240,23 +212,26 @@ function defineEndpoints(server: Application): void { | |||
// POST create new product | |||
// middleware - Check if client is authorized, Parse JSON from body, then try and create product | |||
server.post(apiBaseEndpoint, [checkAuthorization, jsonParser, createProductEndpoint]); | |||
server.post(apiBaseEndpoint, [helpers.checkAuthorization, jsonParser, createProductEndpoint]); | |||
// PATCH edit specified product | |||
// middleware - Check if client is authorized, Parse JSON from body, then try and edit product | |||
server.patch(specifiedProductEndpoint, [checkAuthorization, jsonParser, editProductEndpoint]); | |||
server.patch(specifiedProductEndpoint, [helpers.checkAuthorization, jsonParser, editProductEndpoint]); | |||
// DELETE specified product | |||
// middleware - Check if client is authorized, then delete product | |||
server.delete(specifiedProductEndpoint, [checkAuthorization, deleteProductEndpoint]); | |||
server.delete(specifiedProductEndpoint, [helpers.checkAuthorization, deleteProductEndpoint]); | |||
} | |||
/** | |||
* Runs REST API server. Makes sure the connection to the mongodb is established. | |||
*/ | |||
export function runServer(): void { | |||
const server = express(); | |||
connectDB(); | |||
const server = express(); | |||
server.use(morgan("dev")); | |||
server.use(helpers.handleMiddlewareError); | |||
defineEndpoints(server); | |||
// Run Server | |||
server.listen(8081, () => { |
@@ -0,0 +1,48 @@ | |||
import { Application, NextFunction, Request, Response } from "express"; | |||
/** | |||
* Checks if an error has occurred and if so, return 500 to the user and don't continue processing | |||
* @param err - error object | |||
* @param req - request object | |||
* @param res - response object | |||
* @param next - next function in the chain | |||
*/ | |||
export function handleMiddlewareError(err: Error, req: Request, res: Response, next: NextFunction) { | |||
if (err) { | |||
// General catch-all for any error in the middleware. Usually this is failing to parse provide JSON at all. | |||
res.status(500).end("Something went wrong!"); | |||
return; | |||
} | |||
// If no error, just continue | |||
next(); | |||
} | |||
/** | |||
* Middleware for the server to have a simple authentication system. | |||
* Checks headers to see if "X-Token" is set to "SECRET_KEY". If it isn't, then the server will return a 401 due to lack of authentication. | |||
* @param req - request object | |||
* @param res - response object | |||
* @param next - next function in the chain | |||
*/ | |||
export function checkAuthorization(req: Request, res: Response, next: NextFunction) { | |||
const token: string | undefined = req.get("X-Token"); | |||
if (token === "SECRET_KEY") { | |||
next(); | |||
} else { | |||
res.status(401).end(); // Return 401 Unauthorized due to no authentication | |||
} | |||
} | |||
/** | |||
* Converts the integer parameters in the API requests | |||
* @param param - the string parameter passed by the user | |||
* @throws Error if the string cannot be converted into a positive integer and thus can't be used to filter the database query. | |||
*/ | |||
export function parseParamInt(param: string): number { | |||
const int = parseInt(param, 10); | |||
if ( isNaN(int) || int < 0 ) { | |||
throw new Error("Param cannot be processed into a valid number"); | |||
} | |||
return int; | |||
} |