From 4ab043c3a90ac5ef993e104b29dd94363e0093a2 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 27 Apr 2022 22:01:08 -0400 Subject: [PATCH] Grey Market Labs assessment --- Developer Evaluation.docx | Bin 0 -> 24154 bytes Dockerfile | 19 ++++++ Makefile | 3 + README.md | 77 ++++++++++++++++++++- app.js | 140 ++++++++++++++++++++++++++++++++++++++ package.json | 12 ++++ start.sh | 3 + test.sh | 4 ++ 8 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 Developer Evaluation.docx create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 app.js create mode 100644 package.json create mode 100644 start.sh create mode 100644 test.sh diff --git a/Developer Evaluation.docx b/Developer Evaluation.docx new file mode 100644 index 0000000000000000000000000000000000000000..1bf117147fa9963cd42a100da8a69c13ee3ee6e3 GIT binary patch literal 24154 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 GN6iW!azH2Htw_MeNl8- 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 ZpbH^Nj|p&$5C|{uECKokuUT$<`y2K8HkSYZ literal 0 HcmV?d00001 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3397ddc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:latest + +# Set working directoy +WORKDIR /usr/src/app + +# Copy dependencies and install +COPY package*.json ./ +RUN npm install + +# Copy code +COPY *.js ./ + +# Run the app +EXPOSE 3000 + +COPY start.sh . +RUN chmod 777 start.sh +CMD ./start.sh + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..09450d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +test: app.js package.json + sudo docker build . -t paul/greymarket + sudo docker run -it -p 4916:3000 paul/greymarket diff --git a/README.md b/README.md index 0967ae8..3f09a59 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,76 @@ -# greymarketlabs-assignment +# Running the service +## Pre-Reqs +* Access to the interenet +* Docker environment installed with proper user permissions + +## Running +* This will use port 4916, if that is not available modify the Makefile +* Run `make` + +## Stopping +* Type `exit` + +## Testing +* Run `bash ./test.sh` in a seperate window on the same machine + +# API Description +## Setting Persona + +GET request to /set_persona with the following properties +* persona_id: integer (if not unique it will override a previous persona) +* first_name: string first name of persona +* last_name: string last name of persona +* interest_list: string comma seperated list of interests +* lat: floating point latitude of location +* lon: floating point longitude of location + +This will return a json formatted string of the values you set + +## Getting persona +GET request to /get_persona with the following propertie +* persona_id: integer id (error will occur if it does not exist) + +This will return a JSON in the following format +* id: integer persona id +* first_name: string first name +* last_name: string last name +* interest_list: comma seperated string of interests +* latitude: floating point latitude +* longitude: floating point longitude +* city: string city +* state: string state +* temperature: current temperature in fahrenheit + +# Further Questions +## A +I was not able to complete every aspect of this assignment. I few things I would have liked to accomplish in addition were: +* Proper error checking + * Currently it'll just fail out if bad inputs are given. I would have liked to have it error gracefully with proper error codes +* Clean up the code + * There are some odd variable names and no comments + * Several variables in the API itself change from input to output, I would have liked to make those the same +* Better id handling + * Have additional ways to determine what the next un-assigned ID would be + * Have a better way when getting the information that an ID doesn't exist +* Better testing + * Create a little more robust testing +* Temperature + * I grabbed the first temperature returned, rather than the current. It isn't too far off, but would have like to get the actual current temperature. + +## B +Potential issues include +* Poor error handling, the app will crash on bad input + * Provide more rubust intput checking with error code/string returns +* Interest list handling + * Right now it is just a comma seperated list + * I would have liked to have a seperate table of interests, then the returned json could have been an array list. This would have provided better access for the front end +* Repeat/Unknown IDs + * A repeat ID will overwrite perviously set values, this was a choice, but the API should have an update vs new option + * There could also be a way to not give an ID and the next ID is reaturned to the front end + * Unknown IDs on get are not handled and will cause a crash, there should be an error message returned + +## C +Possible security issues are: +* If anyone has arbetrary access they will be able to get all the persona information, this should be locked down to only authorized users +* Overloading the memory with large values, there is no limit to the length of the strings, thus someone could overwhelm the system by putting in too long of strings -Assignment for Grey Market Labs \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..209e077 --- /dev/null +++ b/app.js @@ -0,0 +1,140 @@ +const util = require('util'); +const express = require('express'); +const sqlite = require('sqlite3'); +const request = require('sync-request'); + +const app = express(); + +const persona_db = new sqlite.Database('./counter_persona.db', (err) => { + if(err) { + console.log(err.message); + exit(1); + } + + persona_db.exec(` + CREATE TABLE IF NOT EXISTS persona ( + id unsigned int primary key not null, + first_name text not null, + last_name text not null, + interest_list text not null, + latitude real not null, + longitude real not null + ); + `); +}); + +function on_err(err_str, err_code) { + console.log(err_str); + return { + "err": err_str, + "code": err_code, + }; +}; + +function insert_update_persona(user_params) { + persona_db.get("SELECT * FROM persona WHERE id = ?", [user_params.persona_id], (err, row) => { + if(row == undefined) { + persona_db.run(` + insert into persona + (id, first_name, last_name, interest_list, latitude, longitude) + values (?, ?, ?, ?, ?, ?) + `, [ + user_params.persona_id, + user_params.first_name, + user_params.last_name, + user_params.interest_list, + user_params.lat, + user_params.lon + ], (err) => { + if(err) { + console.log(err.message); + } + }); + } else { + persona_db.run(` + UPDATE persona SET + first_name = ?, + last_name = ?, + interest_list = ?, + latitude = ?, + longitude = ? + WHERE id = ? + `, [ + user_params.first_name, + user_params.last_name, + user_params.interest_list, + user_params.lat, + user_params.lon, + user_params.persona_id + ], (err) => { + if(err) { + console.log(err.message); + } + }); + } + }); +}; + +function get_weather(user_params) { + my_headers = {headers: {'user-agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36"}}; + weather_url = util.format("https://api.weather.gov/points/%d,%d", user_params.latitude, user_params.longitude); + + data = request('GET', weather_url, my_headers); + + data = JSON.parse(data.getBody()); + + user_params.city = data.properties.relativeLocation.properties.city; + user_params.state = data.properties.relativeLocation.properties.state; + + data = request('GET', data.properties.forecast, my_headers); + + data = JSON.parse(data.getBody()); + + user_params.temperature = data.properties.periods[0].temperature; + + return user_params; +}; + +app.get('/get_persona', function(req, res) { + // Gather params + user_params = { + persona_id: Number(req.query.persona_id), + }; + + persona_db.get("SELECT * FROM persona WHERE id = ?", [user_params.persona_id], (err, row) => { + if(err) { + console.error(err.message); + } + user_params = row; + user_params = get_weather(user_params); + console.log(user_params); + res.send(user_params); + }); +}); + +app.get('/set_persona', function(req, res) { + // Gather params + user_params = { + persona_id: parseInt(req.query.persona_id), + first_name: req.query.first_name, + last_name: req.query.last_name, + interest_list: req.query.interest_list, + lat: Number(req.query.lat), + lon: Number(req.query.lon), + }; + + // Verify params + for(const key in user_params) { + if(user_params[key] == undefined) { + res.send(on_err(util.format('%s must be defined', key), 1)); + return; + } + } + + insert_update_persona(user_params); + + res.send(user_params); +}); + +app.listen(3000, () => console.log('Server ready')); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..665506d --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "greymarket", + "version": "1.0", + "description": "Grey Market assessment, tracking profiles", + "author": "Paul Halvorsen ", + "dependencies": { + "express": "", + "util": "", + "sqlite3": "", + "sync-request": "" + } +} diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..08af697 --- /dev/null +++ b/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +node app.js & +/bin/bash diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..8bceb2f --- /dev/null +++ b/test.sh @@ -0,0 +1,4 @@ +echo +curl "http://localhost:4916/set_persona?persona_id=1&first_name=hello&last_name=world&interest_list=a,b,c&lat=39.2673&lon=-76.7983" +curl "http://localhost:4916/get_persona?persona_id=1" +echo