Source: gcdata/store/gcstore_gs.js

"use strict";

const fs = require("fs");
const readline = require("readline");
const Gclog = require("../../gclog/gclog.js");
const Gcstore_base = require("./gcstore_base.js");
const { google } = require("googleapis");

const SCOPES = ["https://www.googleapis.com/auth/spreadsheets"];
const TOKEN_PATH = "../gcenv/token.json";

/**
* Google Sheets data store
* @constructor 
* @extends Gcstore
* @param {Object} config - configuration
* @param {string} config.cred_path - path to credentials.json 
*/
function Gcstore_gs({cred_path = "../gcenv/credentials.json"} = {}) {
   Gcstore_base.call(this);
   this.type = "GOOGLE SHEETS";
   this.cred_path = cred_path;
   this.sheets = google.sheets("v4");
   this.client = null;
}

Gcstore_gs.prototype = Object.create(Gcstore_base.prototype);

Gcstore_gs.prototype.init = function() {
	return new Promise((resolve, reject) => {
        function authorize(credentials, callback) {
            const {client_secret, client_id, redirect_uris} = credentials.installed;
            this.client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
            
            // Check if we have previously stored a token.
            fs.readFile(TOKEN_PATH, (err, token) => {
                if (err) {
                    return get_new_token(this.client, callback);
                }
            
                this.client.setCredentials(JSON.parse(token));
                callback(this.client);
                resolve();
            }); 
        }

        function get_new_token(client, callback) {
                const authUrl = client.generateAuthUrl({
                    access_type: 'offline',
                scope: SCOPES,
            });

            Gclog.log("[GCDATA] Authorize Ground Control to use your Google Sheets account by visiting this URL:", authUrl);
            
            const rl = readline.createInterface({
                input: process.stdin,
                output: process.stdout,
            });
        
            rl.question("[GCDATA] Enter the code from that page here: ", (code) => {
                rl.close();

                client.getToken(code, (err, token) => {
                    if (err) {
                        reject("Error while trying to retrieve access token" + err);
                    }

                    client.setCredentials(token);
                    
                    // Store the token to disk for later program executions
                    fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                        if (err) {
                            reject(err);
                        }
                        
                        Gclog.log("[GCDATA] Token stored to", TOKEN_PATH);
                        resolve();
                    });
             
                    callback(client);
                });
            });
        }

        Gclog.log("[GCDATA] Using Google Sheets module");

        fs.readFile(this.cred_path, (err, content) => {
            if (err) {
                reject("Error loading client secret file:" +  err);
            }
      
            authorize.bind(this, JSON.parse(content), () => {})();
        });
    });
}

Gcstore_gs.prototype.put = async function(key, val) {
    // Try to retrieve the sheet associated with the specified key...
    // if we find it, overwrite it -- if not, create a new sheet and return its key
    // Assumes that val is a spreadsheet resource: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets
    // TODO: the overwrite case doesn't work yet!
    try {
        const res = await this.get(key);

        const request = {
            spreadsheetId: res,
            range: "DEBUG_PLACEHOLDER", // TODO: make me work
            valueInputOption: "DEBUG_PLACEHOLDER", // TODO: make me work
            resource: {}, // TODO: make me work
            auth: this.client
        };

        return await this.sheets.spreadsheets.values.update(request).data;
    } catch(err) {
        Gclog.log(`[GCDATA] (${this.type}) No record found for sheet '${key}' -- creating new sheet...`);
        const res = await this._create(val); // TODO: are errors handled here? 
        Gclog.log(`[GCDATA] (${this.type}) Success! Created sheet ${res}`);
    }
}

Gcstore_gs.prototype.get = async function(key) {
    return await this.sheets.spreadsheets.get({spreadsheetId: key, auth: this.client});
}

Gcstore_gs.prototype._create = function(resource) {
    return new Promise((resolve, reject) => {
        this.sheets.spreadsheets.create({
            auth: this.client,
            resource: resource,
            fields: "spreadsheetId"
        }, (err, spreadsheet) => {
            if (err) {
                // TODO: handle error
                reject(err);
                return;
            }
            
            resolve(spreadsheet.data.spreadsheetId);
        });
    });
}

module.exports = Gcstore_gs;