change linter to standard

This commit is contained in:
Unknown
2018-11-12 10:28:59 +00:00
parent 19f3edf55a
commit 04d80c7c2f
19 changed files with 837 additions and 691 deletions

View File

@@ -7,25 +7,10 @@
"dev": "nodemon -w src --exec \"babel-node src\"", "dev": "nodemon -w src --exec \"babel-node src\"",
"build": "npx babel src -d dist", "build": "npx babel src -d dist",
"start": "node dist", "start": "node dist",
"lint": "eslint src", "lint": "standard \"src/**/*.js\"",
"test-watch": "ava --watch", "test-watch": "ava --watch",
"test": "ava --verbose" "test": "ava --verbose"
}, },
"eslintConfig": {
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module"
},
"env": {
"node": true,
"es6": true
},
"rules": {
"no-console": 0,
"no-unused-vars": 1
}
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/developit/express-es6-rest-api.git" "url": "git+https://github.com/developit/express-es6-rest-api.git"
@@ -54,8 +39,8 @@
"@babel/preset-env": "^7.1.0", "@babel/preset-env": "^7.1.0",
"@babel/register": "^7.0.0", "@babel/register": "^7.0.0",
"ava": "1.0.0-beta.8", "ava": "1.0.0-beta.8",
"eslint": "^3.1.1", "nodemon": "^1.9.2",
"nodemon": "^1.9.2" "standard": "^12.0.1"
}, },
"babel": { "babel": {
"presets": [ "presets": [

View File

@@ -1,22 +1,21 @@
import {version} from "../../package.json"; import { version } from '../../package.json'
import {Router} from "express"; import { Router } from 'express'
import {idxSearcher} from "../lib/util";
export default ({config, controller}) => { export default ({ config, controller }) => {
let api = Router(); let api = Router()
api.get("/", (req, res) => { api.get('/', (req, res) => {
res.json({ res.json({
version version
}); })
}); })
api.get("/blueprints", (req, res) => { api.get('/blueprints', (req, res) => {
res.json(controller.blueprints()); res.json(controller.blueprints())
}); })
api.get("/:source/:tab/:resource/:frag", (req, res) => { api.get('/:source/:tab/:resource/:frag', (req, res) => {
const {source, tab, resource, frag} = req.params; const { source, tab, resource, frag } = req.params
controller controller
.retrieveFrag(source, tab, resource, frag) .retrieveFrag(source, tab, resource, frag)
.then(data => res.json(data)) .then(data => res.json(data))
@@ -24,10 +23,10 @@ export default ({config, controller}) => {
res.json({ res.json({
error: err.message error: err.message
}) })
); )
}); })
api.get("/:source/:tab/:resource", (req, res) => { api.get('/:source/:tab/:resource', (req, res) => {
controller controller
.retrieve(req.params.source, req.params.tab, req.params.resource) .retrieve(req.params.source, req.params.tab, req.params.resource)
.then(data => res.json(data)) .then(data => res.json(data))
@@ -35,10 +34,10 @@ export default ({config, controller}) => {
res.json({ res.json({
error: err.message error: err.message
}) })
); )
}); })
api.get("/update", (req, res) => { api.get('/update', (req, res) => {
controller controller
.update() .update()
.then(msg => .then(msg =>
@@ -50,8 +49,8 @@ export default ({config, controller}) => {
res.json({ res.json({
error: err.message error: err.message
}) })
); )
}); })
return api; return api
}; }

View File

@@ -1,5 +1,5 @@
import R from "ramda"; import R from 'ramda'
import {defaultBlueprint, defaultRoute} from "../lib/blueprinters"; import { defaultBlueprint, defaultRoute } from '../lib/blueprinters'
/** /**
* byColumn - generate a Blueprint from a data source by column. Each column * byColumn - generate a Blueprint from a data source by column. Each column
@@ -9,27 +9,27 @@ import {defaultBlueprint, defaultRoute} from "../lib/blueprinters";
* @return {type} Blueprint * @return {type} Blueprint
* generated. * generated.
*/ */
export default function byColumn(tabName, sourceName, sourceId, data) { export default function byColumn (tabName, sourceName, sourceId, data) {
// Define Blueprint props // Define Blueprint props
const bp = R.clone(defaultBlueprint); const bp = R.clone(defaultBlueprint)
bp.source = { bp.source = {
name: sourceName, name: sourceName,
id: sourceId id: sourceId
}; }
bp.name = tabName; bp.name = tabName
// column names define routes // column names define routes
const labels = data[0]; const labels = data[0]
labels.forEach(label => { labels.forEach(label => {
bp.routes[label] = R.clone(defaultRoute); bp.routes[label] = R.clone(defaultRoute)
}); })
// remaining rows as data // remaining rows as data
data.forEach((row, idx) => { data.forEach((row, idx) => {
if (idx == 0) return; if (idx === 0) return
labels.forEach((label, idx) => { labels.forEach((label, idx) => {
bp.routes[label].data.push(row[idx]); bp.routes[label].data.push(row[idx])
}); })
}); })
return bp; return bp
} }

View File

@@ -1,6 +1,6 @@
import R from "ramda"; import R from 'ramda'
import {fmtObj, idxSearcher} from "../lib/util"; import { fmtObj } from '../lib/util'
import {defaultBlueprint, defaultRoute} from "../lib/blueprinters"; import { defaultBlueprint, defaultRoute } from '../lib/blueprinters'
/** /**
* byGroup - generate a Blueprint from a data source grouped by a column called 'group' * byGroup - generate a Blueprint from a data source grouped by a column called 'group'
@@ -13,44 +13,44 @@ import {defaultBlueprint, defaultRoute} from "../lib/blueprinters";
* @param {type} name="" name of blueprint. * @param {type} name="" name of blueprint.
* @return {type} Blueprint * @return {type} Blueprint
*/ */
export default function byGroup( export default function byGroup (
tabName, tabName,
sourceName, sourceName,
sourceId, sourceId,
data, data,
label = "groups" label = 'groups'
) { ) {
// Define Blueprint // Define Blueprint
const bp = R.clone(defaultBlueprint); const bp = R.clone(defaultBlueprint)
bp.source = { bp.source = {
name: sourceName, name: sourceName,
id: sourceId id: sourceId
}; }
bp.name = tabName; bp.name = tabName
// Column names define routes // Column names define routes
const itemLabels = data[0]; const itemLabels = data[0]
const fmt = fmtObj(itemLabels); const fmt = fmtObj(itemLabels)
bp.routes[label] = R.clone(defaultRoute); bp.routes[label] = R.clone(defaultRoute)
bp.routes[label].data = []; bp.routes[label].data = []
const dataGroups = {}; const dataGroups = {}
data.forEach((row, idx) => { data.forEach((row, idx) => {
if (idx == 0) return; if (idx === 0) return
const group = fmt(row).group; const group = fmt(row).group
if (!dataGroups[group]) { if (!dataGroups[group]) {
dataGroups[group] = [fmt(row)]; dataGroups[group] = [fmt(row)]
} else { } else {
dataGroups[group].push(fmt(row)); dataGroups[group].push(fmt(row))
} }
}); })
Object.keys(dataGroups).forEach(groupKey => { Object.keys(dataGroups).forEach(groupKey => {
bp.routes[label].data.push({ bp.routes[label].data.push({
group: groupKey, group: groupKey,
group_label: dataGroups[groupKey][0].group_label, group_label: dataGroups[groupKey][0].group_label,
data: dataGroups[groupKey] data: dataGroups[groupKey]
}); })
}); })
return bp; return bp
} }

View File

@@ -1,6 +1,6 @@
import R from "ramda"; import R from 'ramda'
import {fmtObj, idxSearcher} from "../lib/util"; import { fmtObj } from '../lib/util'
import {defaultBlueprint, defaultRoute} from "../lib/blueprinters"; import { defaultBlueprint, defaultRoute } from '../lib/blueprinters'
/** /**
* byId - generate a Blueprint from a data source by id, which is an integer. * byId - generate a Blueprint from a data source by id, which is an integer.
@@ -13,30 +13,30 @@ import {defaultBlueprint, defaultRoute} from "../lib/blueprinters";
* @param {type} name="" name of blueprint. * @param {type} name="" name of blueprint.
* @return {type} Blueprint * @return {type} Blueprint
*/ */
export default function byId( export default function byId (
tabName, tabName,
sourceName, sourceName,
sourceId, sourceId,
data, data,
label = "ids" label = 'ids'
) { ) {
// Define Blueprint // Define Blueprint
const bp = R.clone(defaultBlueprint); const bp = R.clone(defaultBlueprint)
bp.source = { bp.source = {
name: sourceName, name: sourceName,
id: sourceId id: sourceId
}; }
bp.name = tabName; bp.name = tabName
// Column names define routes // Column names define routes
const itemLabels = data[0]; const itemLabels = data[0]
const fmt = fmtObj(itemLabels); const fmt = fmtObj(itemLabels)
bp.routes[label] = R.clone(defaultRoute); bp.routes[label] = R.clone(defaultRoute)
bp.routes[label].data = []; bp.routes[label].data = []
data.forEach((row, idx) => { data.forEach((row, idx) => {
if (idx == 0) return; if (idx === 0) return
bp.routes[label].data[fmt(row).id] = fmt(row); bp.routes[label].data[fmt(row).id] = fmt(row)
}); })
return bp; return bp
} }

View File

@@ -1,6 +1,6 @@
import R from "ramda"; import R from 'ramda'
import {fmtObj, idxSearcher} from "../lib/util"; import { fmtObj } from '../lib/util'
import {defaultBlueprint, defaultRoute} from "../lib/blueprinters"; import { defaultBlueprint, defaultRoute } from '../lib/blueprinters'
/** /**
* byRow - generate a Blueprint from a data source by row. The resource name * byRow - generate a Blueprint from a data source by row. The resource name
@@ -12,30 +12,30 @@ import {defaultBlueprint, defaultRoute} from "../lib/blueprinters";
* @param {type} name="" name of blueprint. * @param {type} name="" name of blueprint.
* @return {type} Blueprint * @return {type} Blueprint
*/ */
export default function byRow( export default function byRow (
tabName, tabName,
sourceName, sourceName,
sourceId, sourceId,
data, data,
label = "rows" label = 'rows'
) { ) {
// Define Blueprint // Define Blueprint
const bp = R.clone(defaultBlueprint); const bp = R.clone(defaultBlueprint)
bp.source = { bp.source = {
name: sourceName, name: sourceName,
id: sourceId id: sourceId
}; }
bp.name = tabName; bp.name = tabName
// Column names define routes // Column names define routes
const itemLabels = data[0]; const itemLabels = data[0]
const fmt = fmtObj(itemLabels); const fmt = fmtObj(itemLabels)
bp.routes[label] = R.clone(defaultRoute); bp.routes[label] = R.clone(defaultRoute)
bp.routes[label].data = []; bp.routes[label].data = []
data.forEach((row, idx) => { data.forEach((row, idx) => {
if (idx == 0) return; if (idx === 0) return
bp.routes[label].data.push(fmt(row)); bp.routes[label].data.push(fmt(row))
}); })
return bp; return bp
} }

View File

@@ -1,6 +1,5 @@
import R from "ramda"; import R from 'ramda'
import {fmtObj, idxSearcher} from "../lib/util"; import { defaultBlueprint, defaultRoute } from '../lib/blueprinters'
import {defaultBlueprint, defaultRoute} from "../lib/blueprinters";
/** /**
* byTree - generate a Blueprint from a data source grouped by a column called 'group' * byTree - generate a Blueprint from a data source grouped by a column called 'group'
@@ -13,55 +12,56 @@ import {defaultBlueprint, defaultRoute} from "../lib/blueprinters";
* @param {type} name="" name of blueprint. * @param {type} name="" name of blueprint.
* @return {type} Blueprint * @return {type} Blueprint
*/ */
export default function byTree( export default function byTree (
tabName, tabName,
sourceName, sourceName,
sourceId, sourceId,
data, data,
label = "tree" label = 'tree'
) { ) {
// Define Blueprint // Define Blueprint
const bp = R.clone(defaultBlueprint); const bp = R.clone(defaultBlueprint)
bp.source = { bp.source = {
name: sourceName, name: sourceName,
id: sourceId id: sourceId
}; }
bp.name = tabName; bp.name = tabName
// Column names define routes // Column names define routes
bp.routes[label] = R.clone(defaultRoute); bp.routes[label] = R.clone(defaultRoute)
bp.routes[label].data = {}; bp.routes[label].data = {}
const tree = { const tree = {
key: "tags", key: 'tags',
children: {} children: {}
}; }
data.forEach(path => { data.forEach(path => {
const root = path[0]; const root = path[0]
if (!tree.children[root]) if (!tree.children[root]) {
tree.children[root] = { tree.children[root] = {
key: root, key: root,
children: {} children: {}
}; }
}
let depth = 1; let depth = 1
let parentNode = tree.children[root]; let parentNode = tree.children[root]
while (depth < path.length) { while (depth < path.length) {
const node = path[depth]; const node = path[depth]
if (!parentNode.children[node]) { if (!parentNode.children[node]) {
parentNode.children[node] = { parentNode.children[node] = {
key: node, key: node,
children: {} children: {}
}; }
} }
parentNode = parentNode.children[node]; parentNode = parentNode.children[node]
depth++; depth++
} }
}); })
bp.routes[label].data = tree; bp.routes[label].data = tree
return bp; return bp
} }

View File

@@ -1,18 +1,18 @@
import BP from "./lib/blueprinters"; import BP from './lib/blueprinters'
export default { export default {
port: 4040, port: 4040,
googleSheets: { googleSheets: {
email: "project-name@reliable-baptist-23338.iam.gserviceaccount.com", email: 'project-name@reliable-baptist-23338.iam.gserviceaccount.com',
privateKey: "SOME_PRIVATE_KEY", privateKey: 'SOME_PRIVATE_KEY',
sheets: [ sheets: [
{ {
name: "example", name: 'example',
id: "1s-vfBR8Uy-B-TLO_C5Ozw4z-L0E3hdP8ohMV761ouRI", id: '1s-vfBR8Uy-B-TLO_C5Ozw4z-L0E3hdP8ohMV761ouRI',
tabs: { tabs: {
objects: [BP.byRow] objects: [BP.byRow]
} }
} }
] ]
} }
}; }

View File

@@ -1,12 +1,12 @@
import http from "http"; import http from 'http'
import express from "express"; import express from 'express'
import initialize from "./initialize"; import initialize from './initialize'
import middleware from "./middleware"; import middleware from './middleware'
import api from "./api"; import api from './api'
import config from "./config"; import config from './config'
let app = express(); let app = express()
app.server = http.createServer(app); app.server = http.createServer(app)
initialize(controller => { initialize(controller => {
app.use( app.use(
@@ -14,18 +14,18 @@ initialize(controller => {
config, config,
controller controller
}) })
); )
app.use( app.use(
"/api", '/api',
api({ api({
config, config,
controller controller
}) })
); )
app.server.listen(process.env.PORT || config.port, () => { app.server.listen(process.env.PORT || config.port, () => {
console.log(`Started on port ${app.server.address().port}`); console.log(`Started on port ${app.server.address().port}`)
}); })
}); })
export default app; export default app

View File

@@ -1,16 +1,16 @@
import StoreJson from "./models/StoreJson"; import StoreJson from './models/StoreJson'
import Fetcher from "./lib/Fetcher"; import Fetcher from './lib/Fetcher'
import Controller from "./lib/Controller"; import Controller from './lib/Controller'
import config from "./config"; import config from './config'
const {googleSheets} = config; const { googleSheets } = config
const {sheets, privateKey, email} = googleSheets; const { sheets, privateKey, email } = googleSheets
function authenticate(_fetcher) { function authenticate (_fetcher) {
return _fetcher.fetcher.authenticate(email, privateKey).then(msg => { return _fetcher.fetcher.authenticate(email, privateKey).then(msg => {
console.log(msg); console.log(msg)
return true; return true
}); })
} }
export default callback => { export default callback => {
@@ -18,29 +18,29 @@ export default callback => {
return { return {
name: sheet.name, name: sheet.name,
fetcher: new Fetcher(new StoreJson(), sheet.name, sheet.id, sheet.tabs) fetcher: new Fetcher(new StoreJson(), sheet.name, sheet.id, sheet.tabs)
}; }
}); })
Promise.all(fetchers.map(authenticate)) Promise.all(fetchers.map(authenticate))
.then(() => { .then(() => {
console.log(`===================`); console.log(`===================`)
console.log(`grant access to: ${email}`); console.log(`grant access to: ${email}`)
console.log(`===================`); console.log(`===================`)
// NB: reformat fetchers as config for controller // NB: reformat fetchers as config for controller
const config = {}; const config = {}
fetchers.forEach(fetcher => { fetchers.forEach(fetcher => {
config[fetcher.name] = fetcher.fetcher; config[fetcher.name] = fetcher.fetcher
}); })
const controller = new Controller(config); const controller = new Controller(config)
callback(controller); callback(controller)
}) })
.catch(err => { .catch(err => {
console.log(err); console.log(err)
console.log( console.log(
`ERROR: the server couldn't connect to all of the sheets you provided. Ensure you have granted access to ${ `ERROR: the server couldn't connect to all of the sheets you provided. Ensure you have granted access to ${
email email
} on ALL listed sheets.` } on ALL listed sheets.`
); )
}); })
}; }

View File

@@ -3,51 +3,51 @@
* *
*/ */
class Controller { class Controller {
constructor(fetchers) { constructor (fetchers) {
this.fetchers = fetchers; this.fetchers = fetchers
} }
sourceExists(source) { sourceExists (source) {
return (Object.keys(this.fetchers).indexOf(source) >= 0) return (Object.keys(this.fetchers).indexOf(source) >= 0)
} }
blueprints() { blueprints () {
return Object.keys(this.fetchers).map( return Object.keys(this.fetchers).map(
source => this.fetchers[source].blueprints source => this.fetchers[source].blueprints
); )
} }
update() { update () {
return Promise.all( return Promise.all(
Object.keys(this.fetchers).map(source => { Object.keys(this.fetchers).map(source => {
return this.fetchers[source].update(); return this.fetchers[source].update()
}) })
).then(results => { ).then(results => {
return "All sources updated"; return 'All sources updated'
}); })
} }
retrieve(source, tab, resource) { retrieve (source, tab, resource) {
if (this.sourceExists(source)) { if (this.sourceExists(source)) {
const fetcher = this.fetchers[source]; const fetcher = this.fetchers[source]
return fetcher.retrieve(tab, resource); return fetcher.retrieve(tab, resource)
} else { } else {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
throw new Error(`Source ${source} not available.`); throw new Error(`Source ${source} not available.`)
}); })
} }
} }
retrieveFrag(source, tab, resource, frag) { retrieveFrag (source, tab, resource, frag) {
if (this.sourceExists(source)) { if (this.sourceExists(source)) {
const fetcher = this.fetchers[source]; const fetcher = this.fetchers[source]
return fetcher.retrieveFrag(tab, resource, frag); return fetcher.retrieveFrag(tab, resource, frag)
} else { } else {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
throw new Error(`Source ${source} not available.`); throw new Error(`Source ${source} not available.`)
}); })
} }
} }
} }
export default Controller; export default Controller

View File

@@ -1,50 +1,49 @@
// FetcherTwo class interfaces with Google Sheet, and saves to a specified db // FetcherTwo class interfaces with Google Sheet, and saves to a specified db
import {google} from "googleapis"; import { google } from 'googleapis'
import { import {
fmtSourceTitle, fmtSourceTitle,
fmtBlueprinterTitles, fmtBlueprinterTitles,
deriveFilename,
bp, bp,
isFunction isFunction
} from "./util"; } from './util'
import {byRow, byId} from "./blueprinters"; import { byRow } from './blueprinters'
import R from "ramda"; import R from 'ramda'
class Fetcher { class Fetcher {
constructor(db, sourceName, sourceId, blueprinters) { constructor (db, sourceName, sourceId, blueprinters) {
/* /*
* The database that the fetcher should use. This should be an instance of a model-compliant class. * The database that the fetcher should use. This should be an instance of a model-compliant class.
* See models/Interface.js for the specifications for a model-compliant class. * See models/Interface.js for the specifications for a model-compliant class.
*/ */
this.db = db; this.db = db
/* /*
* ID of the Google Sheet where the data is sourced. Note that the privateKey.client_email * ID of the Google Sheet where the data is sourced. Note that the privateKey.clientEmail
* loaded here must be added to the sheet as an editor. * loaded here must be added to the sheet as an editor.
*/ */
this.sourceId = sourceId; this.sourceId = sourceId
/* /*
* The name of the source. This will prefix tabs saved in the database. * The name of the source. This will prefix tabs saved in the database.
*/ */
this.sourceName = sourceName; this.sourceName = sourceName
/* /*
* These are the available tabs for storing and retrieving data. * These are the available tabs for storing and retrieving data.
* Each blueprinter is a function that returns a Blueprint from a * Each blueprinter is a function that returns a Blueprint from a
* list of lists (which will be retrieved from gsheets). * list of lists (which will be retrieved from gsheets).
*/ */
this.blueprinters = fmtBlueprinterTitles(blueprinters); this.blueprinters = fmtBlueprinterTitles(blueprinters)
this.blueprints = {}; this.blueprints = {}
Object.keys(this.blueprinters).forEach(key => { Object.keys(this.blueprinters).forEach(key => {
this.blueprints[key] = null; this.blueprints[key] = null
}); })
/* /*
* Google API setup * Google API setup
*/ */
this.sheets = google.sheets("v4"); this.sheets = google.sheets('v4')
this.auth = null; this.auth = null
/** /**
* saveBp is a curried function that takes in a title and * saveBp is a curried function that takes in a title and
@@ -57,35 +56,34 @@ class Fetcher {
this.sourceName, this.sourceName,
this.sourceId, this.sourceId,
data data
); )
const blueprint = bp(saturatedBp); // TODO: come up with better semantics. const blueprint = bp(saturatedBp) // TODO: come up with better semantics.
this.blueprints[title] = blueprint; this.blueprints[title] = blueprint
return this.db.save(saturatedBp); return this.db.save(saturatedBp)
}); })
} }
/** returns a Promise that resolves if access is granted to the account, and rejects otherwise. */ /** returns a Promise that resolves if access is granted to the account, and rejects otherwise. */
authenticate(client_email, private_key) { authenticate (clientEmail, privateKey) {
const googleAuth = new google.auth.JWT(client_email, null, private_key, [ const googleAuth = new google.auth.JWT(clientEmail, null, privateKey, [
"https://www.googleapis.com/auth/spreadsheets" 'https://www.googleapis.com/auth/spreadsheets'
]); ])
this.auth = googleAuth; this.auth = googleAuth
const {sourceId} = this; const { sourceId } = this
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
googleAuth.authorize(function(err, tokens) { googleAuth.authorize(function (err) {
if (err) { if (err) {
reject(err); reject(err)
return;
} else { } else {
resolve(`Connected to ${sourceId}.`); resolve(`Connected to ${sourceId}.`)
} }
}); })
}); })
} }
update() { update () {
let tabTitles; let tabTitles
/* Retrieve all available routes on a given sheet, and store formatted copies of it where a formatter is available */ /* Retrieve all available routes on a given sheet, and store formatted copies of it where a formatter is available */
return this.sheets.spreadsheets return this.sheets.spreadsheets
.get({ .get({
@@ -93,57 +91,57 @@ class Fetcher {
spreadsheetId: this.sourceId spreadsheetId: this.sourceId
}) })
.then(response => { .then(response => {
tabTitles = response.data.sheets.map(sheet => sheet.properties.title); tabTitles = response.data.sheets.map(sheet => sheet.properties.title)
return this.sheets.spreadsheets.values.batchGet({ return this.sheets.spreadsheets.values.batchGet({
auth: this.auth, auth: this.auth,
spreadsheetId: this.sourceId, spreadsheetId: this.sourceId,
ranges: tabTitles ranges: tabTitles
}); })
}) })
.then(results => { .then(results => {
const tabData = results.data.valueRanges; const tabData = results.data.valueRanges
return Promise.all( return Promise.all(
tabData.map((tab, idx) => { tabData.map((tab, idx) => {
const {values} = tab; const { values } = tab
if (values == undefined) { if (values === undefined) {
return Promise.resolve({}); return Promise.resolve({})
} }
const name = tabTitles[idx]; const name = tabTitles[idx]
return this.save(name, values); return this.save(name, values)
}) })
); )
}) })
.then(() => "All tabs updated"); .then(() => 'All tabs updated')
} }
save(tab, data) { save (tab, data) {
const title = fmtSourceTitle(tab); const title = fmtSourceTitle(tab)
if (Object.keys(this.blueprinters).indexOf(title) > -1) { if (Object.keys(this.blueprinters).indexOf(title) > -1) {
const bpConfig = this.blueprinters[title]; const bpConfig = this.blueprinters[title]
if (isFunction(bpConfig)) { if (isFunction(bpConfig)) {
return this._saveBp(tab, title, data, bpConfig); return this._saveBp(tab, title, data, bpConfig)
} else { } else {
return bpConfig.map(this._saveBp(tab, title, data)); return bpConfig.map(this._saveBp(tab, title, data))
} }
} else { } else {
// If it can't find a blueprinter for the tab title, default to byRow // If it can't find a blueprinter for the tab title, default to byRow
return this.db.save(byRow(tab, this.sourceName, this.sourceId, data)); return this.db.save(byRow(tab, this.sourceName, this.sourceId, data))
} }
} }
// NB: could combine these functions by checking kwargs length // NB: could combine these functions by checking kwargs length
retrieve(tab, resource) { retrieve (tab, resource) {
const title = fmtSourceTitle(tab); const title = fmtSourceTitle(tab)
const url = `${this.sourceName}/${tab}/${resource}`; const url = `${this.sourceName}/${tab}/${resource}`
return this.db.load(url, this.blueprints[title]); return this.db.load(url, this.blueprints[title])
} }
retrieveFrag(tab, resource, frag) { retrieveFrag (tab, resource, frag) {
const title = fmtSourceTitle(tab); const title = fmtSourceTitle(tab)
const url = `${this.sourceName}/${tab}/${resource}/${frag}`; const url = `${this.sourceName}/${tab}/${resource}/${frag}`
return this.db.load(url, this.blueprints[title]); return this.db.load(url, this.blueprints[title])
} }
} }
export default Fetcher; export default Fetcher

View File

@@ -1,33 +1,32 @@
import path from "path"; import path from 'path'
import fs from "fs"; import fs from 'fs'
export const defaultBlueprint = { export const defaultBlueprint = {
name: null, name: null,
id: null, id: null,
dialects: ["rest"], // supported dialects, can (eventually) be multiple dialects: ['rest'], // supported dialects, can (eventually) be multiple
routes: {} routes: {}
}; }
export const defaultRoute = { export const defaultRoute = {
options: { options: {
fragment: true fragment: true
}, },
data: [] data: []
}; }
// import all default exports from 'blueprinters' folder // import all default exports from 'blueprinters' folder
const allBps = {}; const allBps = {}
const REL_PATH_TO_BPS = "../blueprinters"; const REL_PATH_TO_BPS = '../blueprinters'
const normalizedPath = path.join(__dirname, REL_PATH_TO_BPS); const normalizedPath = path.join(__dirname, REL_PATH_TO_BPS)
fs.readdirSync(normalizedPath).forEach(file => { fs.readdirSync(normalizedPath).forEach(file => {
const bpName = file.replace(".js", ""); const bpName = file.replace('.js', '')
allBps[bpName] = require(`${REL_PATH_TO_BPS}/${file}`).default; allBps[bpName] = require(`${REL_PATH_TO_BPS}/${file}`).default
}); })
// NB: revert to ES5 'module.exports' required to make blueprinters from // NB: revert to ES5 'module.exports' required to make blueprinters from
// each file in blueprinters folder available for granular import from here. // each file in blueprinters folder available for granular import from here.
module.exports = { module.exports = Object.assign({
defaultBlueprint, defaultBlueprint,
defaultRoute, defaultRoute
...allBps }, allBps)
}

View File

@@ -1,16 +1,17 @@
import R from "ramda"; import R from 'ramda'
String.prototype.replaceAll = function(search, replacement) { /* eslint-disable */
const target = this; String.prototype.replaceAll = function (search, replacement) {
return target.replace(new RegExp(search, "g"), replacement); const target = this
}; return target.replace(new RegExp(search, 'g'), replacement)
}
/* eslint-enable */
function camelize(str) { function camelize (str) {
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) { return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match) {
if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces if (+match === 0) return '' // or if (/\s+/.test(match)) for white spaces
// return index == 0 ? match.toLowerCase() : match.toUpperCase(); return match.toUpperCase()
return match.toUpperCase(); })
});
} }
export const fmtObj = R.curry( export const fmtObj = R.curry(
@@ -23,71 +24,71 @@ export const fmtObj = R.curry(
camelCaseKeys: false camelCaseKeys: false
} }
) => { ) => {
const obj = {}; const obj = {}
const fmtColName = colName => { const fmtColName = colName => {
if (options.camelCaseKeys) { if (options.camelCaseKeys) {
return camelize(colName); return camelize(colName)
} else if (options.hyphenatedKeys) { } else if (options.hyphenatedKeys) {
return colName.toLowerCase().replaceAll(" ", "-"); return colName.toLowerCase().replaceAll(' ', '-')
} else if (options.noSpacesInKeys) { } else if (options.noSpacesInKeys) {
return colName.replaceAll(" ", ""); return colName.replaceAll(' ', '')
} else { } else {
return colName; return colName
} }
}; }
columnNames.forEach((columnName, idx) => { columnNames.forEach((columnName, idx) => {
obj[fmtColName(columnName)] = row[idx]; obj[fmtColName(columnName)] = row[idx]
}); })
return obj; return obj
} }
); )
/* search for object with key in array. Return index if exists, or -1 if not */ /* search for object with key in array. Return index if exists, or -1 if not */
export const idxSearcher = R.curry((attrName, searchValue, myArray) => { export const idxSearcher = R.curry((attrName, searchValue, myArray) => {
for (var i = 0; i < myArray.length; i++) { for (var i = 0; i < myArray.length; i++) {
if (myArray[i][attrName] == searchValue) { if (myArray[i][attrName] === searchValue) {
return i; return i
} }
} }
return -1; return -1
}); })
/* more site specific functions. TODO: maybe move to another folder? */ /* more site specific functions. TODO: maybe move to another folder? */
export function fmtSourceTitle(name) { export function fmtSourceTitle (name) {
return name.replaceAll(" ", "-").toLowerCase(); return name.replaceAll(' ', '-').toLowerCase()
} }
export function fmtBlueprinterTitles(tabs) { export function fmtBlueprinterTitles (tabs) {
const obj = {}; const obj = {}
Object.keys(tabs).forEach(tab => { Object.keys(tabs).forEach(tab => {
const name = fmtSourceTitle(tab); const name = fmtSourceTitle(tab)
obj[name] = tabs[tab]; obj[name] = tabs[tab]
}); })
return obj; return obj
} }
export function deriveFilename(source, tab) { export function deriveFilename (source, tab) {
return `${fmtSourceTitle(source)}-${fmtSourceTitle(tab)}.json`; return `${fmtSourceTitle(source)}-${fmtSourceTitle(tab)}.json`
} }
export function bp(full) { export function bp (full) {
const blueprint = { const blueprint = {
name: R.clone(full.name), name: R.clone(full.name),
source: R.clone(full.source), source: R.clone(full.source),
dialects: R.clone(full.dialects), dialects: R.clone(full.dialects),
routes: {} routes: {}
}; }
Object.keys(full.routes).forEach(route => { Object.keys(full.routes).forEach(route => {
blueprint.routes[route] = { blueprint.routes[route] = {
options: R.clone(full.routes[route].options) options: R.clone(full.routes[route].options)
}; }
}); })
return blueprint; return blueprint
} }
export function isFunction(functionToCheck) { export function isFunction (functionToCheck) {
return ( return (
functionToCheck && {}.toString.call(functionToCheck) === "[object Function]" functionToCheck && {}.toString.call(functionToCheck) === '[object Function]'
); )
} }

View File

@@ -1,17 +1,18 @@
import {Router, next} from "express"; import { Router } from 'express'
import {mapboxAccessToken} from "../config"; import { mapboxAccessToken } from '../config'
import morgan from "morgan"; import morgan from 'morgan'
import mapbox from "./mapbox"; import mapbox from './mapbox'
export default ({config, db}) => { // eslint-disable-next-line
let routes = Router(); export default ({ config, db }) => {
let routes = Router()
/* logging middleware */ /* logging middleware */
routes.use(morgan("dev")); routes.use(morgan('dev'))
if (mapboxAccessToken) { if (mapboxAccessToken) {
routes.get("/mapbox/:z/:y/:x", mapbox(mapboxAccessToken)); routes.get('/mapbox/:z/:y/:x', mapbox(mapboxAccessToken))
} }
return routes; return routes
}; }

View File

@@ -1,16 +1,14 @@
import fetch from "node-fetch"; import fetch from 'node-fetch'
import fs from "fs";
// TODO: load images from mapbox API and store.
const baseUrl = "http://a.tiles.mapbox.com/v4/mapbox.satellite"; const baseUrl = 'http://a.tiles.mapbox.com/v4/mapbox.satellite'
export default accessToken => (req, res) => { export default accessToken => (req, res) => {
const {x, y, z} = req.params; const { x, y, z } = req.params
// const filename = `${z}-${y}-${x}.png` // const filename = `${z}-${y}-${x}.png`
// const fileStream = fs.createWriteStream(`${z}-${y}-${x}.png`) // const fileStream = fs.createWriteStream(`${z}-${y}-${x}.png`)
fetch( fetch(
`http://a.tiles.mapbox.com/v4/mapbox.satellite/${z}/${y}/${x}@2x.png?access_token=${accessToken}` `${baseUrl}/${z}/${y}/${x}@2x.png?access_token=${accessToken}`
).then(result => { ).then(result => {
res.set("Content-Type", "image/png"); res.set('Content-Type', 'image/png')
result.body.pipe(res); result.body.pipe(res)
}); })
}; }

View File

@@ -1,3 +1,4 @@
/* eslint-disable */
/** /**
* Model is a class whose sole responsibility is to save and load blueprints. * Model is a class whose sole responsibility is to save and load blueprints.
* It allows for different storage mechanisms for different kinds of blueprints. * It allows for different storage mechanisms for different kinds of blueprints.
@@ -9,7 +10,7 @@ class Model {
* @param {type} blueprint the Blueprint to be saved. * @param {type} blueprint the Blueprint to be saved.
* @return {type} Promise which returns True. * @return {type} Promise which returns True.
*/ */
save(blueprint) {} save (blueprint) {}
/** /**
* load - load a resource from a data model, using a Blueprint object as * load - load a resource from a data model, using a Blueprint object as
@@ -19,5 +20,5 @@ class Model {
* @param {type} blueprint Blueprint object (desaturated?). * @param {type} blueprint Blueprint object (desaturated?).
* @return {type} Object containing the resource data. * @return {type} Object containing the resource data.
*/ */
load(url, blueprint) {} load (url, blueprint) {}
} }

View File

@@ -1,12 +1,10 @@
import fs from "mz/fs"; import fs from 'mz/fs'
import hash from "object-hash"; import { fmtSourceTitle } from '../lib/util'
import {fmtSourceTitle} from "../lib/util";
import path from "path";
const STORAGE_DIRNAME = "temp"; const STORAGE_DIRNAME = 'temp'
class StoreJson { class StoreJson {
save(bp) { save (bp) {
return Promise.all( return Promise.all(
Object.keys(bp.routes).map(route => Object.keys(bp.routes).map(route =>
fs.writeFile( fs.writeFile(
@@ -16,50 +14,50 @@ class StoreJson {
JSON.stringify(bp.routes[route].data) JSON.stringify(bp.routes[route].data)
) )
) )
); )
} }
load(url, bp) { load (url) {
const parts = url.split("/"); const parts = url.split('/')
const fname = `${STORAGE_DIRNAME}/${parts[0]}__${parts[1]}__${ const fname = `${STORAGE_DIRNAME}/${parts[0]}__${parts[1]}__${
parts[2] parts[2]
}.json`; }.json`
return fs return fs
.exists(fname) .exists(fname)
.then(isAvailable => { .then(isAvailable => {
if (isAvailable) return fs.readFile(fname, "utf8"); if (isAvailable) return fs.readFile(fname, 'utf8')
else { else {
throw new Error("No resource exists"); throw new Error('No resource exists')
} }
}) })
.then(data => JSON.parse(data)) .then(data => JSON.parse(data))
.then(data => { .then(data => {
if (parts.length === 3) { if (parts.length === 3) {
// No lookup if the requested url doesn't have a fragment // No lookup if the requested url doesn't have a fragment
return data; return data
} else if (parts[2] === "ids") { } else if (parts[2] === 'ids') {
// Do a lookup if fragment is included to filter a relevant item // Do a lookup if fragment is included to filter a relevant item
// When the resource requested is 'ids' // When the resource requested is 'ids'
const id = parseInt(parts[3]); const id = parseInt(parts[3])
if (!isNaN(id) && id >= 0 && id < data.length) { if (!isNaN(id) && id >= 0 && id < data.length) {
return data[id]; return data[id]
} else { } else {
throw new Error(`Fragment index does not exist`); throw new Error(`Fragment index does not exist`)
} }
} else { } else {
// Do a lookup if fragment is included to filter a relevant item // Do a lookup if fragment is included to filter a relevant item
const index = parseInt(parts[3]); const index = parseInt(parts[3])
if (!isNaN(index) && index >= 0 && index < data.length) { if (!isNaN(index) && index >= 0 && index < data.length) {
console.log(data, index); console.log(data, index)
return data.filter((vl, idx) => idx === index)[0]; return data.filter((vl, idx) => idx === index)[0]
} else { } else {
throw new Error(`Fragment index does not exist`); throw new Error(`Fragment index does not exist`)
} }
} }
}); })
} }
// TODO: add method to build blueprint from data source // TODO: add method to build blueprint from data source
} }
export default StoreJson; export default StoreJson

828
yarn.lock

File diff suppressed because it is too large Load Diff