2025-02-13 21:07:30 +01:00
|
|
|
import { ActualImpl } from "@/actual.ts"
|
2024-11-17 22:27:29 +01:00
|
|
|
import { cronJobDaily } from "@/cron.ts"
|
2025-01-22 21:00:04 +01:00
|
|
|
import {
|
|
|
|
BANK_ACCOUNT_IDS,
|
2025-02-06 18:56:51 +01:00
|
|
|
TRANSACTION_RELATIVE_FROM_DATE,
|
|
|
|
TRANSACTION_RELATIVE_TO_DATE,
|
2025-02-09 13:01:34 +01:00
|
|
|
} from "@/config.ts"
|
2025-02-09 12:35:08 +01:00
|
|
|
import logger from "@common/logger.ts"
|
2024-12-01 19:35:45 +01:00
|
|
|
import type { UUID } from "node:crypto"
|
2025-01-25 22:30:52 +01:00
|
|
|
import { CronJob } from "cron"
|
2025-02-06 18:56:51 +01:00
|
|
|
import dayjs from "dayjs"
|
2025-02-13 21:07:30 +01:00
|
|
|
import type { Actual, Bank, Interval } from "@common/types.ts"
|
|
|
|
import { Sparebank1Impl } from "@sb1impl/sparebank1.ts"
|
2024-11-15 22:55:53 +01:00
|
|
|
|
2025-01-26 17:41:44 +01:00
|
|
|
// TODO move tsx to devDependency. Requires ts support for Node with support for @ alias
|
2025-01-26 19:29:43 +01:00
|
|
|
// TODO verbatimSyntax in tsconfig, conflicts with jest
|
2025-01-31 16:58:02 +01:00
|
|
|
// TODO store last fetched date in db, and refetch from that date, if app has been offline for some time
|
2025-02-06 18:56:51 +01:00
|
|
|
// TODO do not fetch if saturday or sunday
|
2024-11-15 22:55:53 +01:00
|
|
|
|
2025-02-13 21:07:30 +01:00
|
|
|
async function main(): Promise<void> {
|
|
|
|
logger.info("Starting application")
|
|
|
|
const bank = new Sparebank1Impl()
|
|
|
|
|
|
|
|
if (process.env.ONCE) {
|
|
|
|
return await runOnce(bank)
|
|
|
|
}
|
|
|
|
await ActualImpl.testConnection()
|
|
|
|
await runCronJob(bank)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO log the days the transactions are fetched
|
2025-02-09 13:34:00 +01:00
|
|
|
export async function moveTransactions(
|
|
|
|
actual: Actual,
|
|
|
|
bank: Bank,
|
|
|
|
): Promise<void> {
|
2025-02-06 18:56:51 +01:00
|
|
|
const actualTransactions = await bank.fetchTransactions(
|
|
|
|
relativeInterval(),
|
|
|
|
...BANK_ACCOUNT_IDS,
|
2024-12-01 19:35:45 +01:00
|
|
|
)
|
2025-02-06 18:56:51 +01:00
|
|
|
logger.info(`Fetched ${actualTransactions.length} transactions`)
|
2024-12-01 19:35:45 +01:00
|
|
|
|
2025-02-06 18:56:51 +01:00
|
|
|
const transactionsByAccount = Object.groupBy(
|
2024-12-25 21:06:42 +01:00
|
|
|
actualTransactions,
|
2025-02-02 12:37:43 +01:00
|
|
|
(transaction) => transaction.account,
|
|
|
|
)
|
|
|
|
|
2025-02-06 18:56:51 +01:00
|
|
|
const responses = await Promise.all(
|
|
|
|
Object.entries(transactionsByAccount).map(([accountId, transactions]) =>
|
2025-02-02 12:37:43 +01:00
|
|
|
actual.importTransactions(accountId as UUID, transactions || []),
|
|
|
|
),
|
2024-12-25 21:06:42 +01:00
|
|
|
)
|
2025-02-02 12:37:43 +01:00
|
|
|
|
2025-02-06 18:56:51 +01:00
|
|
|
logger.debug(
|
|
|
|
responses.map((response) => ({
|
|
|
|
added: response.added,
|
|
|
|
updated: response.updated,
|
|
|
|
})),
|
|
|
|
"Finished importing transactions",
|
|
|
|
)
|
2024-11-17 22:27:29 +01:00
|
|
|
}
|
2024-11-15 22:55:53 +01:00
|
|
|
|
2025-02-06 18:56:51 +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"),
|
2025-01-22 21:00:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-13 21:07:30 +01:00
|
|
|
async function runOnce(bank: Bank) {
|
|
|
|
const actual = await ActualImpl.init()
|
2025-01-22 21:00:04 +01:00
|
|
|
|
2025-02-13 21:07:30 +01:00
|
|
|
registerInterrupt(bank)
|
2024-12-26 12:49:54 +01:00
|
|
|
|
2025-02-13 21:07:30 +01:00
|
|
|
try {
|
|
|
|
return await moveTransactions(actual, bank)
|
|
|
|
} finally {
|
|
|
|
await actual.shutdown()
|
|
|
|
await shutdown(bank)
|
|
|
|
}
|
|
|
|
}
|
2024-12-01 20:48:42 +01:00
|
|
|
|
2025-02-13 21:07:30 +01:00
|
|
|
async function runCronJob(bank: Bank): Promise<void> {
|
|
|
|
let actual: Actual | undefined
|
2025-01-25 22:30:52 +01:00
|
|
|
let cronJob: CronJob | undefined
|
2024-12-01 20:48:42 +01:00
|
|
|
|
2025-02-09 13:54:35 +01:00
|
|
|
logger.info("Waiting for CronJob to start")
|
2025-02-02 11:22:29 +01:00
|
|
|
try {
|
2025-02-13 21:07:30 +01:00
|
|
|
// TODO move try-catch inside closure?
|
2025-02-09 13:34:00 +01:00
|
|
|
cronJob = cronJobDaily(async () => {
|
|
|
|
actual = await ActualImpl.init()
|
|
|
|
await moveTransactions(actual, bank)
|
|
|
|
})
|
2025-02-13 21:07:30 +01:00
|
|
|
registerInterrupt(bank, cronJob)
|
2025-02-02 11:22:29 +01:00
|
|
|
} catch (exception) {
|
2025-02-09 13:34:00 +01:00
|
|
|
logger.error(exception, "Caught exception at CronJob, shutting down!")
|
2025-02-13 21:07:30 +01:00
|
|
|
await shutdown(bank, cronJob)
|
2025-02-09 13:34:00 +01:00
|
|
|
} finally {
|
2025-02-13 21:07:30 +01:00
|
|
|
// TODO shuts down immediatly, move into closure
|
2025-02-09 13:34:00 +01:00
|
|
|
await actual?.shutdown()
|
2025-02-02 11:22:29 +01:00
|
|
|
}
|
2025-02-13 21:07:30 +01:00
|
|
|
}
|
2025-01-25 21:12:49 +01:00
|
|
|
|
2025-02-13 21:07:30 +01:00
|
|
|
function registerInterrupt(
|
|
|
|
bank: Bank,
|
|
|
|
cronJob: CronJob | undefined = undefined,
|
|
|
|
): void {
|
|
|
|
process.on("SIGINT", async () => {
|
|
|
|
logger.info("Caught interrupt signal")
|
|
|
|
await shutdown(bank, cronJob)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async function shutdown(
|
|
|
|
bank: Bank,
|
|
|
|
cronJob: CronJob | undefined = undefined,
|
|
|
|
): Promise<void> {
|
|
|
|
logger.info("Shutting down, Bye!")
|
|
|
|
await bank.shutdown()
|
|
|
|
cronJob?.stop()
|
2024-11-15 22:55:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void main()
|