🐋 Docker volume for Sqlite
Some checks failed
Deploy application / deploy (push) Failing after 7s

- Moved database to subdir specified by env
- Default data dir /data
- Move loggers to cronJob onTick
- Added volume for data dir in Docker compose
- Create any missing dir specified by env
This commit is contained in:
Martin Berg Alstad 2025-01-26 19:29:43 +01:00
parent aaa85e99cb
commit 9850017e3a
Signed by: martials
GPG Key ID: A3824877B269F2E2
8 changed files with 34 additions and 23 deletions

View File

@ -14,4 +14,5 @@ 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_DIRECTORY=data# Relative path, must not start or end with /
DB_FILENAME=default

View File

@ -2,7 +2,7 @@ name: Deploy application
on:
push:
branches: [ main ]
branches: [main]
jobs:
deploy:
@ -25,6 +25,7 @@ jobs:
BANK_ACCOUNT_IDS: ${{ secrets.BANK_ACCOUNT_IDS }}
# Configuration
LOG_LEVEL: ${{ var.LOG_LEVEL }}
DB_DIRECTORY: ${{ var.DB_DIRECTORY }}
DB_FILENAME: ${{ var.DB_FILENAME }}
steps:

View File

@ -19,6 +19,7 @@ export const BANK_OAUTH_CLIENT_SECRET = getOrThrow("BANK_OAUTH_CLIENT_SECRET")
export const BANK_ACCOUNT_IDS = getArrayOrThrow("BANK_ACCOUNT_IDS")
// Configuration
export const DB_DIRECTORY = getOrDefault("DB_DIRECTORY", "data")
export const DB_FILENAME = getOrDefault("DB_FILENAME", "default")
export const LOG_LEVEL = getOrDefault("LOG_LEVEL", "info")

View File

@ -1,8 +1,7 @@
services:
# TODO Database in storage
server:
container_name: actual_budget_sparebank1
restart: no # TODO unless-stopped
container_name: actual_sparebank1_cronjob
restart: unless-stopped
build:
context: .
environment:
@ -19,4 +18,10 @@ services:
- BANK_OAUTH_REDIRECT_URI
- BANK_ACCOUNT_IDS
- LOG_LEVEL
- DB_DIRECTORY # Required for Docker Compose
- DB_FILENAME
volumes:
- data:/${DB_DIRECTORY}
volumes:
data:

View File

@ -17,8 +17,8 @@ export type TokenResponseRaw = {
export type TokenKey = "access-token" | "refresh-token"
export function createDb(filename: string) {
const db = new Database(filename)
export function createDb(filepath: string) {
const db = new Database(filepath)
db.pragma("journal_mode = WAL")
db.exec(
"CREATE TABLE IF NOT EXISTS tokens ('key' VARCHAR PRIMARY KEY, token VARCHAR NOT NULL, expires_at DATETIME NOT NULL)",

View File

@ -93,6 +93,7 @@ export class Sparebank1Impl implements Sparebank1 {
async transactionsPastDay(
...accountKeys: ReadonlyArray<string>
): Promise<TransactionResponse> {
// TODO API is inclusive, today should equal lastDay
const today = dayjs()
const lastDay = today.subtract(1, "day")
return await Api.transactions(await this.getAccessToken(), accountKeys, {

View File

@ -1,4 +1,5 @@
import { CronJob } from "cron"
import logger from "@/logger.ts"
/**
* Run a function every day at 1 AM, Oslo time.
@ -8,7 +9,11 @@ import { CronJob } from "cron"
export function cronJobDaily(onTick: () => Promise<void>): CronJob {
return CronJob.from({
cronTime: "0 0 1 * * *",
onTick,
onTick: async () => {
logger.info("Starting daily job")
await onTick()
logger.info("Finished daily job")
},
start: true,
timeZone: "Europe/Oslo",
})

View File

@ -10,6 +10,7 @@ import {
ACTUAL_ACCOUNT_IDS,
ACTUAL_DATA_DIR,
BANK_ACCOUNT_IDS,
DB_DIRECTORY,
DB_FILENAME,
} from "../config.ts"
import logger from "@/logger.ts"
@ -19,10 +20,9 @@ import * as fs from "node:fs"
import { CronJob } from "cron"
// TODO Transports api for pino https://github.com/pinojs/pino/blob/HEAD/docs/transports.md
// TODO create Dockerfile and docker-compose.yml
// TODO Gitea workflow
// TODO move tsx to devDependency. Requires ts support for Node with support for @ alias
// TODO global exception handler, log and graceful shutdown
// TODO verbatimSyntax in tsconfig, conflicts with jest
export async function daily(actual: Actual, bank: Bank): Promise<void> {
// Fetch transactions from the bank
@ -60,22 +60,23 @@ async function fetchTransactionsFromPastDay(
return response.transactions
}
function createCacheDirIfMissing(): void {
if (!fs.existsSync(ACTUAL_DATA_DIR)) {
logger.info(`Missing '${ACTUAL_DATA_DIR}', creating...`)
fs.mkdirSync(ACTUAL_DATA_DIR)
function createDirIfMissing(directory: string): void {
if (!fs.existsSync(directory)) {
logger.info(`Missing '${directory}', creating...`)
fs.mkdirSync(directory, { recursive: true })
}
}
async function main(): Promise<void> {
logger.info("Starting application")
createCacheDirIfMissing()
createDirIfMissing(ACTUAL_DATA_DIR)
createDirIfMissing(DB_DIRECTORY)
const actual = await ActualImpl.init()
const databaseFileName = `${DB_FILENAME}.sqlite`
const db = createDb(databaseFileName)
logger.info(`Started SQLlite database with filename="${databaseFileName}"`)
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 () => {
@ -91,14 +92,10 @@ async function main(): Promise<void> {
}
logger.info("Waiting for CRON job to start")
cronJob = cronJobDaily(async () => {
logger.info("Running daily job")
await daily(actual, bank)
logger.info("Finished daily job")
})
cronJob = cronJobDaily(async () => await daily(actual, bank))
async function shutdown(): Promise<void> {
logger.info("Shutting down")
logger.info("Shutting down, Bye!")
await actual.shutdown()
db.close()
cronJob?.stop()