-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
42 changed files
with
378 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1 @@ | ||
.DS_Store | ||
.env | ||
node_modules/ | ||
/dist/ | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
/test/unit/coverage/ | ||
|
||
# Editor directories and files | ||
.idea | ||
.vscode | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln | ||
node_modules |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
.DS_Store | ||
.env | ||
node_modules/ | ||
/dist/ | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
/test/unit/coverage/ | ||
|
||
# Editor directories and files | ||
.idea | ||
.vscode | ||
*.suo | ||
*.ntvs* | ||
*.njsproj | ||
*.sln |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
const express = require("express"); | ||
const request = require("request"); | ||
const bodyParser = require("body-parser"); | ||
const cors = require("cors"); | ||
require("dotenv").config(); | ||
const app = express(); | ||
const Stache = require("../stache.js"); | ||
|
||
const API_URL = "https://api.yelp.com/v3/graphql"; | ||
const API_KEY = process.env.ACCESS_TOKEN; | ||
|
||
const config = { | ||
cacheExpiration: 120, // seconds | ||
uniqueVariables: { | ||
term: String, | ||
location: Number, | ||
radius: Number, | ||
}, | ||
queryObject: "search", | ||
queryTypename: "Businesses", | ||
flexArg: "limit", | ||
offsetArg: "offset", | ||
}; | ||
const stache = new Stache(config); | ||
|
||
app.use(bodyParser.json()); | ||
|
||
app.use( | ||
cors({ | ||
origin: "http://localhost:8080", | ||
optionsSuccessStatus: 200, | ||
}) | ||
); | ||
|
||
app.post( | ||
"/api", | ||
stache.check, | ||
(req, res, next) => { | ||
if (res.locals.httpRequest) { | ||
request.post( | ||
{ | ||
url: API_URL, | ||
method: "POST", | ||
headers: { | ||
Authorization: "Bearer " + API_KEY, | ||
}, | ||
json: true, | ||
body: req.body, | ||
}, | ||
(err, response, body) => { | ||
res.locals.body = body; | ||
return next(); | ||
} | ||
); | ||
} else { | ||
return next(); | ||
} | ||
}, | ||
stache.it | ||
); | ||
|
||
app.listen(3020); |
File renamed without changes.
0
src/assets/loader.gif → demo/src/assets/loader.gif
100755 → 100644
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<template> | ||
<div class="search-page"> | ||
<search-bar></search-bar> | ||
<h1>Load time: <b>{{search.total}} millisecond{{search.total === 1 ? '' : 's'}}</b></h1> | ||
<div class="loading" v-if="isLoading"> | ||
<img src="../assets/timer2.gif"> Loading... | ||
</div> | ||
<ul v-if="search.business"> | ||
<li v-for="business in search.business"> | ||
<br><b>{{business.name}}</b> {{business.rating}} ☆</br> | ||
<br>{{business.location.formatted_address}}</br> | ||
<br>{{business.reviews[0].user.name}} says: <i>"{{business.reviews[0].text}}"</i> </br> | ||
<br></br> | ||
</li> | ||
</ul> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import SearchBar from "./SearchBar.vue"; | ||
import gql from "graphql-tag"; | ||
import { _ } from "vue-underscore"; | ||
import Router from "vue-router"; | ||
export default { | ||
name: "SearchPage", | ||
components: { | ||
SearchBar | ||
}, | ||
apollo: { | ||
$loadingKey: "isLoading", | ||
search: { | ||
query() { | ||
if (this.term && this.location && this.radius && this.limit) { | ||
return gql` | ||
query searchYelp( | ||
$term: String! | ||
$location: String! | ||
$radius: Float! | ||
$limit: Int! | ||
$offset: Int! | ||
) { | ||
search( | ||
term: $term | ||
location: $location | ||
radius: $radius | ||
limit: $limit | ||
sort_by: "rating" | ||
offset: $offset | ||
) { | ||
total | ||
business { | ||
name | ||
rating | ||
price | ||
photos | ||
id | ||
location{ | ||
formatted_address | ||
city | ||
state | ||
} | ||
reviews{ | ||
text | ||
user{ | ||
name | ||
} | ||
id | ||
rating | ||
time_created | ||
url | ||
} | ||
} | ||
} | ||
} | ||
`; | ||
} | ||
}, | ||
variables() { | ||
return { | ||
term: this.term, | ||
location: this.location, | ||
radius: this.radius * 1609.34, | ||
limit: +this.limit | ||
}; | ||
}, | ||
} | ||
}, | ||
props: ["term", "location", "radius", "limit"], | ||
data() { | ||
return { | ||
search: {}, | ||
isLoading: 0, | ||
}; | ||
}, | ||
}; | ||
</script> | ||
|
||
<!-- "scoped" attribute limits CSS to this component only --> | ||
<style scoped> | ||
h1, | ||
h2 { | ||
font-weight: normal; | ||
} | ||
|
||
ul { | ||
list-style: none; | ||
} | ||
</style> |
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
const Redis = require("ioredis"); | ||
require("dotenv").config(); | ||
const redis = new Redis({ | ||
host: process.env.DB_HOST, | ||
port: process.env.DB_PORT, | ||
password: process.env.DB_PASS | ||
}); | ||
|
||
class Stache { | ||
constructor(config, supersets = true) { | ||
this.it = this.it.bind(this); | ||
this.stage = this.stage.bind(this); | ||
this.check = this.check.bind(this); | ||
this.makeQueryString = this.makeQueryString.bind(this); | ||
this.config = config; | ||
this.supersets = supersets; | ||
} | ||
|
||
denormalize(object) { | ||
const newObj = {}; | ||
for (let key in object) { | ||
let workingObj = newObj; | ||
let path = key.split("."); | ||
for (let i = 1; i < path.length; i += 1) { | ||
const e = path[i]; | ||
if (i === path.length - 1) workingObj[e] = object[key]; | ||
if (!workingObj[e]) { | ||
if (Number(path[i + 1]) || Number(path[i + 1]) === 0) { | ||
workingObj[e] = []; | ||
} else workingObj[e] = {}; | ||
} | ||
workingObj = workingObj[e]; | ||
} | ||
} | ||
return newObj; | ||
} | ||
|
||
normalize(object) { | ||
return Object.assign( | ||
{}, | ||
...(function flattener(objectBit, path = "") { | ||
return [].concat( | ||
...Object.keys(objectBit).map(key => { | ||
return typeof objectBit[key] === "object" && objectBit[key] !== null | ||
? flattener(objectBit[key], `${path}.${key}`) | ||
: { [`${path}.${key}`]: objectBit[key] }; | ||
}) | ||
); | ||
})(object) | ||
); | ||
} | ||
|
||
offsetKeys(object, offset) { | ||
let newObj = {}; | ||
for (let key in object) { | ||
let path = key.split("."); | ||
if (path[4]) { | ||
path[4] = +path[4] + offset; | ||
newObj[path.join(".")] = object[key]; | ||
} | ||
} | ||
return newObj; | ||
} | ||
|
||
makeQueryString(variables, req) { | ||
let queryString = ""; | ||
for (let key in variables) { | ||
let stringy = "req.body.variables.".concat(key); | ||
queryString = queryString.concat(eval(stringy)); | ||
} | ||
return queryString.toLowerCase(); | ||
} | ||
|
||
check(req, res, next) { | ||
console.log("\n"); | ||
res.locals.query = this.makeQueryString(this.config.uniqueVariables, req); | ||
res.locals.start = Date.now(); // demo timer | ||
redis.get(res.locals.query, (err, result) => { | ||
if (err) { | ||
console.log("~~ERROR~~ in redis.get: ", err); // more error handling? | ||
} else if (result) { | ||
let parsedResult = JSON.parse(result); | ||
// ***EXACT MATCH*** | ||
if ( | ||
parsedResult[`.data.${this.config.queryObject}.count`] === | ||
req.body.variables[this.config.flexArg] | ||
) { | ||
parsedResult[".data.search.total"] = Date.now() - res.locals.start; // for timer | ||
console.log( | ||
`*** EXACT: ${ | ||
req.body.variables[this.config.flexArg] | ||
} from cache ***` | ||
); | ||
console.log( | ||
`Returned from cache: ${Date.now() - res.locals.start} ms` | ||
); | ||
return res.send(this.denormalize(parsedResult)); | ||
// ***SUBSET MATCH*** | ||
} else { | ||
res.locals.subset = {}; | ||
res.locals.subset[ | ||
`.data.${this.config.queryObject}.__typename` | ||
] = this.config.queryTypename; | ||
let max = 0; | ||
for (let key in parsedResult) { | ||
let path = key.split("."); | ||
if (path[4] && +path[4] < req.body.variables[this.config.flexArg]) { | ||
if (+path[4] > max) max = +path[4]; | ||
res.locals.subset[key] = parsedResult[key]; | ||
} | ||
} | ||
if (req.body.variables[this.config.flexArg] > max + 1) | ||
res.locals.offset = max + 1; // initializing res.locals.offset will mean that we have a superset | ||
} | ||
} | ||
this.stage(req, res, next); | ||
}); | ||
} | ||
|
||
stage(req, res, next) { | ||
// ***SUBSET ROUTE*** | ||
if (res.locals.subset && !res.locals.offset) { | ||
console.log( | ||
`*** SUBSET: get ${ | ||
req.body.variables[this.config.flexArg] | ||
} from cache ***` | ||
); | ||
console.log(`Returned from cache: ${Date.now() - res.locals.start} ms`); | ||
res.locals.subset = this.denormalize(res.locals.subset); | ||
res.locals.subset.data.search.total = Date.now() - res.locals.start; // for timer | ||
return res.send(res.locals.subset); | ||
} | ||
// ***SUPERSET ROUTE*** | ||
else if (res.locals.subset && res.locals.offset && this.supersets) { | ||
console.log( | ||
`*** SUPERSET: fetch ${req.body.variables[this.config.flexArg] - | ||
res.locals.offset} add'l ***` | ||
); | ||
req.body.variables[this.config.offsetArg] = res.locals.offset; | ||
req.body.variables[this.config.flexArg] = | ||
req.body.variables[this.config.flexArg] - res.locals.offset; | ||
res.locals.httpRequest = true; | ||
next(); | ||
// ***NO MATCH ROUTE*** | ||
} else { | ||
console.log( | ||
`*** NO MATCH: fetch ${req.body.variables[this.config.flexArg]} ***` | ||
); | ||
req.body.variables[this.config.offsetArg] = 0; // need to initialize offset for any API request | ||
res.locals.httpRequest = true; | ||
next(); | ||
} | ||
} | ||
|
||
it(req, res) { | ||
let normalized; | ||
// ***SUPERSET ROUTE*** | ||
if (res.locals.subset && res.locals.offset && this.supersets) { | ||
res.locals.superset = Object.assign( | ||
{}, | ||
res.locals.subset, | ||
this.offsetKeys(this.normalize(res.locals.body), res.locals.offset) | ||
); | ||
normalized = res.locals.superset; | ||
normalized[`.data.${this.config.queryObject}.count`] = | ||
req.body.variables[this.config.flexArg] + | ||
req.body.variables[this.config.offsetArg]; | ||
res.locals.superset = this.denormalize(res.locals.superset); | ||
res.locals.superset.data.search.total = Date.now() - res.locals.start; // demo timer | ||
res.send(res.locals.superset); | ||
} | ||
// ***NO MATCH ROUTE*** | ||
else { | ||
res.locals.body.data.search.total = Date.now() - res.locals.start; // demo timer | ||
normalized = this.normalize(res.locals.body); | ||
normalized[`.data.${this.config.queryObject}.count`] = | ||
req.body.variables[this.config.flexArg]; | ||
res.send(res.locals.body); | ||
} | ||
console.log(`Inserted to Redis: ${Date.now() - res.locals.start} ms`); | ||
redis.set( | ||
res.locals.query, | ||
JSON.stringify(normalized), | ||
"ex", | ||
this.config.cacheExpiration | ||
); | ||
} | ||
} | ||
|
||
module.exports = Stache; |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.