Browse Source

Enhance PlatformServiceMixin with utility methods and apply to TopMessage

Add $getSettings, $getMergedSettings utilities with built-in error handling. Reduce TopMessage code by 57% while eliminating Vue property conflicts.
pull/142/head
Matthew Raymer 3 days ago
parent
commit
fecaa59a2a
  1. 111
      src/components/TopMessage.vue
  2. 213
      src/utils/PlatformServiceMixin.ts

111
src/components/TopMessage.vue

@ -18,22 +18,19 @@ import { Component, Vue, Prop } from "vue-facing-decorator";
import { AppString, NotificationIface } from "../constants/app";
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
import { DEFAULT_ENDORSER_API_SERVER } from "../constants/app";
import {
PlatformServiceMixin,
IPlatformServiceMixin,
} from "../utils/PlatformServiceMixin";
import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil";
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
@Component({
mixins: [PlatformServiceMixin],
})
export default class TopMessage extends Vue {
// NOTE: This component uses PlatformServiceMixin which provides:
// - this.dbQuery(), this.dbExec(), this.dbGetOneRow() methods
// - this.platformService computed property
// - this.isCapacitor, this.isWeb, this.isElectron computed properties
// Enhanced PlatformServiceMixin provides:
// - this.$dbQuery(), this.$dbExec(), this.$dbGetOneRow() with built-in error handling
// - this.$getSettings(), this.$getMergedSettings() utility methods
// - this.$withTransaction() for safe database transactions
// - this.platformService, this.isCapacitor, this.isWeb, this.isElectron
// - this.capabilities computed property
// TypeScript requires (this as any) for mixin methods due to compile-time limitations
// All methods use $ prefix following Vue conventions
$notify!: (notification: NotificationIface, timeout?: number) => void;
@ -71,97 +68,37 @@ export default class TopMessage extends Vue {
}
/**
* Get settings for the active account using the platform service mixin.
* This demonstrates the concise mixin pattern with direct database access.
* Get settings for the active account using enhanced mixin utilities.
* Dramatically simplified using $getMergedSettings utility method.
*/
private async getActiveAccountSettings() {
// Declare defaultSettings outside try block for proper scope
let defaultSettings;
try {
// Get default settings first
defaultSettings = await this.getDefaultSettings();
// If no active DID, return defaults
if (!defaultSettings.activeDid) {
return defaultSettings;
}
// Get account-specific settings using the mixin (much more concise!)
const result = await (this as any).dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[defaultSettings.activeDid],
);
if (!result?.values?.length) {
return defaultSettings;
}
// Map and filter settings
const overrideSettings = mapColumnsToValues(
result.columns,
result.values,
)[0] as any;
const overrideSettingsFiltered = Object.fromEntries(
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
);
// Merge settings
const settings = { ...defaultSettings, ...overrideSettingsFiltered };
// Handle searchBoxes parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
}
return settings;
} catch (error) {
console.error(
`Failed to retrieve account settings for ${defaultSettings?.activeDid}:`,
error,
);
return (
defaultSettings || {
// First get the default settings to find activeDid
const defaultSettings = await (this as any).$getSettings(
MASTER_SETTINGS_KEY,
{
id: MASTER_SETTINGS_KEY,
activeDid: undefined,
apiServer: DEFAULT_ENDORSER_API_SERVER,
}
},
);
}
}
/**
* Get default settings using the platform service mixin
*/
private async getDefaultSettings() {
try {
// Direct database access via mixin - no destructuring needed!
const result = await (this as any).dbQuery("SELECT * FROM settings WHERE id = ?", [
// Use enhanced utility to merge default and account-specific settings
// This replaces 50+ lines of duplicated logic with a single method call!
const mergedSettings = await (this as any).$getMergedSettings(
MASTER_SETTINGS_KEY,
]);
if (!result?.values?.length) {
return {
defaultSettings.activeDid,
{
id: MASTER_SETTINGS_KEY,
activeDid: undefined,
apiServer: DEFAULT_ENDORSER_API_SERVER,
};
}
const settings = mapColumnsToValues(
result.columns,
result.values,
)[0] as any;
// Handle searchBoxes parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
}
},
);
return settings;
return mergedSettings;
} catch (error) {
console.error("Failed to retrieve default settings:", error);
// Enhanced mixin already provides detailed error logging
// Just provide fallback for UI stability
return {
id: MASTER_SETTINGS_KEY,
activeDid: undefined,

213
src/utils/PlatformServiceMixin.ts

@ -8,23 +8,28 @@
* - Class-level service caching for performance
* - Vue composition API patterns for modern development
* - Mixin pattern for easy integration with existing class components
* - Enhanced utility methods for common patterns
* - Robust error handling and logging
*
* Benefits:
* - Eliminates repeated PlatformServiceFactory.getInstance() calls
* - Provides consistent service access pattern across components
* - Improves performance with cached instances
* - Maintains type safety with TypeScript
* - Includes common database utility patterns
* - Enhanced error handling and logging
*
* @author Matthew Raymer
* @version 1.0.0
* @version 2.0.0
* @since 2025-07-02
*/
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import type { PlatformService } from "@/services/PlatformService";
import { mapColumnsToValues, parseJsonField } from "@/db/databaseUtil";
/**
* Mixin that provides cached platform service access to Vue components
* Enhanced mixin that provides cached platform service access and utility methods
*
* Usage:
* ```typescript
@ -33,8 +38,11 @@ import type { PlatformService } from "@/services/PlatformService";
* })
* export default class MyComponent extends Vue {
* async someMethod() {
* // Access cached platform service directly
* const result = await this.platformService.dbQuery('SELECT * FROM users');
* // Direct access without type assertions
* const result = await this.$dbQuery('SELECT * FROM users');
*
* // Utility methods for common patterns
* const settings = await this.$getSettings('user123');
* }
* }
* ```
@ -96,39 +104,204 @@ export const PlatformServiceMixin = {
methods: {
/**
* Convenient database query method
* Shorthand for this.platformService.dbQuery()
* Enhanced database query method with error handling
* Prefixed with $ to avoid naming conflicts and improve discoverability
*/
async $dbQuery(sql: string, params?: unknown[]) {
try {
return await (this as any).platformService.dbQuery(sql, params);
} catch (error) {
console.error(
`[${(this as any).$options.name}] Database query failed:`,
{
sql,
params,
error,
},
);
throw error;
}
},
/**
* Enhanced database execution method with error handling
*/
async dbQuery(sql: string, params?: unknown[]) {
return await (this as any).platformService.dbQuery(sql, params);
async $dbExec(sql: string, params?: unknown[]) {
try {
return await (this as any).platformService.dbExec(sql, params);
} catch (error) {
console.error(
`[${(this as any).$options.name}] Database exec failed:`,
{
sql,
params,
error,
},
);
throw error;
}
},
/**
* Convenient database execution method
* Shorthand for this.platformService.dbExec()
* Enhanced database single row method with error handling
*/
async dbExec(sql: string, params?: unknown[]) {
return await (this as any).platformService.dbExec(sql, params);
async $dbGetOneRow(sql: string, params?: unknown[]) {
try {
return await (this as any).platformService.dbGetOneRow(sql, params);
} catch (error) {
console.error(
`[${(this as any).$options.name}] Database getOneRow failed:`,
{
sql,
params,
error,
},
);
throw error;
}
},
/**
* Convenient database single row method
* Shorthand for this.platformService.dbGetOneRow()
* Utility method for retrieving and parsing settings
* Common pattern used across many components
*/
async dbGetOneRow(sql: string, params?: unknown[]) {
return await (this as any).platformService.dbGetOneRow(sql, params);
async $getSettings(key: string, fallback: any = null) {
try {
const result = await this.$dbQuery(
"SELECT * FROM settings WHERE id = ? OR accountDid = ?",
[key, key],
);
if (!result?.values?.length) {
return fallback;
}
const settings = mapColumnsToValues(
result.columns,
result.values,
)[0] as any;
// Handle JSON field parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
}
return settings;
} catch (error) {
console.error(
`[${(this as any).$options.name}] Failed to get settings:`,
{
key,
error,
},
);
return fallback;
}
},
/**
* Utility method for merging default and account-specific settings
* Handles the common pattern of layered settings
*/
async $getMergedSettings(
defaultKey: string,
accountDid?: string,
defaultFallback: any = {},
) {
try {
// Get default settings
const defaultSettings = await this.$getSettings(
defaultKey,
defaultFallback,
);
// If no account DID, return defaults
if (!accountDid) {
return defaultSettings;
}
// Get account-specific overrides
const accountResult = await this.$dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[accountDid],
);
if (!accountResult?.values?.length) {
return defaultSettings;
}
// Map and filter non-null overrides
const overrideSettings = mapColumnsToValues(
accountResult.columns,
accountResult.values,
)[0] as any;
const filteredOverrides = Object.fromEntries(
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
);
// Merge settings with overrides taking precedence
const mergedSettings = { ...defaultSettings, ...filteredOverrides };
// Handle JSON field parsing
if (mergedSettings.searchBoxes) {
mergedSettings.searchBoxes = parseJsonField(
mergedSettings.searchBoxes,
[],
);
}
return mergedSettings;
} catch (error) {
console.error(
`[${(this as any).$options.name}] Failed to get merged settings:`,
{
defaultKey,
accountDid,
error,
},
);
return defaultFallback;
}
},
/**
* Utility method for safe database transactions
* Handles common transaction patterns with proper error handling
*/
async $withTransaction<T>(callback: () => Promise<T>): Promise<T> {
try {
await this.$dbExec("BEGIN TRANSACTION");
const result = await callback();
await this.$dbExec("COMMIT");
return result;
} catch (error) {
await this.$dbExec("ROLLBACK");
console.error(
`[${(this as any).$options.name}] Transaction failed:`,
error,
);
throw error;
}
},
},
};
/**
* Type-only export for components that need to declare the mixin interface
* Enhanced interface with utility methods
*/
export interface IPlatformServiceMixin {
platformService: PlatformService;
dbQuery(sql: string, params?: unknown[]): Promise<any>;
dbExec(sql: string, params?: unknown[]): Promise<any>;
dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
$dbExec(sql: string, params?: unknown[]): Promise<any>;
$dbGetOneRow(sql: string, params?: unknown[]): Promise<any>;
$getSettings(key: string, fallback?: any): Promise<any>;
$getMergedSettings(
defaultKey: string,
accountDid?: string,
defaultFallback?: any,
): Promise<any>;
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;

Loading…
Cancel
Save