commit eb07185d6ed62d2171a2883201ab9929f9ee5721 Author: benjaminramey Date: Thu Dec 10 22:13:20 2020 -0600 Cache YNAB data responses diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..700565b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.cache/ \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..5f9bf82 --- /dev/null +++ b/config.js @@ -0,0 +1,2 @@ +exports.pat = "992a2de8f26768d1a7cbb120ad545698c8712332f1dffdc1be74a528606b8b45"; +exports.cacheDir = "./.cache" \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..c3ad324 --- /dev/null +++ b/index.js @@ -0,0 +1,71 @@ +const ynab = require("ynab"); +const { argv } = require('yargs'); +const fs = require('fs'); +const { pat, cacheDir } = require('./config'); +const ynabAPI = new ynab.API(pat); + +const ensureDataDir = async function(path) { + if (!fs.existsSync(path)) { + await fs.promises.mkdir(path); + } +} + +const getCachedJsonOrHydrate = async function(fileName, hydrator) { + const filePath = `${cacheDir}/${fileName}` + + if (fs.existsSync(filePath)) { + return JSON.parse(await fs.promises.readFile(filePath)); + } + + const data = await hydrator(); + await ensureDataDir(cacheDir); + await fs.promises.writeFile(filePath, JSON.stringify(data)); + return data; +} + +const getBudgetId = async function(budgetName) { + const budgets = await getCachedJsonOrHydrate("budgets.json", async () => { + console.log("Grabbing budgets from YNAB"); + const budgetsResponse = await ynabAPI.budgets.getBudgets(); + return budgetsResponse.data.budgets; + }); + + const budgetId = budgets.filter(b => { return b.name === budgetName; })[0].id; + return budgetId; +} + +const getAccountId = async function(budgetId, accountName) { + let accounts = await getCachedJsonOrHydrate("accounts.json", async () => { + console.log("Grabbing accounts from YNAB"); + const accountsResponse = await ynabAPI.accounts.getAccounts(budgetId); + return accountsResponse.data.accounts; + }); + + const accountId = accounts.filter(a => { return a.name === accountName; })[0].id; + return accountId; +} + +const getTransactions = async function(budgetId, accountId) { + const date = new Date(); + const jsonFile = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-transactions.json`; + let transactions = await getCachedJsonOrHydrate(jsonFile, async () => { + console.log("Grabbing transactions from YNAB"); + const transactionsResponse = await ynabAPI.transactions.getTransactionsByAccount(budgetId, accountId); + return transactionsResponse.data.transactions; + }); + + return transactions; +} + +const main = async function(budgetName, accountName) { + console.log(`Looking for account '${accountName}' in budget named '${budgetName}'.`); + + const budgetId = await getBudgetId(budgetName); + const accountId = await getAccountId(budgetId, accountName); + const transactions = await getTransactions(budgetId, accountId); + + console.log(transactions.length); + console.log(transactions[transactions.length - 1]); +}; + +main(argv.budgetName, argv.accountName); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5fd634d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,178 @@ +{ + "name": "ynabreconciler", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.1.tgz", + "integrity": "sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "portable-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/portable-fetch/-/portable-fetch-3.0.0.tgz", + "integrity": "sha1-PL9KptvFpXNLQcBBnJJzMTv9mtg=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "whatwg-fetch": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz", + "integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.2.tgz", + "integrity": "sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA==" + }, + "yargs": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.0.3.tgz", + "integrity": "sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==", + "requires": { + "cliui": "^7.0.0", + "escalade": "^3.0.2", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.1", + "yargs-parser": "^20.0.0" + } + }, + "yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-yYsjuSkjbLMBp16eaOt7/siKTjNVjMm3SoJnIg3sEh/JsvqVVDyjRKmaJV4cl+lNIgq6QEco2i3gDebJl7/vLA==" + }, + "ynab": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/ynab/-/ynab-1.20.0.tgz", + "integrity": "sha512-agn2NhLGx/hXKLh5/Y8Ri45DZvyabXJCtJ9jTHOnaocr7XAL0R5jW7ZnKupUI9dIJy3vAXln5i+KDgjQ98ieqA==", + "requires": { + "portable-fetch": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0b562f7 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "ynabreconciler", + "version": "1.0.0", + "description": "Command line tool to reconcile YNAB account", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "yargs": "^16.0.3", + "ynab": "^1.20.0" + } +}