From 20bf8cc8a47d9612a45567f6b53b5ae5361006e2 Mon Sep 17 00:00:00 2001 From: Sean Crim Date: Wed, 15 Apr 2020 14:10:02 -0400 Subject: [PATCH 1/6] added csv endpoint to do a data dump for any model passed as an argument --- src/routes/csv.js | 162 ++++++++++++++++++++++++++++++++++++++++++++ src/routes/index.js | 4 +- 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 src/routes/csv.js diff --git a/src/routes/csv.js b/src/routes/csv.js new file mode 100644 index 00000000..b60bcdc0 --- /dev/null +++ b/src/routes/csv.js @@ -0,0 +1,162 @@ +import { Router } from 'express'; +import validator from 'validator'; +import utils from '../utils'; + +const router = new Router(); +router.use(utils.authMiddleware) + +// Gets all contacts. +router.get('/:model_type', async (req, res) => { + let code; + let message; + try { + if(req.context.models.hasOwnProperty(req.params.model_type)){ + const results = await req.context.models[req.params.model_type].findAll({}) + + code = 200; + message = { + _meta: { + total: results.length + }, + results + }; + } else { + console.log("model type is invalid") + code = 422; + } + } catch (e) { + console.error(e); + code = 500; + } + + return utils.response(res, code, message); +}); + +// Gets a specific contact. +/*router.get('/:contact_id', async (req, res) => { + let code; + let message; + try { + if (validator.isUUID(req.params.contact_id)) { + const contact = await req.context.models.Contact.findOne({ + where: { + id: req.params.contact_id + }, + }); + + code = 200; + message = contact; + } else { + code = 422; + } + } catch (e) { + console.error(e); + code = 500; + } + + return utils.response(res, code, message); +}); + +// Creates a new contact. +router.post('/', async (req, res) => { + let code; + let message; + try { + if (req.body.name !== undefined) { + const { name, phone, email, UserId, EntityId } = req.body; + + // Validating emails + if (email) { + const goodEmail = await utils.validateEmails(email); + if (!goodEmail) return utils.response(res, 422); + } + + const contact = await req.context.models.Contact.create({ name, email, phone, UserId, EntityId }); + + code = 200; + message = contact.id + ' created'; + } else { + code = 422; + } + } catch (e) { + console.error(e); + code = 500; + } + + return utils.response(res, code, message); +}); + +// Updates any contact. +router.put('/', async (req, res) => { + let code; + let message; + try { + if (validator.isUUID(req.body.id)) { + const { id, name, phone, email, UserId, EntityId } = req.body; + + // Validating emails + if (email) { + const goodEmail = await utils.validateEmails(email); + if (!goodEmail) return utils.response(res, 422); + } + + const contact = await req.context.models.Contact.findOne({ + where: { + id: id + } + }); + + if (!contact) return utils.response(res, 400, message); + + contact.name = (name) ? name : contact.name; + contact.phone = (phone) ? phone : contact.phone; + contact.email = (email) ? email : contact.email; + contact.UserId = (UserId) ? UserId : contact.UserId; + contact.EntityId = (EntityId) ? EntityId : contact.EntityId; + contact.updatedAt = new Date(); + + await contact.save(); + + code = 200; + message = contact.id + ' updated'; + } else { + code = 422; + } + + } catch (e) { + console.error(e); + code = 500; + } + + return utils.response(res, code, message); +}); + +// Deletes a contact. +router.delete('/:contact_id', async (req, res) => { + let code; + let message; + try { + if (validator.isUUID(req.params.contact_id)) { + const contact = await req.context.models.Contact.findOne({ + where: { + id: req.params.contact_id + } + }); + await contact.destroy(); + + code = 200; + message = req.params.contact_id + ' deleted'; + } else { + code = 422; + } + } catch (e) { + console.error(e); + code = 500; + } + + return utils.response(res, code, message); +}); + +export default router;*/ + +export default router; \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js index fbeb9a15..dca1fc10 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -2,10 +2,12 @@ import user from './user'; // import userRole from './user-role'; import contact from './contact'; import entity from './entity'; +import csv from './csv'; // Exports object of routes we import above. Add to this if you're adding new routes. export default { user, // userRole, contact, - entity + entity, + csv }; From a82b0e3b8fd7a71f3b5ad45a8dfc92b8ba724e6b Mon Sep 17 00:00:00 2001 From: Sean Crim Date: Thu, 16 Apr 2020 11:12:37 -0400 Subject: [PATCH 2/6] added json2csv to modify json results to csv format --- package.json | 1 + src/routes/csv.js | 141 ++-------------------------------------------- 2 files changed, 7 insertions(+), 135 deletions(-) diff --git a/package.json b/package.json index a49cfb3d..b3650f50 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "express": "4.17.1", "express-request-id": "1.4.1", "helmet": "3.22.0", + "json2csv": "^5.0.0", "jsonwebtoken": "8.5.1", "lodash": "^4.17.15", "mocha": "7.1.1", diff --git a/src/routes/csv.js b/src/routes/csv.js index b60bcdc0..8bdd4ed3 100644 --- a/src/routes/csv.js +++ b/src/routes/csv.js @@ -1,152 +1,25 @@ import { Router } from 'express'; import validator from 'validator'; import utils from '../utils'; +import { parseAsync } from "json2csv"; const router = new Router(); router.use(utils.authMiddleware) -// Gets all contacts. +// Gets a data dump from the passed in model (if it exists). router.get('/:model_type', async (req, res) => { let code; let message; try { if(req.context.models.hasOwnProperty(req.params.model_type)){ - const results = await req.context.models[req.params.model_type].findAll({}) + const results = await req.context.models[req.params.model_type].findAll({raw:true}); - code = 200; - message = { - _meta: { - total: results.length - }, - results - }; - } else { - console.log("model type is invalid") - code = 422; - } - } catch (e) { - console.error(e); - code = 500; - } - - return utils.response(res, code, message); -}); - -// Gets a specific contact. -/*router.get('/:contact_id', async (req, res) => { - let code; - let message; - try { - if (validator.isUUID(req.params.contact_id)) { - const contact = await req.context.models.Contact.findOne({ - where: { - id: req.params.contact_id - }, - }); - - code = 200; - message = contact; - } else { - code = 422; - } - } catch (e) { - console.error(e); - code = 500; - } - - return utils.response(res, code, message); -}); - -// Creates a new contact. -router.post('/', async (req, res) => { - let code; - let message; - try { - if (req.body.name !== undefined) { - const { name, phone, email, UserId, EntityId } = req.body; - - // Validating emails - if (email) { - const goodEmail = await utils.validateEmails(email); - if (!goodEmail) return utils.response(res, 422); - } - - const contact = await req.context.models.Contact.create({ name, email, phone, UserId, EntityId }); - - code = 200; - message = contact.id + ' created'; - } else { - code = 422; - } - } catch (e) { - console.error(e); - code = 500; - } - - return utils.response(res, code, message); -}); - -// Updates any contact. -router.put('/', async (req, res) => { - let code; - let message; - try { - if (validator.isUUID(req.body.id)) { - const { id, name, phone, email, UserId, EntityId } = req.body; - - // Validating emails - if (email) { - const goodEmail = await utils.validateEmails(email); - if (!goodEmail) return utils.response(res, 422); - } - - const contact = await req.context.models.Contact.findOne({ - where: { - id: id - } - }); - - if (!contact) return utils.response(res, 400, message); - - contact.name = (name) ? name : contact.name; - contact.phone = (phone) ? phone : contact.phone; - contact.email = (email) ? email : contact.email; - contact.UserId = (UserId) ? UserId : contact.UserId; - contact.EntityId = (EntityId) ? EntityId : contact.EntityId; - contact.updatedAt = new Date(); - - await contact.save(); + const csv = results.length === 0 ? "no data found for type" : await parseAsync(JSON.parse(JSON.stringify(results)), Object.keys(results[0]), {}); code = 200; - message = contact.id + ' updated'; - } else { - code = 422; - } - - } catch (e) { - console.error(e); - code = 500; - } - - return utils.response(res, code, message); -}); - -// Deletes a contact. -router.delete('/:contact_id', async (req, res) => { - let code; - let message; - try { - if (validator.isUUID(req.params.contact_id)) { - const contact = await req.context.models.Contact.findOne({ - where: { - id: req.params.contact_id - } - }); - await contact.destroy(); - - code = 200; - message = req.params.contact_id + ' deleted'; + message = csv; } else { + message = "model type is invalid" code = 422; } } catch (e) { @@ -157,6 +30,4 @@ router.delete('/:contact_id', async (req, res) => { return utils.response(res, code, message); }); -export default router;*/ - export default router; \ No newline at end of file From 9d719055cd97311138c622ec941813743d739d57 Mon Sep 17 00:00:00 2001 From: Sean Crim Date: Thu, 16 Apr 2020 12:14:43 -0400 Subject: [PATCH 3/6] updated csv route to handle empty database results and updated swagger --- src/routes/csv.js | 11 +++++++---- swagger.json | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/routes/csv.js b/src/routes/csv.js index 8bdd4ed3..17aa910c 100644 --- a/src/routes/csv.js +++ b/src/routes/csv.js @@ -14,10 +14,13 @@ router.get('/:model_type', async (req, res) => { if(req.context.models.hasOwnProperty(req.params.model_type)){ const results = await req.context.models[req.params.model_type].findAll({raw:true}); - const csv = results.length === 0 ? "no data found for type" : await parseAsync(JSON.parse(JSON.stringify(results)), Object.keys(results[0]), {}); - - code = 200; - message = csv; + if(results.length !== 0){ + code = 200; + message = await parseAsync(JSON.parse(JSON.stringify(results)), Object.keys(results[0]), {}); + } else { + code = 404; + message = "no data found for model type" + } } else { message = "model type is invalid" code = 422; diff --git a/swagger.json b/swagger.json index 81f5546d..e35b7b32 100644 --- a/swagger.json +++ b/swagger.json @@ -28,7 +28,10 @@ }, { "name" : "contact", "description" : "Operations related to contacts" - } ], + }, { + "name": "csv", + "description": "Operations related to CSV data dumps" + }], "paths" : { "/health" : { "get" : { @@ -702,6 +705,48 @@ } } } + }, + "/csv/{model_type}": { + "get": { + "tags" : [ "csv" ], + "summary" : "returns a full csv data dump based on the model type", + "description" : "By passing the model type, you can dump all of the current data in csv format.", + "parameters" : [ { + "name" : "model_type", + "in" : "path", + "description" : "type of model you want a csv data dump for", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "the csv data dump", + "content" : { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "401" : { + "description" : "Unauthorized" + }, + "404" : { + "description": "No data for particular model" + }, + "422" : { + "description" : "Invalid input" + }, + "500" : { + "description" : "Server error" + } + } + } } }, "components" : { From c28458c3ac70204c1bfe669f08e3f33296b51990 Mon Sep 17 00:00:00 2001 From: Sean Crim Date: Thu, 16 Apr 2020 12:26:22 -0400 Subject: [PATCH 4/6] empty response will still return 200 and updated swagger --- src/routes/csv.js | 6 +----- swagger.json | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/routes/csv.js b/src/routes/csv.js index 17aa910c..d16b1a88 100644 --- a/src/routes/csv.js +++ b/src/routes/csv.js @@ -13,14 +13,10 @@ router.get('/:model_type', async (req, res) => { try { if(req.context.models.hasOwnProperty(req.params.model_type)){ const results = await req.context.models[req.params.model_type].findAll({raw:true}); - if(results.length !== 0){ - code = 200; message = await parseAsync(JSON.parse(JSON.stringify(results)), Object.keys(results[0]), {}); - } else { - code = 404; - message = "no data found for model type" } + code = 200; } else { message = "model type is invalid" code = 422; diff --git a/swagger.json b/swagger.json index e35b7b32..a6d70448 100644 --- a/swagger.json +++ b/swagger.json @@ -736,9 +736,6 @@ "401" : { "description" : "Unauthorized" }, - "404" : { - "description": "No data for particular model" - }, "422" : { "description" : "Invalid input" }, From b72a7dadc7c94daa2f98d7b11534d25c0e6c0cf0 Mon Sep 17 00:00:00 2001 From: Sean Crim Date: Thu, 16 Apr 2020 15:33:32 -0400 Subject: [PATCH 5/6] added functionality to process results as needed from data dumps --- src/routes/csv.js | 11 ++++++++--- src/utils/index.js | 27 ++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/routes/csv.js b/src/routes/csv.js index d16b1a88..afa249e3 100644 --- a/src/routes/csv.js +++ b/src/routes/csv.js @@ -10,11 +10,16 @@ router.use(utils.authMiddleware) router.get('/:model_type', async (req, res) => { let code; let message; + const modelType = req.params.model_type; try { - if(req.context.models.hasOwnProperty(req.params.model_type)){ - const results = await req.context.models[req.params.model_type].findAll({raw:true}); + if(req.context.models.hasOwnProperty(modelType)){ + //todo add filtering + const results = await req.context.models[modelType].findAll({raw:true}); + + const processedResults = await utils.processResults(results, modelType); + if(results.length !== 0){ - message = await parseAsync(JSON.parse(JSON.stringify(results)), Object.keys(results[0]), {}); + message = await parseAsync(JSON.parse(JSON.stringify(processedResults)), Object.keys(results[0]), {}); } code = 200; } else { diff --git a/src/utils/index.js b/src/utils/index.js index a839dc2a..652d42d6 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -109,10 +109,35 @@ const validateEmails = async emails => { return true; } +/** + * Processes model results based on type + * + * @param {Array} results + * @param {String} modelType + * + * @return {processedResults} + */ +const processResults = async (results, modelType) => { + switch (modelType){ + case "Entity": + let processedResults = []; + for(let result of results){ + if(result["checkIn"].checkIns.length !== 0) { + result["checkIn"] = result["checkIn"].checkIns[0]; + } + processedResults = [...processedResults, result]; + } + return processedResults; + default: + return results; + }; +} + export default { formatTime, authMiddleware, response, encryptPassword, - validateEmails + validateEmails, + processResults }; From 13d40cdd8c9a97478eace1f4b26e7a1c3a586155 Mon Sep 17 00:00:00 2001 From: Sean Crim Date: Fri, 17 Apr 2020 15:47:35 -0400 Subject: [PATCH 6/6] modified null check for checkins in entities and added todo to expand the null check when the checkin object changes --- src/utils/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/index.js b/src/utils/index.js index 652d42d6..2b7f2c33 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -122,7 +122,8 @@ const processResults = async (results, modelType) => { case "Entity": let processedResults = []; for(let result of results){ - if(result["checkIn"].checkIns.length !== 0) { + //todo expand conditional checking as checkin object becomes more mature + if(result["checkIn"] !== null) { result["checkIn"] = result["checkIn"].checkIns[0]; } processedResults = [...processedResults, result];