Browse Source
- 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 2025pull/204/head
3 changed files with 317 additions and 220 deletions
@ -0,0 +1,297 @@ |
|||
/** |
|||
* @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>; |
|||
} |
Loading…
Reference in new issue