-
Notifications
You must be signed in to change notification settings - Fork 0
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 9134a39
Showing
10 changed files
with
1,336 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,11 @@ | ||
# Port the server will listen on | ||
PORT=3030 | ||
# Optional auth token to use | ||
AUTH_TOKEN=aR4nd0mT0k3n?! | ||
|
||
ACTUAL_SERVER_URL=https://my-actual.server.dev/ | ||
ACTUAL_PASSWORD=myAc7ualP4ssword! | ||
# This is the ID from Settings → Show advanced settings → Sync ID | ||
ACTUAL_BUDGET_SYNC_ID=761e1721-895a-4790-acce-b2c157df1647 | ||
# How many seconds between each data pull from Actual | ||
ACTUAL_REFRESH_INTERVAL_SECONDS=3600 |
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,8 @@ | ||
node_modules | ||
dist | ||
|
||
.env* | ||
!.env.example | ||
|
||
data/* | ||
!data/.gitkeep |
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,52 @@ | ||
import * as api from '@actual-app/api'; | ||
|
||
export async function init() { | ||
await api.init({ | ||
dataDir: './data', | ||
serverURL: process.env.ACTUAL_SERVER_URL, | ||
password: process.env.ACTUAL_PASSWORD, | ||
}); | ||
|
||
await api.downloadBudget(process.env.ACTUAL_BUDGET_SYNC_ID); | ||
} | ||
|
||
export async function shutdown() { | ||
return api.shutdown(); | ||
} | ||
|
||
export async function getAccounts() { | ||
return api.getAccounts(); | ||
} | ||
|
||
export async function getTransactions(accountID: string) { | ||
return api.getTransactions(accountID); | ||
} | ||
|
||
export async function getAllTransactions(offbudget?: boolean) { | ||
const accounts = await api.getAccounts(); | ||
|
||
const accountsToFetch = accounts?.filter( | ||
(a: any) => !a.offbudget || offbudget, | ||
); | ||
|
||
const transactions = []; | ||
for (const account of accountsToFetch) { | ||
const tx = await api.getTransactions(account.id); | ||
transactions.push(...tx); | ||
} | ||
|
||
return transactions; | ||
} | ||
|
||
export async function getLatestBudget() { | ||
const budgetMonths = await api.getBudgetMonths(); | ||
if (!budgetMonths?.length) { | ||
return null; | ||
} | ||
|
||
return api.getBudgetMonth(budgetMonths.at(-1)); | ||
} | ||
|
||
export async function getBudgetAtMonth(month: string) { | ||
return api.getBudgetMonth(month); | ||
} |
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,19 @@ | ||
import type { Request, Response, NextFunction } from 'express'; | ||
|
||
const authToken = process.env.AUTH_TOKEN; | ||
const authEnabled = !!authToken && authToken.length > 0; | ||
|
||
export function authMiddleware( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction, | ||
) { | ||
if (authEnabled) { | ||
const incomingToken = req.headers['authorization'] || req.query['token']; | ||
if (incomingToken !== authToken) { | ||
return res.sendStatus(403); | ||
} | ||
} | ||
|
||
return next(); | ||
} |
Empty file.
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,85 @@ | ||
import express from 'express'; | ||
import * as actual from './actual'; | ||
import { authMiddleware } from './auth'; | ||
|
||
const app = express(); | ||
|
||
app.use(authMiddleware); | ||
|
||
app.get('/accounts', async (_, res) => { | ||
try { | ||
const accounts = await actual.getAccounts(); | ||
return res.status(200).json(accounts); | ||
} catch (e) { | ||
console.error(e); | ||
return res.status(500).send(); | ||
} | ||
}); | ||
|
||
app.get('/accounts/:accountid/transactions', async (req, res) => { | ||
try { | ||
const transactions = await actual.getTransactions(req.params.accountid); | ||
return res.status(200).json(transactions); | ||
} catch (e) { | ||
console.error(e); | ||
return res.status(500).send(); | ||
} | ||
}); | ||
|
||
app.get('/transactions', async (req, res) => { | ||
try { | ||
const offbudget = req.query['offbudget'] === 'true'; | ||
const transactions = await actual.getAllTransactions(offbudget); | ||
return res.status(200).json(transactions); | ||
} catch (e) { | ||
console.error(e); | ||
return res.status(500).send(); | ||
} | ||
}); | ||
|
||
app.get('/budget', async (_, res) => { | ||
try { | ||
const budget = actual.getLatestBudget(); | ||
return res.status(200).json(budget); | ||
} catch (e) { | ||
console.error(e); | ||
return res.status(500).send(); | ||
} | ||
}); | ||
|
||
app.get('/budget/:month', async (req, res) => { | ||
try { | ||
const budget = await actual.getBudgetAtMonth(req.params.month); | ||
return res.status(200).json(budget); | ||
} catch (e) { | ||
console.error(e); | ||
return res.status(500).send(); | ||
} | ||
}); | ||
|
||
// Server start | ||
const port = process.env.PORT || 3000; | ||
const server = app.listen(port, () => { | ||
console.log(`Listening on port ${port}...`); | ||
console.log('Initializing Actual DB...'); | ||
actual.init().then(() => console.log('Actual DB initialized')); | ||
}); | ||
|
||
// Refresh Actual data at a given frequence | ||
const intervalID = setInterval(() => { | ||
actual.init().then(() => console.log('Actual DB refreshed')); | ||
}, Number(process.env.ACTUAL_REFRESH_INTERVAL_SECONDS) * 1000 ?? 60_000); | ||
|
||
process.on('SIGTERM', shutdown); | ||
process.on('SIGINT', shutdown); | ||
|
||
// Graceful shutdown | ||
function shutdown() { | ||
clearInterval(intervalID); | ||
|
||
console.log('Shutting down Actual DB...'); | ||
actual.shutdown().then(() => console.log('Actual DB shut down')); | ||
|
||
console.log('Closing HTTP server...'); | ||
server.close(() => console.log('HTTP server closed')); | ||
} |
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,22 @@ | ||
{ | ||
"name": "actual-to-csv", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"build": "tsc", | ||
"prestart": "pnpm build", | ||
"start": "node --env-file=.env ./dist/index.js" | ||
}, | ||
"keywords": [], | ||
"author": "Alessandro Cifani <alessandro.cifani@gmail.com>", | ||
"license": "MIT", | ||
"dependencies": { | ||
"@actual-app/api": "^6.7.1", | ||
"express": "5.0.0-beta.3" | ||
}, | ||
"devDependencies": { | ||
"@types/express": "^4.17.21", | ||
"typescript": "^5.4.5" | ||
} | ||
} |
Oops, something went wrong.