From 9850017e3ad9d7754756b7afff01cf6a600eaf5c Mon Sep 17 00:00:00 2001 From: Martin Berg Alstad Date: Sun, 26 Jan 2025 19:29:43 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=8B=20Docker=20volume=20for=20Sqlite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .env.example | 1 + .gitea/workflows/deploy.yml | 3 ++- config.ts | 1 + docker-compose.yml | 11 ++++++++--- src/bank/db/queries.ts | 4 ++-- src/bank/sparebank1.ts | 1 + src/cron.ts | 7 ++++++- src/main.ts | 29 +++++++++++++---------------- 8 files changed, 34 insertions(+), 23 deletions(-) diff --git a/.env.example b/.env.example index ad870fe..4ee9b58 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 83b1a9b..8bcba88 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -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: diff --git a/config.ts b/config.ts index 2aa793b..8f6f0bb 100644 --- a/config.ts +++ b/config.ts @@ -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") diff --git a/docker-compose.yml b/docker-compose.yml index 5dc7cd2..e5d6708 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: diff --git a/src/bank/db/queries.ts b/src/bank/db/queries.ts index 230f74f..860a80b 100644 --- a/src/bank/db/queries.ts +++ b/src/bank/db/queries.ts @@ -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)", diff --git a/src/bank/sparebank1.ts b/src/bank/sparebank1.ts index 8d82f57..84b9243 100644 --- a/src/bank/sparebank1.ts +++ b/src/bank/sparebank1.ts @@ -93,6 +93,7 @@ export class Sparebank1Impl implements Sparebank1 { async transactionsPastDay( ...accountKeys: ReadonlyArray ): Promise { + // 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, { diff --git a/src/cron.ts b/src/cron.ts index 6799f8f..f046f06 100644 --- a/src/cron.ts +++ b/src/cron.ts @@ -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): 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", }) diff --git a/src/main.ts b/src/main.ts index 2f1eaef..18d472c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 { // 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 { 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 { } 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 { - logger.info("Shutting down") + logger.info("Shutting down, Bye!") await actual.shutdown() db.close() cronJob?.stop()