diff --git a/src/lib/Controller.js b/src/lib/Controller.js index de37f97..0f97c35 100644 --- a/src/lib/Controller.js +++ b/src/lib/Controller.js @@ -9,7 +9,7 @@ class Controller { this.fetchers = fetchers } - sheetExists (sheet) { + _sheetExists (sheet) { return (Object.keys(this.fetchers).indexOf(sheet) >= 0) } @@ -25,6 +25,7 @@ class Controller { return this.fetchers[sheet].update() }) ).then(results => { + console.log(results) if (results.every(r => r)) { return copy.success.update } else { @@ -34,7 +35,7 @@ class Controller { } retrieve (sheet, tab, resource) { - if (this.sheetExists(sheet)) { + if (this._sheetExists(sheet)) { const fetcher = this.fetchers[sheet] return fetcher.retrieve(tab, resource) } else { @@ -43,7 +44,7 @@ class Controller { } retrieveFrag (sheet, tab, resource, frag) { - if (this.sheetExists(sheet)) { + if (this._sheetExists(sheet)) { const fetcher = this.fetchers[sheet] return fetcher.retrieveFrag(tab, resource, frag) } else { diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index 89477b4..b0a369c 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -1,12 +1,11 @@ // FetcherTwo class interfaces with Google Sheet, and saves to a specified db import { google } from 'googleapis' import { - fmtSheetTitle, + fmtName, fmtBlueprinterTitles, - bp, isFunction } from './util' -import { byRow } from './blueprinters' +import { createHash } from 'crypto' import R from 'ramda' class Fetcher { @@ -28,39 +27,62 @@ class Fetcher { */ this.sheetName = sheetName + /* + * A unique ID for the Fetcher to identify its elements in the model layer + */ + this.id = createHash('md5').update(sheetName).update(sheetId).digest('hex') + /* * These are the available tabs for storing and retrieving data. * Each blueprinter is a function that returns a Blueprint from a * list of lists (which will be retrieved from gsheets). */ this.blueprinters = fmtBlueprinterTitles(blueprinters) - this.blueprints = {} - Object.keys(this.blueprinters).forEach(key => { - this.blueprints[key] = null - }) + + /* + * This object is the canonical represenation for the data that a Fetcher + * proxies. When the fetcher is initialized, its model layer (db) is indexed, + * and this object populated accordingly. Whenever the fetcher updates, this + * data structure updates as well. It is the model layer that determines the + * performance of indexing the blueprints. + */ + this.blueprints = this._indexDbForBlueprints() + .then(res => res) /* * Google API setup */ - this.sheets = google.sheets('v4') + this.API = google.sheets('v4') this.auth = null - /** - * saveBp is a curried function that takes in a title and - * a blueprinter. NB: it sits here in the constructor as - * I am not sure how to curry a class method with Ramda. - */ - this._saveBp = R.curry((tab, title, data, blueprinter) => { - const saturatedBp = blueprinter( - tab, - this.sheetName, - this.sheetId, - data + /** curry to allow convenient syntax with map */ + this._saveViaBlueprinter = R.curry(this._saveViaBlueprinter) + } + + /** save data under a given tab name via its blueprinter, which generates + * its resource name. Note that this is curried in the constructor. + */ + _saveViaBlueprinter (tab, data, blueprinter) { + const saturatedBp = blueprinter( + tab, + this.sheetName, + this.sheetId, + data + ) + + return Promise.all( + Object.keys(saturatedBp.routes).map(route => + this.db.save(`${this.id}/${tab}/${route}`, saturatedBp.routes[route].data) ) - const blueprint = bp(saturatedBp) // TODO: come up with better semantics. - this.blueprints[title] = blueprint - return this.db.save(saturatedBp) - }) + ) + } + + /** index the db and produce appropriate blueprints structure **/ + _indexDbForBlueprints () { + return this.db.index() + .then(res => { + return res + }) } /** returns a Promise that resolves if access is granted to the account, and rejects otherwise. */ @@ -85,14 +107,14 @@ class Fetcher { update () { let tabTitles /* 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.API.spreadsheets .get({ auth: this.auth, spreadsheetId: this.sheetId }) .then(response => { tabTitles = response.data.sheets.map(sheet => sheet.properties.title) - return this.sheets.spreadsheets.values.batchGet({ + return this.API.spreadsheets.values.batchGet({ auth: this.auth, spreadsheetId: this.sheetId, ranges: tabTitles @@ -100,12 +122,15 @@ class Fetcher { }) .then(results => { const tabData = results.data.valueRanges + return Promise.all( tabData.map((tab, idx) => { const { values } = tab + if (values === undefined) { return Promise.resolve({}) } + const name = tabTitles[idx] return this.save(name, values) }) @@ -115,31 +140,34 @@ class Fetcher { .catch(() => false) } - save (tab, data) { - const title = fmtSheetTitle(tab) - if (Object.keys(this.blueprinters).indexOf(title) > -1) { - const bpConfig = this.blueprinters[title] + save (_tab, data) { + const tab = fmtName(_tab) + if (Object.keys(this.blueprinters).indexOf(tab) > -1) { + const bpConfig = this.blueprinters[tab] if (isFunction(bpConfig)) { - return this._saveBp(tab, title, data, bpConfig) + // if bpConfig specifies a single blueprinter + return this._saveViaBlueprinter(tab, data, bpConfig) } else { - return bpConfig.map(this._saveBp(tab, title, data)) + // if bpConfig specifies an array of blueprinters + return bpConfig.map(this._saveViaBlueprinter(tab, data)) } } else { - // If it can't find a blueprinter for the tab title, default to byRow - return this.db.save(byRow(tab, this.sheetName, this.sheetId, data)) + // NB: if a blueprinter is not specified for a tab, + // just skip it. + return true } } // NB: could combine these functions by checking kwargs length retrieve (tab, resource) { - const title = fmtSheetTitle(tab) + const title = fmtName(tab) const url = `${this.sheetName}/${tab}/${resource}` return this.db.load(url, this.blueprints[title]) } retrieveFrag (tab, resource, frag) { - const title = fmtSheetTitle(tab) + const title = fmtName(tab) const url = `${this.sheetName}/${tab}/${resource}/${frag}` return this.db.load(url, this.blueprints[title]) } diff --git a/src/lib/util.js b/src/lib/util.js index 5e71f3a..f84bc59 100755 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -55,24 +55,24 @@ export const idxSearcher = R.curry((attrName, searchValue, myArray) => { /* more site specific functions. TODO: maybe move to another folder? */ -export function fmtSheetTitle (name) { +export function fmtName (name) { return name.replaceAll(' ', '-').toLowerCase() } export function fmtBlueprinterTitles (tabs) { const obj = {} Object.keys(tabs).forEach(tab => { - const name = fmtSheetTitle(tab) + const name = fmtName(tab) obj[name] = tabs[tab] }) return obj } export function deriveFilename (sheet, tab) { - return `${fmtSheetTitle(sheet)}-${fmtSheetTitle(tab)}.json` + return `${fmtName(sheet)}-${fmtName(tab)}.json` } -export function bp (full) { +export function desaturate (full) { const blueprint = { name: R.clone(full.name), sheet: R.clone(full.sheet), diff --git a/src/models/StoreJson.js b/src/models/StoreJson.js index e94e2f8..5f5ab28 100644 --- a/src/models/StoreJson.js +++ b/src/models/StoreJson.js @@ -1,20 +1,19 @@ import fs from 'mz/fs' -import { fmtSheetTitle } from '../lib/util' import copy from '../copy/en' const STORAGE_DIRNAME = 'temp' class StoreJson { - save (bp) { - return Promise.all( - Object.keys(bp.routes).map(route => - fs.writeFile( - `${STORAGE_DIRNAME}/${fmtSheetTitle( - bp.sheet.name - )}__${fmtSheetTitle(bp.name)}__${route}.json`, - JSON.stringify(bp.routes[route].data) - ) - ) + index () { + return Promise.resolve({}) + } + + save (url, data) { + const parts = url.split('/') + + return fs.writeFile( + `${STORAGE_DIRNAME}/${parts[0]}__${parts[1]}__${parts[2]}.json`, + JSON.stringify(data) ) }