This repository has been archived by the owner on Jan 5, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 57a8273
Showing
19 changed files
with
3,759 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Include any files or directories that you don't want to be copied to your | ||
# container here (e.g., local build artifacts, temporary files, etc.). | ||
# | ||
# For more help, visit the .dockerignore file reference guide at | ||
# https://docs.docker.com/go/build-context-dockerignore/ | ||
|
||
**/.classpath | ||
**/.dockerignore | ||
**/.env | ||
**/.git | ||
**/.gitignore | ||
**/.project | ||
**/.settings | ||
**/.toolstarget | ||
**/.vs | ||
**/.vscode | ||
**/.next | ||
**/.cache | ||
**/*.*proj.user | ||
**/*.dbmdl | ||
**/*.jfm | ||
**/charts | ||
**/docker-compose* | ||
**/compose* | ||
**/Dockerfile* | ||
**/node_modules | ||
**/npm-debug.log | ||
**/obj | ||
**/secrets.dev.yaml | ||
**/values.dev.yaml | ||
**/build | ||
**/dist | ||
LICENSE | ||
README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Your Tink keys, which can be found in the Tink Dashboard. | ||
# https://tink.com/ | ||
|
||
TINK_CLIENT_ID= | ||
TINK_CLIENT_SECRET= | ||
TINK_USER_ID= | ||
TINK_ACTOR_ID= | ||
|
||
# Comma-separated. | ||
# both need to have the same length | ||
TINK_ACCOUNT_MAP= | ||
ACTUAL_ACCOUNT_MAP= | ||
|
||
ACTUAL_SERVER_URL="server url" | ||
ACTUAL_SERVER_PASSWORD="server password" | ||
ACTUAL_SYNC_ID= | ||
|
||
CRON_EXPRESSION= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
name: Docker Image Publish | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
tags: [ 'v*.*.*' ] | ||
pull_request: | ||
branches: [ "main" ] | ||
|
||
jobs: | ||
build: | ||
|
||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- | ||
name: Checkout | ||
uses: actions/checkout@v3 | ||
# Workaround: https://github.com/docker/build-push-action/issues/461 | ||
- name: Setup Docker buildx | ||
uses: docker/setup-buildx-action@v2 | ||
|
||
- name: Log into registry | ||
uses: docker/login-action@v2 | ||
with: | ||
username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
||
- name: Extract Docker metadata | ||
id: meta | ||
uses: docker/metadata-action@v4 | ||
with: | ||
images: rodriguestiago0/tinkactual | ||
|
||
|
||
- name: Build and push Docker image | ||
id: build-and-push | ||
uses: docker/build-push-action@v4 | ||
with: | ||
context: . | ||
file: ./Dockerfile | ||
push: ${{ github.event_name != 'pull_request' }} # Don't push on PR | ||
tags: ${{ steps.meta.outputs.tags }} | ||
labels: ${{ steps.meta.outputs.labels }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules/ | ||
.vscode/ | ||
.env | ||
temp_data_actual/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
ARG NODE_VERSION=20.11.1 | ||
|
||
FROM node:${NODE_VERSION}-alpine AS BUILD_IMAGE | ||
|
||
WORKDIR /usr/src/app | ||
|
||
COPY package*.json ./ | ||
|
||
RUN npm install --omit=dev | ||
RUN npm ci --omit=dev | ||
|
||
COPY . . | ||
|
||
FROM node:${NODE_VERSION}-alpine AS RUNNER_IMAGE | ||
|
||
WORKDIR /usr/src/app | ||
|
||
COPY --from=BUILD_IMAGE /usr/src/app/node_modules ./node_modules | ||
ADD . . | ||
ADD package*.json ./ | ||
|
||
|
||
RUN chmod +x index-cron.js | ||
|
||
ENV NODE_ENV production | ||
|
||
# Run the application. | ||
CMD node index-cron.js | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2024 rodriguestiago0 | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
### Building and running your application | ||
|
||
When you're ready, start your application by running: | ||
`docker compose up --build`. | ||
|
||
Docker-comple.yaml example: | ||
``` | ||
version: '3' | ||
services: | ||
actual_server: | ||
container_name: tinkactual | ||
image: docker.io/rodriguestiago0/tinkactual | ||
ports: | ||
- '5006:5006' | ||
environment: | ||
- PUID=1003 | ||
- PGID=100 | ||
- TZ=Europe/Lisbon | ||
- TINK_CLIENT_ID= | ||
- TINK_CLIENT_SECRET= | ||
- TINK_USER_ID= | ||
- TINK_ACTOR_ID= | ||
- TINK_ACCOUNT_MAP= #comma separated vlue (Both TINK_ACCOUNT_MAP and ACTUAL_ACCOUNT_MAP need to have the same size) | ||
- ACTUAL_ACCOUNT_MAP= #comma separated vlue | ||
- ACTUAL_SERVER_URL= | ||
- ACTUAL_SERVER_PASSWORD= | ||
- ACTUAL_SYNC_ID= | ||
- CRON_EXPRESSION= # default value is "0 */4 * * *" | ||
restart: unless-stopped | ||
``` | ||
|
||
``` | ||
docker run -d --name tinkactual \ | ||
- e 'TINK_CLIENT_ID=' \ | ||
- e 'TINK_CLIENT_SECRET=' \ | ||
- e 'TINK_USER_ID=' \ | ||
- e 'TINK_ACTOR_ID=' \ | ||
- e 'TINK_ACCOUNT_MAP=' \ | ||
- e 'ACTUAL_ACCOUNT_MAP=' \ | ||
- e 'ACTUAL_SERVER_URL= ' \ | ||
- e 'ACTUAL_SERVER_PASSWORD=' \ | ||
- e 'ACTUAL_SYNC_ID=' \ | ||
- e CRON_EXPRESSION= # default value is "0 */4 * '* *"' \ | ||
--restart=on-failure rodriguestiago0/tinkactual:latest | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# tinkactual | ||
|
||
## Setup | ||
|
||
- Clone this repo! | ||
- Install dependencies: `npm ci` | ||
- Copy `.sample.env` to `.env` and fill in the blanks | ||
- Run `check`: `node index.js check`, this will check the balance between your Actual Budget account and the accounts setup on tink | ||
- Run `import`: `node index.js import`, this will import all transactions to Actual | ||
|
||
## Some things worth noting | ||
|
||
The intial transaction import does not have a starting balance, so you will need to manually add that to Actual Budget. | ||
|
||
You need to manually create the accounts inside Actual, and then map them to the accounts you setup in Tink. | ||
|
||
## Commands | ||
|
||
|
||
``` | ||
Usage | ||
$ myedenredactual <command> <flags> | ||
Commands & Options | ||
ls List currently syncing accounts | ||
import Sync bank accounts to Actual Budget | ||
config Print the location of actualplaid the config file | ||
--version Print the version of actualplaid being used | ||
Options for all commands | ||
--user, -u Specify the user to load configs for | ||
Examples | ||
$ myedenredactual import --account="My Checking" --since="2020-05-28" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
const { getAppConfigFromEnv } = require("./config"); | ||
const actual = require("@actual-app/api"); | ||
const fs = require("fs"); | ||
const inquirer = require("inquirer"); | ||
let { q, runQuery } = require('@actual-app/api'); | ||
|
||
|
||
const appConfig = getAppConfigFromEnv(); | ||
|
||
/** | ||
* | ||
* @returns {Promise<typeof actual>} | ||
*/ | ||
async function initialize(config) { | ||
try { | ||
const tmp_dir = `./temp_data_actual/${config.get("user")}` | ||
fs.mkdirSync(tmp_dir, { recursive: true }); | ||
await actual.init({ | ||
serverURL: appConfig.ACTUAL_SERVER_URL, | ||
password: appConfig.ACTUAL_SERVER_PASSWORD, | ||
dataDir: tmp_dir | ||
}); | ||
|
||
let id = config.get("budget_id") | ||
await actual.downloadBudget(id); | ||
} catch (e) { | ||
throw new Error(`Actual Budget Error: ${e.message}`); | ||
} | ||
|
||
return actual; | ||
} | ||
|
||
/** | ||
* | ||
* @param {typeof actual} actualInstance | ||
*/ | ||
function listAccounts(actualInstance) { | ||
return actualInstance.getAccounts(); | ||
} | ||
|
||
/** | ||
* Only works for the past month | ||
* @param {typeof actual} actualInstance | ||
* @param {*} accountId | ||
*/ | ||
async function getLastTransactionDate(actualInstance, accountId) { | ||
const monthAgo = new Date(); | ||
monthAgo.setMonth(monthAgo.getMonth() -3); | ||
|
||
const transactions = await actualInstance.getTransactions(accountId, monthAgo, new Date()); | ||
|
||
if (transactions.length === 0) { | ||
return new Date(0); | ||
} | ||
|
||
// Transactions of the day are already imported, so start from the next day. | ||
const last = new Date(transactions[0].date); | ||
last.setDate(last.getDate() + 1); | ||
|
||
return last; | ||
} | ||
|
||
|
||
async function importTransactions(actualInstance, accountId, transactions) { | ||
console.info("Importing transactions raw data START:") | ||
console.debug(transactions) | ||
const actualResult = await actualInstance.importTransactions( | ||
accountId, | ||
transactions | ||
); | ||
console.info("Actual logs: ", actualResult); | ||
} | ||
|
||
async function getBalance(actualInstance, accountId) { | ||
const balance = await actualInstance.runQuery(q('transactions') | ||
.filter({ account: accountId }) | ||
//.options({ splits: 'inline' }) | ||
.calculate({ $sum: '$amount' }),) | ||
return balance.data; | ||
} | ||
|
||
/** | ||
* | ||
* @param {typeof actual} actualInstance | ||
*/ | ||
async function finalize(actualInstance) { | ||
await actualInstance.sync() | ||
await actualInstance.shutdown(); | ||
} | ||
|
||
module.exports = { | ||
initialize, | ||
listAccounts, | ||
getLastTransactionDate, | ||
importTransactions, | ||
finalize, | ||
getBalance | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
const { getAppConfigFromEnv, getConf } = require("./config.js"); | ||
const { importMyEdenredTransactions, init } = require("./engine.js"); | ||
|
||
let config; | ||
const appConfig = getAppConfigFromEnv() | ||
|
||
const printSyncedAccounts = () => { | ||
const actualData = config.get("actualSync"); | ||
if (!actualData) { | ||
console.error("No syncing data found"); | ||
return; | ||
} | ||
|
||
console.info("The following accounts are linked to Actual:"); | ||
console.table( | ||
Object.values(actualData).map((account) => ({ | ||
"Actual Account": account.actualName, | ||
"Actual Account Id": account.actualAccountId, | ||
"MyEdenred Account Id": account.myEdenredAccountId, | ||
})) | ||
); | ||
}; | ||
|
||
/** | ||
* | ||
* @param {string} command | ||
* @param {object} flags | ||
* @param {string} flags.since | ||
*/ | ||
module.exports = async (command, flags) => { | ||
if (!command) { | ||
console.log('Try "thinkactual --help"'); | ||
process.exit(); | ||
} | ||
|
||
config = getConf(flags.user || "default") | ||
|
||
if (command === "config") { | ||
console.log(`Config for this app is located at: ${config.path}`); | ||
} else if (command == "init") { | ||
init() | ||
} else if (command === "import") { | ||
await importMyEdenredTransactions(); | ||
} else if (command === "ls") { | ||
printSyncedAccounts(); | ||
} | ||
process.exit(); | ||
}; |
Oops, something went wrong.