Grey Market Labs assessment
This commit is contained in:
BIN
Developer Evaluation.docx
Normal file
BIN
Developer Evaluation.docx
Normal file
Binary file not shown.
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -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
|
||||
|
||||
3
Makefile
Normal file
3
Makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
test: app.js package.json
|
||||
sudo docker build . -t paul/greymarket
|
||||
sudo docker run -it -p 4916:3000 paul/greymarket
|
||||
77
README.md
77
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
|
||||
140
app.js
Normal file
140
app.js
Normal file
@@ -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'));
|
||||
|
||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "greymarket",
|
||||
"version": "1.0",
|
||||
"description": "Grey Market assessment, tracking profiles",
|
||||
"author": "Paul Halvorsen <pmghalvorsen@gmail.com>",
|
||||
"dependencies": {
|
||||
"express": "",
|
||||
"util": "",
|
||||
"sqlite3": "",
|
||||
"sync-request": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user