From 2dde8b78165fc897114fe1876b14a37e06f5c457 Mon Sep 17 00:00:00 2001 From: Kevin Schaich Date: Thu, 16 Jan 2020 13:56:15 -0500 Subject: [PATCH] Add CREATE_BALANCE_SHEET feature, clean up config docs --- docs/CONFIG.md | 158 +++++++++++++++++++++++++----------- src/components/accounts.jsx | 4 +- src/lib/common.js | 1 + src/lib/google.js | 36 ++++---- src/lib/plaid.js | 18 ++-- src/scripts/migrate.js | 1 + src/scripts/mintable.js | 53 +++++++++++- src/scripts/setup.js | 2 +- 8 files changed, 193 insertions(+), 80 deletions(-) diff --git a/docs/CONFIG.md b/docs/CONFIG.md index da05f178..5c0e5a21 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -6,7 +6,85 @@ All configurations below can be made using the web configuration framework or by > **Pro Tip:** You can use Dropbox or another trusted service to sync `mintable.config.json` across your machines. Run `ln -s /mintable.config.json .` from the repo root to symlink Mintable to the cloud version. -### Automate Updates with a CI Provider +#### Table of Contents + +- [General configuration](#general-configuration) +- [Fetching Balances](#fetching-balances) +- [Fetching Transactions](#fetching-transactions) +- [Plaid](#plaid) +- [Google Sheets](#google-sheets) + +## General configuration + +#### Debug Mode + +`DEBUG` mode logs the output of each API call and function to the console. + +**Default:** + +```javascript +"DEBUG": undefined // If unspecified, defaults to false +``` + +If you want to enable debug mode, you can add the following line to your `mintable.config.json` file: + +```javascript +"DEBUG": true +``` + +#### Host + +`HOST` specifies the host for Mintable's setup server. + +**Default:** + +```javascript +"HOST": "localhost" +``` + +For example, if you want to run Mintable on a custom server and listen on `0.0.0.0`, you could add the following line to your `mintable.config.json` file: + +```javascript +"HOST": "0.0.0.0" +``` + +#### Port + +`PORT` specifies the port for Mintable's setup server. + +**Default:** + +```javascript +"PORT": 3000 +``` + +For example, if you already have an application running on port `3000` and instead want to use port `8080`, you could add the following line to your `mintable.config.json` file: + +```javascript +"PORT": "8080" +``` + +#### Account Provider + +`ACCOUNT_PROVIDER` specifies which service to use to fetch transactions. + +**Default:** + +```javascript +"ACCOUNT_PROVIDER": "plaid" +``` + +#### Spreadsheet Provider + +`SHEET_PROVIDER` specifies which service to use to automate spreadsheet updates. + +**Default:** + +```javascript +"SHEET_PROVIDER": "sheets" // "sheets" = Google Sheets +``` + +#### Automate Updates with a CI Provider This repo includes config files for both [CircleCI](https://circleci.com/) and [Travis CI](https://travis-ci.com) to run builds automatically. @@ -22,71 +100,75 @@ Run this command and paste the result into an environment variable called `MINTA > **Warning:** If you choose to use CircleCI, you should turn off **Pass secrets to builds from forked pull requests** under **Build Settings** > **Advanced Settings**. -### Start Date +## Fetching Balances -`START_DATE` specifies the lower bound for fetching transactions in `YYYY.MM.DD` format. +#### Create Balances Sheet + +`CREATE_BALANCES_SHEET` optionally fetches the balances of all your connected accounts and places them in a sheet called `Balances`. **Default:** ```javascript -"START_DATE": undefined // If end date is not specified, Mintable will fetch the last 2 months of transactions +"CREATE_BALANCES_SHEET": undefined // If unspecified, defaults to false ``` -For example, if you only want to fetch transactions which occur after or on December 1, 2018, you could add the following line to your `mintable.config.json` file: +If you want to enable this, you can add the following line to your `mintable.config.json` file: ```javascript -"START_DATE": "2018.12.01" +"CREATE_BALANCES_SHEET": true ``` -### End Date +#### Balance Columns -`END_DATE` specifies the upper bound for fetching transactions in `YYYY.MM.DD` format. +`BALANCE_COLUMNS` specifies a list of account properties (using [`_.get()` syntax](https://lodash.com/docs/4.17.11#get)) to automatically update in your `Balances` spreadsheet. All the contents of these columns will be cleared and overwritten each time you run Mintable. -**Default:** +**Default:** ```javascript -"END_DATE": undefined // If end date is not specified, Mintable will fetch up until the current date +"BALANCE_COLUMNS": ['name', 'official_name', 'type', 'balances.available', 'balances.current', 'balances.limit'] ``` -For example, if you only want to fetch transactions which occur before or on December 1, 2018, you could add the following line to your `mintable.config.json` file: +For example, if you only want to auto-populate the name and amount for each account, you could add the following line to your `mintable.config.json` file: ```javascript -"END_DATE": "2018.12.01" +"TRANSACTION_COLUMNS": ["name", "balances.current"] ``` -### Host +## Fetching Transactions -`HOST` specifies the host for Mintable's setup server. +#### Start Date -**Default:** +`START_DATE` specifies the lower bound for fetching transactions in `YYYY.MM.DD` format. + +**Default:** ```javascript -"HOST": "localhost" +"START_DATE": undefined // If end date is not specified, Mintable will fetch the last 2 months of transactions ``` -For example, if you want to run Mintable on a custom server and listen on `0.0.0.0`, you could add the following line to your `mintable.config.json` file: +For example, if you only want to fetch transactions which occur after or on December 1, 2018, you could add the following line to your `mintable.config.json` file: ```javascript -"HOST": "0.0.0.0" +"START_DATE": "2018.12.01" ``` -### Port +#### End Date -`PORT` specifies the port for Mintable's setup server. +`END_DATE` specifies the upper bound for fetching transactions in `YYYY.MM.DD` format. -**Default:** +**Default:** ```javascript -"PORT": 3000 +"END_DATE": undefined // If end date is not specified, Mintable will fetch up until the current date ``` -For example, if you already have an application running on port `3000` and instead want to use port `8080`, you could add the following line to your `mintable.config.json` file: +For example, if you only want to fetch transactions which occur before or on December 1, 2018, you could add the following line to your `mintable.config.json` file: ```javascript -"PORT": "8080" +"END_DATE": "2018.12.01" ``` -### Transaction Columns +#### Transaction Columns `TRANSACTION_COLUMNS` specifies a list of transaction properties (using [`_.get()` syntax](https://lodash.com/docs/4.17.11#get)) to automatically update in your spreadsheet. All the contents of these columns will be cleared and overwritten each time you run Mintable. @@ -102,7 +184,7 @@ For example, if you only want to auto-populate the name and amount for each tran "TRANSACTION_COLUMNS": ["name", "amount"] ``` -### Reference Columns +#### Reference Columns `REFERENCE_COLUMNS` specifies a list of additional, non-automated columns for your reference/bookkeeping purposes. Each time you run Mintable, the contents of these columns will be preserved. @@ -120,33 +202,13 @@ For example, if you want to add one column to track work expenses, and another t > **Warning:** Since reference columns are not automated by Mintable, they have the potential to get out of sync with transaction data (for example, if your bank deletes a transaction, causing a row to get removed in `TRANSACTION_COLUMNS`) -### Account Provider - -`ACCOUNT_PROVIDER` specifies which service to use to fetch transactions. - -**Default:** - -```javascript -"ACCOUNT_PROVIDER": "plaid" -``` - -### Spreadsheet Provider - -`SHEET_PROVIDER` specifies which service to use to automate spreadsheet updates. - -**Default:** - -```javascript -"SHEET_PROVIDER": "sheets" // "sheets" = Google Sheets -``` - # Provider-Specific Configuration You can see the API definitions for account & spreadsheet providers in the **[provider docs](./docs/PROVIDERS.md)**. ## Plaid -### Category Overrides +#### Category Overrides `CATEGORY_OVERRIDES` specifies a list of overrides to handle transactions that are routinely miscategorized by Plaid's servers. @@ -178,7 +240,7 @@ For example, if you want anything matching `autopay` or `e-payment` to get categ ## Google Sheets -### Template Sheet +#### Template Sheet `TEMPLATE_SHEET` specifies the template spreadsheet to use when creating a _new_ sheet for a month. diff --git a/src/components/accounts.jsx b/src/components/accounts.jsx index f7d98a80..9bd8376d 100644 --- a/src/components/accounts.jsx +++ b/src/components/accounts.jsx @@ -43,7 +43,7 @@ class Accounts extends React.Component { window.Plaid.create({ clientName: 'Mintable', env: this.props.config.PLAID_ENVIRONMENT, - product: ['auth', 'transactions'], + product: ['transactions'], key: this.props.config.PLAID_PUBLIC_KEY, onExit: this.handleOnExit, onSuccess: this.handleOnSuccess, @@ -99,7 +99,7 @@ class Accounts extends React.Component { const sortSheets = order => wrapPromise( - promisify( - sheets.spreadsheets.batchUpdate, - { - spreadsheetId: process.env.SHEETS_SHEET_ID, - resource: { - requests: _.flatten( - _.map(order, sheetId => [ - { - updateSheetProperties: { - properties: { sheetId: sheetId[0], index: sheetId[1] }, - fields: 'index' - } + promisify(sheets.spreadsheets.batchUpdate, { + spreadsheetId: process.env.SHEETS_SHEET_ID, + resource: { + requests: _.flatten( + _.map(order, sheetId => [ + { + updateSheetProperties: { + properties: { sheetId: sheetId[0], index: sheetId[1] }, + fields: 'index' } - ]) - ) - } - }, - `Sorting sheets` - ) + } + ]) + ) + } + }), + `Sorting sheets` ) const updateSheets = async (updates, options) => { @@ -248,5 +245,6 @@ module.exports = { renameSheet, clearRanges, updateRanges, - updateSheets + updateSheets, + formatSheets } diff --git a/src/lib/plaid.js b/src/lib/plaid.js index f0e3c790..77c3b43c 100644 --- a/src/lib/plaid.js +++ b/src/lib/plaid.js @@ -60,14 +60,16 @@ const fetchBalances = options => { const fetchBalanceForAccount = account => { return wrapPromise( - PLAID_CLIENT.getBalance(account.token).then(data => { - return { - ...data, - nickname: account.nickname - } - }).catch(error => { - return { nickname: account.nickname, error: JSON.stringify(error, null, 2) } - }), + PLAID_CLIENT.getBalance(account.token) + .then(data => { + return { + ...data, + nickname: account.nickname + } + }) + .catch(error => { + return { nickname: account.nickname, error: JSON.stringify(error, null, 2) } + }), `Fetching balance for account ${account.nickname}`, options ) diff --git a/src/scripts/migrate.js b/src/scripts/migrate.js index cb12be13..8d78bd77 100644 --- a/src/scripts/migrate.js +++ b/src/scripts/migrate.js @@ -25,6 +25,7 @@ const dotenv = require('dotenv') 'CATEGORY_OVERRIDES', 'TRANSACTION_PROVIDER', 'SPREADSHEET_PROVIDER', + 'BALANCE_COLUMNS', 'TRANSACTION_COLUMNS', 'REFERENCE_COLUMNS' ] diff --git a/src/scripts/mintable.js b/src/scripts/mintable.js index fb73485e..3f552934 100644 --- a/src/scripts/mintable.js +++ b/src/scripts/mintable.js @@ -2,10 +2,60 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // IMPORTS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - + await require('../lib/common').maybeWriteDefaultConfig() await require('../lib/common').getConfigEnv() const { parse, differenceInMonths, subMonths, startOfMonth, addMonths, format } = require('date-fns') const _ = require('lodash') + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // FETCH BALANCES + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + if (process.env.CREATE_BALANCES_SHEET) { + let balances + + switch (process.env.ACCOUNT_PROVIDER) { + case 'plaid': + balances = _.keyBy( + _.flatten(_.map(await require('../lib/plaid').fetchBalances(), item => item.accounts)), + 'account_id' + ) + break + default: + break + } + + switch (process.env.SHEET_PROVIDER) { + case 'sheets': + let balanceSheet = _.find( + await require('../lib/google').getSheets(process.env.SHEETS_SHEET_ID), + sheet => sheet.properties.title === 'Balances' + ) + if (!balanceSheet) { + await require('../lib/google').addSheet('Balances') + balanceSheet = _.find( + await require('../lib/google').getSheets(process.env.SHEETS_SHEET_ID), + sheet => sheet.properties.title === 'Balances' + ) + } + + const cleanedBalances = _.map(_.values(balances), account => _.at(account, process.env.BALANCE_COLUMNS)) + + await require('../lib/google').updateRanges({ + range: `Balances!A1:${alphabet[process.env.BALANCE_COLUMNS.length - 1]}${_.keys(balances).length + 1}`, + values: [process.env.BALANCE_COLUMNS].concat(cleanedBalances) + }) + + await require('../lib/google').formatSheets( + [balanceSheet.properties.sheetId], + process.env.BALANCE_COLUMNS.length + ) + break + default: + break + } + } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // FETCH TRANSACTIONS @@ -104,7 +154,6 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Column headers in spreadsheets are defined by letters A-Z, this list gets us indexes for each letter - const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') const options = { // First automated column Mintable populates from transaction data firstTransactionColumn: alphabet[0], diff --git a/src/scripts/setup.js b/src/scripts/setup.js index 1d0aa405..54c5cbc9 100644 --- a/src/scripts/setup.js +++ b/src/scripts/setup.js @@ -56,7 +56,7 @@ maybeWriteDefaultConfig().then(() => { switch (process.env.ACCOUNT_PROVIDER) { case 'plaid': return require('../lib/plaid') - .fetchBalances({quiet: true}) + .fetchBalances({ quiet: true }) .then(balances => res.json({ data: balances })) .catch(error => res.json(error)) default: