diff --git a/.env.sample b/.env.sample index 0a501ae..7cfdd23 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,6 @@ PORT=8000 +# These POSTGRESQL_* below are only used if the config.core.databaseEngine is 'postgresql' POSTGRESQL_HOST="postgresql" # docker container name or external hostname/IP POSTGRESQL_USER="postgres" POSTGRESQL_PASSWORD="fake" diff --git a/bewcloud.config.sample.ts b/bewcloud.config.sample.ts index a22412e..eb2df42 100644 --- a/bewcloud.config.sample.ts +++ b/bewcloud.config.sample.ts @@ -22,6 +22,8 @@ const config: PartialDeep = { // }, // core: { // enabledApps: ['news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'], // dashboard and files cannot be disabled + // databaseEngine: 'postgresql', // The database engine to use. Currently only 'postgresql' and 'sqlite' are supported. + // sqliteFilePath: '', // The path to the SQLite database file. Only used if databaseEngine is 'sqlite'. // }, // visuals: { // title: 'My own cloud', diff --git a/lib/config.ts b/lib/config.ts index a37ef87..644690a 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -26,6 +26,8 @@ export class AppConfig { }, core: { enabledApps: ['news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'], + databaseEngine: 'postgresql', + sqliteFilePath: '', }, visuals: { title: '', diff --git a/lib/interfaces/database.ts b/lib/interfaces/database.ts index a247f6c..436814d 100644 --- a/lib/interfaces/database.ts +++ b/lib/interfaces/database.ts @@ -1,6 +1,11 @@ import { Client } from 'postgres'; +import { DatabaseSync } from 'node:sqlite'; + import '@std/dotenv/load'; +import { Config } from '/lib/types.ts'; +import { AppConfig } from '/lib/config.ts'; + const POSTGRESQL_HOST = Deno.env.get('POSTGRESQL_HOST') || ''; const POSTGRESQL_USER = Deno.env.get('POSTGRESQL_USER') || ''; const POSTGRESQL_PASSWORD = Deno.env.get('POSTGRESQL_PASSWORD') || ''; @@ -20,8 +25,9 @@ const tls = POSTGRESQL_CAFILE }; export default class Database { - protected db?: Client; - protected throwOnConnectionError?: boolean; + protected db?: Client | DatabaseSync; + private databaseEngine: Config['core']['databaseEngine'] = 'postgresql'; + private throwOnConnectionError?: boolean; constructor( { connectNow = false, throwOnConnectionError = false }: { connectNow?: boolean; throwOnConnectionError?: boolean } = @@ -30,11 +36,43 @@ export default class Database { this.throwOnConnectionError = throwOnConnectionError; if (connectNow) { - this.connectToPostgres(); + this.connectToDatabase(); } } - protected async connectToPostgres() { + private async connectToDatabase() { + if (this.db) { + return this.db; + } + + const config = await AppConfig.getConfig(); + + this.databaseEngine = config.core.databaseEngine; + + if (this.databaseEngine === 'postgresql') { + await this.connectToPostgres(); + } else { + await this.connectToSQLite(); + } + } + + private async disconnectFromDatabase() { + if (!this.db) { + return; + } + + const config = await AppConfig.getConfig(); + + this.databaseEngine = config.core.databaseEngine; + + if (this.databaseEngine === 'postgresql') { + await this.disconnectFromPostgres(); + } else { + this.disconnectFromSQLite(); + } + } + + private async connectToPostgres() { if (this.db) { return this.db; } @@ -88,28 +126,54 @@ export default class Database { } } - protected async disconnectFromPostgres() { + private async connectToSQLite() { + if (this.db) { + return this.db; + } + + const config = await AppConfig.getConfig(); + + const sqliteDatabase = new DatabaseSync(config.core.sqliteFilePath); + + this.db = sqliteDatabase; + } + + private disconnectFromSQLite() { if (!this.db) { return; } - await this.db.end(); + (this.db as DatabaseSync).close(); + } + + private async disconnectFromPostgres() { + if (!this.db) { + return; + } + + await (this.db as Client).end(); this.db = undefined; } public close() { - this.disconnectFromPostgres(); + this.disconnectFromDatabase(); } - public async query(sql: string, args?: any[]) { + public async query(sql: string, args?: any[]): Promise { if (!this.db) { - await this.connectToPostgres(); + await this.connectToDatabase(); } - const result = await this.db!.queryObject(sql, args); + if (this.databaseEngine === 'postgresql') { + const result = await (this.db as Client).queryObject(sql, args); - return result.rows; + return result.rows; + } + + const result = (this.db as DatabaseSync).prepare(sql).all(...(args || [])) as T[]; + + return result; } } diff --git a/lib/types.ts b/lib/types.ts index 2f1b481..388b536 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -190,6 +190,10 @@ export interface Config { core: { /** dashboard and files cannot be disabled */ enabledApps: OptionalApp[]; + /** The database engine to use. Currently only 'postgresql' and 'sqlite' are supported. */ + databaseEngine: 'postgresql' | 'sqlite'; + /** The path to the SQLite database file. Only used if databaseEngine is 'sqlite'. */ + sqliteFilePath: string; }; visuals: { /** An override title of the application. Empty shows the default title. */ diff --git a/migrate-db.ts b/migrate-db.ts index e65fd2c..b44e160 100644 --- a/migrate-db.ts +++ b/migrate-db.ts @@ -72,7 +72,7 @@ async function runMigrations(missingMigrations: string[]): Promise { await db.query(migrationSql); - await db.query(sql`INSERT INTO "public"."bewcloud_migrations" ("name", "executed_at") VALUES ($1, NOW())`, [ + await db.query(sql`INSERT INTO "bewcloud_migrations" ("name", "executed_at") VALUES ($1, NOW())`, [ missingMigration, ]);