forked from jsnbuchanan/crowd-funder-for-time-pwa
- Create abstract BaseDatabaseService class with common database operations - Extract 7 duplicate methods from WebPlatformService and CapacitorPlatformService - Ensure consistent database logic across all platform implementations - Fix constructor inheritance issues with proper super() calls - Improve maintainability by centralizing database operations Methods consolidated: - generateInsertStatement - updateDefaultSettings - updateActiveDid - getActiveIdentity - insertNewDidIntoSettings - updateDidSpecificSettings - retrieveSettingsForActiveAccount Architecture: - BaseDatabaseService (abstract base class) - WebPlatformService extends BaseDatabaseService - CapacitorPlatformService extends BaseDatabaseService - ElectronPlatformService extends CapacitorPlatformService Benefits: - Eliminates ~200 lines of duplicate code - Guarantees consistency across platforms - Single point of maintenance for database operations - Prevents platform-specific bugs in database logic Author: Matthew Raymer Timestamp: Wed Oct 22 07:26:38 AM UTC 2025
298 lines
9.3 KiB
TypeScript
298 lines
9.3 KiB
TypeScript
/**
|
|
* @fileoverview Base Database Service for Platform Services
|
|
* @author Matthew Raymer
|
|
*
|
|
* This abstract base class provides common database operations that are
|
|
* identical across all platform implementations. It eliminates code
|
|
* duplication and ensures consistency in database operations.
|
|
*
|
|
* Key Features:
|
|
* - Common database utility methods
|
|
* - Consistent settings management
|
|
* - Active identity management
|
|
* - Abstract methods for platform-specific database operations
|
|
*
|
|
* Architecture:
|
|
* - Abstract base class with common implementations
|
|
* - Platform services extend this class
|
|
* - Platform-specific database operations remain abstract
|
|
*
|
|
* @since 1.1.1-beta
|
|
*/
|
|
|
|
import { logger } from "../../utils/logger";
|
|
import { QueryExecResult } from "@/interfaces/database";
|
|
|
|
/**
|
|
* Abstract base class for platform-specific database services.
|
|
*
|
|
* This class provides common database operations that are identical
|
|
* across all platform implementations (Web, Capacitor, Electron).
|
|
* Platform-specific services extend this class and implement the
|
|
* abstract database operation methods.
|
|
*
|
|
* Common Operations:
|
|
* - Settings management (update, retrieve, insert)
|
|
* - Active identity management
|
|
* - Database utility methods
|
|
*
|
|
* @abstract
|
|
* @example
|
|
* ```typescript
|
|
* export class WebPlatformService extends BaseDatabaseService {
|
|
* async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
|
|
* // Web-specific implementation
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
export abstract class BaseDatabaseService {
|
|
/**
|
|
* Generate an INSERT statement for a model object.
|
|
*
|
|
* Creates a parameterized INSERT statement with placeholders for
|
|
* all properties in the model object. This ensures safe SQL
|
|
* execution and prevents SQL injection.
|
|
*
|
|
* @param model - Object containing the data to insert
|
|
* @param tableName - Name of the target table
|
|
* @returns Object containing the SQL statement and parameters
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const { sql, params } = this.generateInsertStatement(
|
|
* { name: 'John', age: 30 },
|
|
* 'users'
|
|
* );
|
|
* // sql: "INSERT INTO users (name, age) VALUES (?, ?)"
|
|
* // params: ['John', 30]
|
|
* ```
|
|
*/
|
|
generateInsertStatement(
|
|
model: Record<string, unknown>,
|
|
tableName: string,
|
|
): { sql: string; params: unknown[] } {
|
|
const keys = Object.keys(model);
|
|
const placeholders = keys.map(() => "?").join(", ");
|
|
const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders})`;
|
|
const params = keys.map((key) => model[key]);
|
|
return { sql, params };
|
|
}
|
|
|
|
/**
|
|
* Update default settings for the currently active account.
|
|
*
|
|
* Retrieves the active DID from the active_identity table and updates
|
|
* the corresponding settings record. This ensures settings are always
|
|
* updated for the correct account.
|
|
*
|
|
* @param settings - Object containing the settings to update
|
|
* @returns Promise that resolves when settings are updated
|
|
*
|
|
* @throws {Error} If no active DID is found or database operation fails
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* await this.updateDefaultSettings({
|
|
* theme: 'dark',
|
|
* notifications: true
|
|
* });
|
|
* ```
|
|
*/
|
|
async updateDefaultSettings(
|
|
settings: Record<string, unknown>,
|
|
): Promise<void> {
|
|
// Get current active DID and update that identity's settings
|
|
const activeIdentity = await this.getActiveIdentity();
|
|
const activeDid = activeIdentity.activeDid;
|
|
|
|
if (!activeDid) {
|
|
logger.warn(
|
|
"[BaseDatabaseService] No active DID found, cannot update default settings",
|
|
);
|
|
return;
|
|
}
|
|
|
|
const keys = Object.keys(settings);
|
|
const setClause = keys.map((key) => `${key} = ?`).join(", ");
|
|
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
|
|
const params = [...keys.map((key) => settings[key]), activeDid];
|
|
await this.dbExec(sql, params);
|
|
}
|
|
|
|
/**
|
|
* Update the active DID in the active_identity table.
|
|
*
|
|
* Sets the active DID and updates the lastUpdated timestamp.
|
|
* This is used when switching between different accounts/identities.
|
|
*
|
|
* @param did - The DID to set as active
|
|
* @returns Promise that resolves when the update is complete
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* await this.updateActiveDid('did:example:123');
|
|
* ```
|
|
*/
|
|
async updateActiveDid(did: string): Promise<void> {
|
|
await this.dbExec(
|
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
|
[did],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the currently active DID from the active_identity table.
|
|
*
|
|
* Retrieves the active DID that represents the currently selected
|
|
* account/identity. This is used throughout the application to
|
|
* ensure operations are performed on the correct account.
|
|
*
|
|
* @returns Promise resolving to object containing the active DID
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const { activeDid } = await this.getActiveIdentity();
|
|
* console.log('Current active DID:', activeDid);
|
|
* ```
|
|
*/
|
|
async getActiveIdentity(): Promise<{ activeDid: string }> {
|
|
const result = (await this.dbQuery(
|
|
"SELECT activeDid FROM active_identity WHERE id = 1",
|
|
)) as QueryExecResult;
|
|
return {
|
|
activeDid: (result?.values?.[0]?.[0] as string) || "",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Insert a new DID into the settings table with default values.
|
|
*
|
|
* Creates a new settings record for a DID with default configuration
|
|
* values. Uses INSERT OR REPLACE to handle cases where settings
|
|
* already exist for the DID.
|
|
*
|
|
* @param did - The DID to create settings for
|
|
* @returns Promise that resolves when settings are created
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* await this.insertNewDidIntoSettings('did:example:123');
|
|
* ```
|
|
*/
|
|
async insertNewDidIntoSettings(did: string): Promise<void> {
|
|
// Import constants dynamically to avoid circular dependencies
|
|
const { DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER } =
|
|
await import("@/constants/app");
|
|
|
|
// Use INSERT OR REPLACE to handle case where settings already exist for this DID
|
|
// This prevents duplicate accountDid entries and ensures data integrity
|
|
await this.dbExec(
|
|
"INSERT OR REPLACE INTO settings (accountDid, finishedOnboarding, apiServer, partnerApiServer) VALUES (?, ?, ?, ?)",
|
|
[did, false, DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update settings for a specific DID.
|
|
*
|
|
* Updates settings for a particular DID rather than the active one.
|
|
* This is useful for bulk operations or when managing multiple accounts.
|
|
*
|
|
* @param did - The DID to update settings for
|
|
* @param settings - Object containing the settings to update
|
|
* @returns Promise that resolves when settings are updated
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* await this.updateDidSpecificSettings('did:example:123', {
|
|
* theme: 'light',
|
|
* notifications: false
|
|
* });
|
|
* ```
|
|
*/
|
|
async updateDidSpecificSettings(
|
|
did: string,
|
|
settings: Record<string, unknown>,
|
|
): Promise<void> {
|
|
const keys = Object.keys(settings);
|
|
const setClause = keys.map((key) => `${key} = ?`).join(", ");
|
|
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
|
|
const params = [...keys.map((key) => settings[key]), did];
|
|
await this.dbExec(sql, params);
|
|
}
|
|
|
|
/**
|
|
* Retrieve settings for the currently active account.
|
|
*
|
|
* Gets the active DID and retrieves all settings for that account.
|
|
* Excludes the 'id' column from the returned settings object.
|
|
*
|
|
* @returns Promise resolving to settings object or null if no active DID
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const settings = await this.retrieveSettingsForActiveAccount();
|
|
* if (settings) {
|
|
* console.log('Theme:', settings.theme);
|
|
* console.log('Notifications:', settings.notifications);
|
|
* }
|
|
* ```
|
|
*/
|
|
async retrieveSettingsForActiveAccount(): Promise<Record<
|
|
string,
|
|
unknown
|
|
> | null> {
|
|
// Get current active DID from active_identity table
|
|
const activeIdentity = await this.getActiveIdentity();
|
|
const activeDid = activeIdentity.activeDid;
|
|
|
|
if (!activeDid) {
|
|
return null;
|
|
}
|
|
|
|
const result = (await this.dbQuery(
|
|
"SELECT * FROM settings WHERE accountDid = ?",
|
|
[activeDid],
|
|
)) as QueryExecResult;
|
|
if (result?.values?.[0]) {
|
|
// Convert the row to an object
|
|
const row = result.values[0];
|
|
const columns = result.columns || [];
|
|
const settings: Record<string, unknown> = {};
|
|
|
|
columns.forEach((column: string, index: number) => {
|
|
if (column !== "id") {
|
|
// Exclude the id column
|
|
settings[column] = row[index];
|
|
}
|
|
});
|
|
|
|
return settings;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Abstract methods that must be implemented by platform-specific services
|
|
|
|
/**
|
|
* Execute a database query (SELECT operations).
|
|
*
|
|
* @abstract
|
|
* @param sql - SQL query string
|
|
* @param params - Optional parameters for prepared statements
|
|
* @returns Promise resolving to query results
|
|
*/
|
|
abstract dbQuery(sql: string, params?: unknown[]): Promise<unknown>;
|
|
|
|
/**
|
|
* Execute a database statement (INSERT, UPDATE, DELETE operations).
|
|
*
|
|
* @abstract
|
|
* @param sql - SQL statement string
|
|
* @param params - Optional parameters for prepared statements
|
|
* @returns Promise resolving to execution results
|
|
*/
|
|
abstract dbExec(sql: string, params?: unknown[]): Promise<unknown>;
|
|
}
|