mirror of
https://github.com/bellingcat/datasheet-server.git
synced 2026-06-13 22:08:31 +03:00
149 lines
4.3 KiB
JavaScript
149 lines
4.3 KiB
JavaScript
// FetcherTwo class interfaces with Google Sheet, and saves to a specified db
|
|
import { google } from 'googleapis'
|
|
import {
|
|
fmtSheetTitle,
|
|
fmtBlueprinterTitles,
|
|
bp,
|
|
isFunction
|
|
} from './util'
|
|
import { byRow } from './blueprinters'
|
|
import R from 'ramda'
|
|
|
|
class Fetcher {
|
|
constructor (db, sheetName, sheetId, blueprinters) {
|
|
/*
|
|
* 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.
|
|
*/
|
|
this.db = db
|
|
|
|
/*
|
|
* ID of the Google Sheet where the data is sheetd. Note that the privateKey.clientEmail
|
|
* loaded here must be added to the sheet as an editor.
|
|
*/
|
|
this.sheetId = sheetId
|
|
|
|
/*
|
|
* The name of the sheet. This will prefix tabs saved in the database.
|
|
*/
|
|
this.sheetName = sheetName
|
|
|
|
/*
|
|
* 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
|
|
})
|
|
|
|
/*
|
|
* Google API setup
|
|
*/
|
|
this.sheets = 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
|
|
)
|
|
const blueprint = bp(saturatedBp) // TODO: come up with better semantics.
|
|
this.blueprints[title] = blueprint
|
|
return this.db.save(saturatedBp)
|
|
})
|
|
}
|
|
|
|
/** returns a Promise that resolves if access is granted to the account, and rejects otherwise. */
|
|
authenticate (clientEmail, privateKey) {
|
|
const googleAuth = new google.auth.JWT(clientEmail, null, privateKey, [
|
|
'https://www.googleapis.com/auth/spreadsheets'
|
|
])
|
|
this.auth = googleAuth
|
|
const { sheetId } = this
|
|
|
|
return new Promise((resolve, reject) => {
|
|
googleAuth.authorize(function (err) {
|
|
if (err) {
|
|
reject(err)
|
|
} else {
|
|
resolve(`Connected to ${sheetId}.`)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
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
|
|
.get({
|
|
auth: this.auth,
|
|
spreadsheetId: this.sheetId
|
|
})
|
|
.then(response => {
|
|
tabTitles = response.data.sheets.map(sheet => sheet.properties.title)
|
|
return this.sheets.spreadsheets.values.batchGet({
|
|
auth: this.auth,
|
|
spreadsheetId: this.sheetId,
|
|
ranges: tabTitles
|
|
})
|
|
})
|
|
.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)
|
|
})
|
|
)
|
|
})
|
|
.then(() => true)
|
|
.catch(() => false)
|
|
}
|
|
|
|
save (tab, data) {
|
|
const title = fmtSheetTitle(tab)
|
|
if (Object.keys(this.blueprinters).indexOf(title) > -1) {
|
|
const bpConfig = this.blueprinters[title]
|
|
|
|
if (isFunction(bpConfig)) {
|
|
return this._saveBp(tab, title, data, bpConfig)
|
|
} else {
|
|
return bpConfig.map(this._saveBp(tab, title, 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: could combine these functions by checking kwargs length
|
|
retrieve (tab, resource) {
|
|
const title = fmtSheetTitle(tab)
|
|
const url = `${this.sheetName}/${tab}/${resource}`
|
|
return this.db.load(url, this.blueprints[title])
|
|
}
|
|
|
|
retrieveFrag (tab, resource, frag) {
|
|
const title = fmtSheetTitle(tab)
|
|
const url = `${this.sheetName}/${tab}/${resource}/${frag}`
|
|
return this.db.load(url, this.blueprints[title])
|
|
}
|
|
}
|
|
|
|
export default Fetcher
|