From ecce02616cef6099153246ecbfe4e01d38cc8f81 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Sat, 7 Jun 2025 14:12:08 +1000 Subject: [PATCH] Validate database version --- src/App.vue | 14 ++++++++++-- src/db.ts | 20 ++++++++++++++--- src/error.ts | 39 +++++++++++++++++++++++++++++++++ src/main.ts | 7 +++++- src/pages/CriticalErrorView.vue | 39 +++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/error.ts create mode 100644 src/pages/CriticalErrorView.vue diff --git a/src/App.vue b/src/App.vue index d1e6953..f539c2f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -22,8 +22,13 @@
- - + +
@@ -31,11 +36,16 @@ diff --git a/src/db.ts b/src/db.ts index e4f485a..4f178bc 100644 --- a/src/db.ts +++ b/src/db.ts @@ -25,6 +25,7 @@ import { reactive } from 'vue'; import { Balance } from './amounts.ts'; import { ExtendedDatabase } from './dbutil.ts'; +import { CriticalError } from './error.ts'; export const DB_VERSION = 3; // Should match schema.sql export const DT_FORMAT = 'YYYY-MM-DD HH:mm:ss.SSS000'; @@ -53,8 +54,23 @@ export const db = reactive({ } if (filename !== null) { - // Initialise cached data const session = await this.load(); + + // Validate database version + let dbVersion: {value: string}[]; + try { + dbVersion = await session.select("SELECT value FROM metadata WHERE key = 'version'"); + } catch (err) { + throw new CriticalError('Unable to parse database (SQL error getting metadata.version)', err); + } + if (dbVersion.length === 0) { + throw new CriticalError('Unable to parse database (no metadata.version)'); + } + if (dbVersion[0].value !== DB_VERSION.toString()) { + throw new CriticalError('Unsupported database version ' + dbVersion[0].value + ' (expected ' + DB_VERSION + ')'); + } + + // Initialise cached data const metadataRaw: {key: string, value: string}[] = await session.select("SELECT * FROM metadata"); const metadataObject = Object.fromEntries(metadataRaw.map((x) => [x.key, x.value])); this.metadata.version = parseInt(metadataObject.version); @@ -62,8 +78,6 @@ export const db = reactive({ this.metadata.reporting_commodity = metadataObject.reporting_commodity; this.metadata.dps = parseInt(metadataObject.amount_dps); } - - // TODO: Validate database version }, load: async function(): Promise { diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 0000000..ffdbee6 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,39 @@ +/* + DrCr: Web-based double-entry bookkeeping framework + Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import { ref } from 'vue'; + +// Global error state +export const error = ref(null as CriticalError | null); + +export class CriticalError extends Error { + public error?: any; + + constructor(message: string, error?: any) { + super(message); + this.error = error; + } +} + +export function handleCriticalError(err: any) { + if (err instanceof CriticalError) { + error.value = err; + } else { + error.value = new CriticalError('An unexpected error occurred', err); + } +} diff --git a/src/main.ts b/src/main.ts index 79c66ce..699f971 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,6 +25,7 @@ import { createRouter, createWebHistory } from 'vue-router'; import App from './App.vue'; import { db } from './db.ts'; +import { handleCriticalError } from './error.ts'; async function initApp() { // Init router @@ -61,7 +62,11 @@ async function initApp() { // Init state const dbFilename: string = await invoke('get_open_filename'); if (dbFilename !== null) { - await db.init(dbFilename); // Ensure all metadata cached before loading Vue + try { + await db.init(dbFilename); // Ensure all metadata cached before loading Vue + } catch (err) { + handleCriticalError(err); + } } // Create Vue app diff --git a/src/pages/CriticalErrorView.vue b/src/pages/CriticalErrorView.vue new file mode 100644 index 0000000..d3c3a44 --- /dev/null +++ b/src/pages/CriticalErrorView.vue @@ -0,0 +1,39 @@ + + + + +