mintable
Version:
Automate your personal finances – for free, with no ads, and no data collection.
422 lines (421 loc) • 23.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var googleapis_1 = require("googleapis");
var config_1 = require("../../common/config");
var integrations_1 = require("../../types/integrations");
var logging_1 = require("../../common/logging");
var lodash_1 = require("lodash");
var date_fns_1 = require("date-fns");
var GoogleIntegration = /** @class */ (function () {
function GoogleIntegration(config) {
var _this = this;
this.getAuthURL = function () {
return _this.client.generateAuthUrl({
scope: _this.googleConfig.credentials.scope
});
};
this.getAccessTokens = function (authCode) {
return _this.client.getToken(authCode).then(function (response) { return response.tokens; });
};
this.saveAccessTokens = function (tokens) {
config_1.updateConfig(function (config) {
var googleConfig = config.integrations[integrations_1.IntegrationId.Google];
googleConfig.credentials.accessToken = tokens.access_token;
googleConfig.credentials.refreshToken = tokens.refresh_token;
googleConfig.credentials.tokenType = tokens.token_type;
googleConfig.credentials.expiryDate = tokens.expiry_date;
config.integrations[integrations_1.IntegrationId.Google] = googleConfig;
return config;
});
};
this.getSheets = function (documentId) {
return _this.sheets
.get({ spreadsheetId: documentId || _this.googleConfig.documentId })
.then(function (res) {
logging_1.logInfo("Fetched " + res.data.sheets.length + " sheets.", res.data.sheets);
return res.data.sheets;
})["catch"](function (error) {
logging_1.logError("Error fetching sheets for spreadsheet " + _this.googleConfig.documentId + ".", error);
return [];
});
};
this.copySheet = function (title, sourceDocumentId) { return __awaiter(_this, void 0, void 0, function () {
var sheets, sourceSheetId;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getSheets(sourceDocumentId || this.googleConfig.documentId)];
case 1:
sheets = _a.sent();
try {
sourceSheetId = sheets.find(function (sheet) { return sheet.properties.title === title; }).properties.sheetId;
}
catch (error) {
logging_1.logError("Error finding template sheet " + title + " in document " + sourceDocumentId + ".", { error: error, sheets: sheets });
}
return [2 /*return*/, this.sheets.sheets
.copyTo({
spreadsheetId: sourceDocumentId || this.googleConfig.documentId,
sheetId: sourceSheetId,
requestBody: { destinationSpreadsheetId: this.googleConfig.documentId }
})
.then(function (res) {
logging_1.logInfo("Copied sheet " + title + ".", res.data);
return res.data;
})["catch"](function (error) {
logging_1.logError("Error copying sheet " + title + ".", error);
return {};
})];
}
});
}); };
this.addSheet = function (title) {
return _this.sheets
.batchUpdate({
spreadsheetId: _this.googleConfig.documentId,
requestBody: { requests: [{ addSheet: { properties: { title: title } } }] }
})
.then(function (res) {
logging_1.logInfo("Added sheet " + title + ".", res.data);
return res.data.replies[0].addSheet.properties;
})["catch"](function (error) {
logging_1.logError("Error adding sheet " + title + ".", error);
return {};
});
};
this.renameSheet = function (oldTitle, newTitle) { return __awaiter(_this, void 0, void 0, function () {
var sheets, sheetId;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getSheets()];
case 1:
sheets = _a.sent();
sheetId = sheets.find(function (sheet) { return sheet.properties.title === oldTitle; }).properties.sheetId;
return [2 /*return*/, this.sheets
.batchUpdate({
spreadsheetId: this.googleConfig.documentId,
requestBody: {
requests: [
{
updateSheetProperties: {
properties: { sheetId: sheetId, title: newTitle },
fields: 'title'
}
}
]
}
})
.then(function (res) {
logging_1.logInfo("Renamed sheet " + oldTitle + " to " + newTitle + ".", res.data);
return res.data.replies;
})["catch"](function (error) {
logging_1.logError("Error renaming sheet " + oldTitle + " to " + newTitle + ".", error);
return [];
})];
}
});
}); };
this.translateRange = function (range) {
return "'" + range.sheet + "'!" + range.start.toUpperCase() + ":" + range.end.toUpperCase();
};
this.translateRanges = function (ranges) { return ranges.map(_this.translateRange); };
this.clearRanges = function (ranges) {
var translatedRanges = _this.translateRanges(ranges);
return _this.sheets.values
.batchClear({
spreadsheetId: _this.googleConfig.documentId,
requestBody: { ranges: translatedRanges }
})
.then(function (res) {
logging_1.logInfo("Cleared " + ranges.length + " range(s): " + translatedRanges + ".", res.data);
return res.data;
})["catch"](function (error) {
logging_1.logError("Error clearing " + ranges.length + " range(s): " + translatedRanges + ".", error);
return {};
});
};
this.updateRanges = function (dataRanges) {
var data = dataRanges.map(function (dataRange) { return ({
range: _this.translateRange(dataRange.range),
values: dataRange.data
}); });
return _this.sheets.values
.batchUpdate({
spreadsheetId: _this.googleConfig.documentId,
requestBody: {
valueInputOption: "USER_ENTERED",
data: data
}
})
.then(function (res) {
logging_1.logInfo("Updated " + data.length + " range(s): " + data.map(function (r) { return r.range; }) + ".", res.data);
return res.data;
})["catch"](function (error) {
logging_1.logError("Error updating " + data.length + " range(s): " + data.map(function (r) { return r.range; }) + ".", error);
return {};
});
};
this.sortSheets = function () { return __awaiter(_this, void 0, void 0, function () {
var sheets, ordered;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getSheets()];
case 1:
sheets = _a.sent();
ordered = lodash_1.sortBy(sheets, function (sheet) { return sheet.properties.title; }).reverse();
return [2 /*return*/, this.sheets
.batchUpdate({
spreadsheetId: this.googleConfig.documentId,
requestBody: {
requests: ordered.map(function (sheet, i) { return ({
updateSheetProperties: {
properties: { sheetId: sheet.properties.sheetId, index: i },
fields: 'index'
}
}); })
}
})
.then(function (res) {
logging_1.logInfo("Updated indices for " + sheets.length + " sheets.", res.data);
return res.data;
})["catch"](function (error) {
logging_1.logError("Error updating indices for " + sheets.length + " sheets.", error);
return {};
})];
}
});
}); };
this.formatSheets = function () { return __awaiter(_this, void 0, void 0, function () {
var sheets;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getSheets()];
case 1:
sheets = _a.sent();
return [2 /*return*/, this.sheets
.batchUpdate({
spreadsheetId: this.googleConfig.documentId,
requestBody: {
requests: sheets
.map(function (sheet) { return [
{
repeatCell: {
range: { sheetId: sheet.properties.sheetId, startRowIndex: 0, endRowIndex: 1 },
cell: {
userEnteredFormat: {
backgroundColor: { red: 0.2, green: 0.2, blue: 0.2 },
horizontalAlignment: 'CENTER',
textFormat: {
foregroundColor: { red: 1.0, green: 1.0, blue: 1.0 },
bold: true
}
}
},
fields: 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)'
}
},
{
updateSheetProperties: {
properties: {
sheetId: sheet.properties.sheetId,
gridProperties: { frozenRowCount: 1 }
},
fields: 'gridProperties.frozenRowCount'
}
},
{
autoResizeDimensions: {
dimensions: {
sheetId: sheet.properties.sheetId,
dimension: 'COLUMNS',
startIndex: 0,
endIndex: sheet.properties.gridProperties.columnCount
}
}
}
]; })
.flat(10)
}
})
.then(function (res) {
logging_1.logInfo("Updated formatting for " + sheets.length + " sheets.", res.data);
return res.data;
})["catch"](function (error) {
logging_1.logError("Error updating formatting for " + sheets.length + " sheets.", error);
return {};
})];
}
});
}); };
this.getRowWithDefaults = function (row, columns, defaultValue) {
if (defaultValue === void 0) { defaultValue = null; }
return columns.map(function (key) {
if (row && row.hasOwnProperty(key)) {
if (key === 'date') {
return date_fns_1.format(row[key], _this.googleConfig.dateFormat || 'yyyy.MM.dd');
}
return row[key];
}
return defaultValue;
});
};
this.updateSheet = function (sheetTitle, rows, columns, useTemplate) { return __awaiter(_this, void 0, void 0, function () {
var sheets, existing, copied, columnHeaders, range, data;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getSheets()];
case 1:
sheets = _a.sent();
existing = sheets.find(function (sheet) { return sheet.properties.title === sheetTitle; });
if (!(existing === undefined)) return [3 /*break*/, 6];
if (!(this.googleConfig.template && useTemplate === true)) return [3 /*break*/, 4];
return [4 /*yield*/, this.copySheet(this.googleConfig.template.sheetTitle, this.googleConfig.template.documentId)];
case 2:
copied = _a.sent();
return [4 /*yield*/, this.renameSheet(copied.title, sheetTitle)];
case 3:
_a.sent();
return [3 /*break*/, 6];
case 4: return [4 /*yield*/, this.addSheet(sheetTitle)];
case 5:
_a.sent();
_a.label = 6;
case 6:
columns = columns || Object.keys(rows[0]);
columnHeaders = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
range = {
sheet: sheetTitle,
start: "A1",
end: "" + columnHeaders[columns.length > 0 ? columns.length - 1 : 1] + (rows.length + 1)
};
data = [columns].concat(rows.map(function (row) { return _this.getRowWithDefaults(row, columns); }));
return [4 /*yield*/, this.clearRanges([range])];
case 7:
_a.sent();
return [2 /*return*/, this.updateRanges([{ range: range, data: data }])];
}
});
}); };
this.updateTransactions = function (accounts) { return __awaiter(_this, void 0, void 0, function () {
var transactions, groupedTransactions, _a, _b, _i, month;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
transactions = lodash_1.sortBy(accounts.map(function (account) { return account.transactions; }).flat(10), 'date');
groupedTransactions = lodash_1.groupBy(transactions, function (transaction) { return date_fns_1.formatISO(date_fns_1.startOfMonth(transaction.date)); });
_a = [];
for (_b in groupedTransactions)
_a.push(_b);
_i = 0;
_c.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 4];
month = _a[_i];
return [4 /*yield*/, this.updateSheet(date_fns_1.format(date_fns_1.parseISO(month), this.googleConfig.dateFormat || 'yyyy.MM'), groupedTransactions[month], this.config.transactions.properties, true)];
case 2:
_c.sent();
_c.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
// Sort Sheets
return [4 /*yield*/, this.sortSheets()
// Format, etc.
];
case 5:
// Sort Sheets
_c.sent();
// Format, etc.
return [4 /*yield*/, this.formatSheets()];
case 6:
// Format, etc.
_c.sent();
logging_1.logInfo('You can view your sheet here:\n');
console.log("https://docs.google.com/spreadsheets/d/" + this.googleConfig.documentId);
return [2 /*return*/];
}
});
}); };
this.updateBalances = function (accounts) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// Update Account Balances Sheets
return [4 /*yield*/, this.updateSheet('Balances', accounts, this.config.balances.properties)
// Sort Sheets
];
case 1:
// Update Account Balances Sheets
_a.sent();
// Sort Sheets
return [4 /*yield*/, this.sortSheets()
// Format, etc.
];
case 2:
// Sort Sheets
_a.sent();
// Format, etc.
return [4 /*yield*/, this.formatSheets()];
case 3:
// Format, etc.
_a.sent();
logging_1.logInfo('You can view your sheet here:\n');
console.log("https://docs.google.com/spreadsheets/d/" + this.googleConfig.documentId);
return [2 /*return*/];
}
});
}); };
this.config = config;
this.googleConfig = config.integrations[integrations_1.IntegrationId.Google];
this.client = new googleapis_1.google.auth.OAuth2(this.googleConfig.credentials.clientId, this.googleConfig.credentials.clientSecret, this.googleConfig.credentials.redirectUri);
this.client.setCredentials({
access_token: this.googleConfig.credentials.accessToken,
refresh_token: this.googleConfig.credentials.refreshToken,
token_type: this.googleConfig.credentials.tokenType,
expiry_date: this.googleConfig.credentials.expiryDate
});
this.sheets = googleapis_1.google.sheets({
version: 'v4',
auth: this.client
}).spreadsheets;
}
return GoogleIntegration;
}());
exports.GoogleIntegration = GoogleIntegration;