🔨 Run once, graceful shutdown, db filename in env

- Refactored toISODateString function
- Added Db filename to env
- Added getOrDefault function for fetching envs, replaced LOG_LEVEL with getOrDefault
- Added script to run job once then exit
- Catch SIGINT signal and do a graceful shutdown
This commit is contained in:
Martin Berg Alstad 2025-01-25 21:12:49 +01:00
parent b61903f5c8
commit 4977e7ad6a
Signed by: martials
GPG Key ID: A3824877B269F2E2
6 changed files with 41 additions and 18 deletions

View File

@ -4,6 +4,7 @@ ACTUAL_SYNC_ID=your-sync-id
ACTUAL_SERVER_URL=http://your-server-url:5006
ACTUAL_PASSWORD=your-password
ACTUAL_ACCOUNT_IDS=your-account-id1,your-account-id2
ACTUAL_DATA_DIR=.cache
# Bank
BANK_INITIAL_REFRESH_TOKEN=initial-valid-refresh-token
BANK_OAUTH_CLIENT_ID=your-client-id
@ -13,3 +14,4 @@ BANK_OAUTH_REDIRECT_URI=http://your-redirect-uri.com
BANK_ACCOUNT_IDS=your-account-id1,your-account-id2
# Configuration
LOG_LEVEL=info# trace | error | warn | info | debug | trace
DB_FILENAME=default

View File

@ -3,12 +3,14 @@ import dotenv from "dotenv"
dotenv.config()
// Actual
export const ACTUAL_SYNC_ID = getOrThrow("ACTUAL_SYNC_ID")
export const ACTUAL_SERVER_URL = getOrThrow("ACTUAL_SERVER_URL")
export const ACTUAL_PASSWORD = getOrThrow("ACTUAL_PASSWORD")
export const ACTUAL_ACCOUNT_IDS = getArrayOrThrow("ACTUAL_ACCOUNT_IDS")
export const ACTUAL_DATA_DIR = ".cache"
export const ACTUAL_DATA_DIR = getOrDefault("ACTUAL_DATA_DIR", ".cache")
// Bank
export const BANK_INITIAL_REFRESH_TOKEN = getOrThrow(
"BANK_INITIAL_REFRESH_TOKEN",
)
@ -16,6 +18,14 @@ export const BANK_OAUTH_CLIENT_ID = getOrThrow("BANK_OAUTH_CLIENT_ID")
export const BANK_OAUTH_CLIENT_SECRET = getOrThrow("BANK_OAUTH_CLIENT_SECRET")
export const BANK_ACCOUNT_IDS = getArrayOrThrow("BANK_ACCOUNT_IDS")
// Configuration
export const DB_FILENAME = getOrDefault("DB_FILENAME", "default")
export const LOG_LEVEL = getOrDefault("LOG_LEVEL", "info")
function getOrDefault(key: string, def: string): string {
return process.env[key] || def
}
function getOrThrow(key: string): string {
const value = process.env[key]
assert(value, `Missing environment variable: ${key}`)

View File

@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"start": "dotenvx run --env-file=.env.local -- node --import=tsx ./src/main.ts | pino-pretty",
"start-once": "ONCE=true dotenvx run --env-file=.env.local -- node --import=tsx ./src/main.ts | pino-pretty",
"test": "dotenvx run --env-file=.env.test.local -- node --experimental-vm-modules node_modules/jest/bin/jest.js | pino-pretty",
"format": "prettier --write \"./**/*.{js,mjs,ts,md,json}\""
},

View File

@ -1,5 +1,3 @@
import { type Dayjs } from "dayjs"
export function toISODateString(day: Dayjs): string {
return `${day.year()}-${(day.month() + 1).toString().padStart(2, "0")}-${day.date().toString().padStart(2, "0")}`
}
export const toISODateString = (day: Dayjs): string => day.format("YYYY-MM-DD")

View File

@ -1,8 +1,9 @@
import pino from "pino"
import { LOG_LEVEL } from "../config.ts"
/**
* / Returns a logging instance with the default log-level "info"
*/
export default pino({
level: process.env.LOG_LEVEL as string || "info",
level: LOG_LEVEL,
})

View File

@ -10,6 +10,7 @@ import {
ACTUAL_ACCOUNT_IDS,
ACTUAL_DATA_DIR,
BANK_ACCOUNT_IDS,
DB_FILENAME,
} from "../config.ts"
import logger from "@/logger.ts"
import type { UUID } from "node:crypto"
@ -17,7 +18,6 @@ import { createDb } from "@/bank/db/queries.ts"
import * as fs from "node:fs"
// TODO Transports api for pino https://github.com/pinojs/pino/blob/HEAD/docs/transports.md
// TODO create .cache if missing
export async function daily(actual: Actual, bank: Bank): Promise<void> {
// Fetch transactions from the bank
@ -59,29 +59,40 @@ function createCacheDirIfMissing(): void {
}
}
// TODO add a script to run an immediate job, without cron
// TODO catch ^C to stop server
async function main(): Promise<void> {
logger.info("Starting application")
createCacheDirIfMissing()
const actual = await ActualImpl.init()
const databaseFileName = "default.sqlite" // TODO move name to env
const databaseFileName = `${DB_FILENAME}.sqlite`
const db = createDb(databaseFileName)
logger.info(`Started SQLlite database with filename="${databaseFileName}"`)
process.on("SIGINT", async () => {
logger.info("Caught interrupt signal")
await shutdown()
})
if (process.env.ONCE) {
await daily(actual, new Sparebank1Impl(db))
await shutdown()
return
}
logger.info("Waiting for CRON job to start")
const cronJob = cronJobDaily(async () => {
logger.info("Running daily job")
await daily(actual, new Sparebank1Impl(db))
logger.info("Finished daily job")
})
// cronJobDaily(async () => {
logger.info("Running daily job")
await daily(actual, new Sparebank1Impl(db))
logger.info("Finished daily job")
// })
logger.info("Shutting down")
await actual.shutdown()
db.close()
async function shutdown(): Promise<void> {
logger.info("Shutting down")
await actual.shutdown()
db.close()
cronJob.stop()
}
}
void main()