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