114 lines
3.1 KiB
TypeScript
Raw Normal View History

import { type Actual, ActualImpl } from "@/actual.ts"
import { cronJobDaily } from "@/cron.ts"
import { type Bank, type Interval, Sparebank1Impl } from "@/bank/sparebank1.ts"
import {
ACTUAL_DATA_DIR,
BANK_ACCOUNT_IDS,
DB_DIRECTORY,
DB_FILENAME,
TRANSACTION_RELATIVE_FROM_DATE,
TRANSACTION_RELATIVE_TO_DATE,
} from "@/config.ts"
import logger from "@common/logger.ts"
import type { UUID } from "node:crypto"
import { createDb } from "@/bank/db/queries.ts"
import { CronJob } from "cron"
import { createDirsIfMissing } from "@/fs.ts"
import dayjs from "dayjs"
2024-11-15 22:55:53 +01:00
// TODO move tsx to devDependency. Requires ts support for Node with support for @ alias
// TODO verbatimSyntax in tsconfig, conflicts with jest
// TODO multi module project. Main | DAL | Sparebank1 impl
// TODO store last fetched date in db, and refetch from that date, if app has been offline for some time
// TODO do not fetch if saturday or sunday
2024-11-15 22:55:53 +01:00
export async function moveTransactions(
actual: Actual,
bank: Bank,
): Promise<void> {
// Fetch transactions from the bank
const actualTransactions = await bank.fetchTransactions(
relativeInterval(),
...BANK_ACCOUNT_IDS,
)
logger.info(`Fetched ${actualTransactions.length} transactions`)
const transactionsByAccount = Object.groupBy(
actualTransactions,
(transaction) => transaction.account,
)
const responses = await Promise.all(
Object.entries(transactionsByAccount).map(([accountId, transactions]) =>
actual.importTransactions(accountId as UUID, transactions || []),
),
)
logger.debug(
responses.map((response) => ({
added: response.added,
updated: response.updated,
})),
"Finished importing transactions",
)
}
2024-11-15 22:55:53 +01:00
function relativeInterval(): Interval {
const today = dayjs()
return {
fromDate: today.subtract(TRANSACTION_RELATIVE_FROM_DATE, "days"),
toDate: today.subtract(TRANSACTION_RELATIVE_TO_DATE, "days"),
}
}
async function main(): Promise<void> {
logger.info("Starting application")
createDirsIfMissing(ACTUAL_DATA_DIR, DB_DIRECTORY)
const databaseFilePath = `${DB_DIRECTORY}/${DB_FILENAME}.sqlite`
const db = createDb(databaseFilePath)
logger.info(`Started Sqlite database at '${databaseFilePath}'`)
const bank = new Sparebank1Impl(db)
process.on("SIGINT", async () => {
logger.info("Caught interrupt signal")
await shutdown()
})
let cronJob: CronJob | undefined
if (process.env.ONCE) {
const actual = await ActualImpl.init()
2025-02-02 11:22:29 +01:00
try {
return await moveTransactions(actual, bank)
2025-02-02 11:22:29 +01:00
} finally {
await actual.shutdown()
2025-02-02 11:22:29 +01:00
await shutdown()
}
} else {
await ActualImpl.testConnection()
}
logger.info("Waiting for CronJob to start")
let actual: Actual | undefined
2025-02-02 11:22:29 +01:00
try {
cronJob = cronJobDaily(async () => {
actual = await ActualImpl.init()
await moveTransactions(actual, bank)
})
2025-02-02 11:22:29 +01:00
} catch (exception) {
logger.error(exception, "Caught exception at CronJob, shutting down!")
2025-02-02 11:22:29 +01:00
await shutdown()
} finally {
await actual?.shutdown()
2025-02-02 11:22:29 +01:00
}
async function shutdown(): Promise<void> {
logger.info("Shutting down, Bye!")
db.close()
cronJob?.stop()
}
2024-11-15 22:55:53 +01:00
}
void main()