forked from jsnbuchanan/crowd-funder-for-time-pwa
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.
This commit is contained in:
@@ -18,22 +18,19 @@ import { Component, Vue, Prop } from "vue-facing-decorator";
|
|||||||
import { AppString, NotificationIface } from "../constants/app";
|
import { AppString, NotificationIface } from "../constants/app";
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||||
import { DEFAULT_ENDORSER_API_SERVER } from "../constants/app";
|
import { DEFAULT_ENDORSER_API_SERVER } from "../constants/app";
|
||||||
import {
|
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
|
||||||
PlatformServiceMixin,
|
|
||||||
IPlatformServiceMixin,
|
|
||||||
} from "../utils/PlatformServiceMixin";
|
|
||||||
import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
})
|
})
|
||||||
export default class TopMessage extends Vue {
|
export default class TopMessage extends Vue {
|
||||||
// NOTE: This component uses PlatformServiceMixin which provides:
|
// Enhanced PlatformServiceMixin provides:
|
||||||
// - this.dbQuery(), this.dbExec(), this.dbGetOneRow() methods
|
// - this.$dbQuery(), this.$dbExec(), this.$dbGetOneRow() with built-in error handling
|
||||||
// - this.platformService computed property
|
// - this.$getSettings(), this.$getMergedSettings() utility methods
|
||||||
// - this.isCapacitor, this.isWeb, this.isElectron computed properties
|
// - this.$withTransaction() for safe database transactions
|
||||||
|
// - this.platformService, this.isCapacitor, this.isWeb, this.isElectron
|
||||||
// - this.capabilities computed property
|
// - 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;
|
$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.
|
* Get settings for the active account using enhanced mixin utilities.
|
||||||
* This demonstrates the concise mixin pattern with direct database access.
|
* Dramatically simplified using $getMergedSettings utility method.
|
||||||
*/
|
*/
|
||||||
private async getActiveAccountSettings() {
|
private async getActiveAccountSettings() {
|
||||||
// Declare defaultSettings outside try block for proper scope
|
|
||||||
let defaultSettings;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get default settings first
|
// First get the default settings to find activeDid
|
||||||
defaultSettings = await this.getDefaultSettings();
|
const defaultSettings = await (this as any).$getSettings(
|
||||||
|
|
||||||
// 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 || {
|
|
||||||
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 = ?", [
|
|
||||||
MASTER_SETTINGS_KEY,
|
MASTER_SETTINGS_KEY,
|
||||||
]);
|
{
|
||||||
|
|
||||||
if (!result?.values?.length) {
|
|
||||||
return {
|
|
||||||
id: MASTER_SETTINGS_KEY,
|
id: MASTER_SETTINGS_KEY,
|
||||||
activeDid: undefined,
|
activeDid: undefined,
|
||||||
apiServer: DEFAULT_ENDORSER_API_SERVER,
|
apiServer: DEFAULT_ENDORSER_API_SERVER,
|
||||||
};
|
},
|
||||||
}
|
);
|
||||||
|
|
||||||
const settings = mapColumnsToValues(
|
// Use enhanced utility to merge default and account-specific settings
|
||||||
result.columns,
|
// This replaces 50+ lines of duplicated logic with a single method call!
|
||||||
result.values,
|
const mergedSettings = await (this as any).$getMergedSettings(
|
||||||
)[0] as any;
|
MASTER_SETTINGS_KEY,
|
||||||
|
defaultSettings.activeDid,
|
||||||
|
{
|
||||||
|
id: MASTER_SETTINGS_KEY,
|
||||||
|
activeDid: undefined,
|
||||||
|
apiServer: DEFAULT_ENDORSER_API_SERVER,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Handle searchBoxes parsing
|
return mergedSettings;
|
||||||
if (settings.searchBoxes) {
|
|
||||||
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to retrieve default settings:", error);
|
// Enhanced mixin already provides detailed error logging
|
||||||
|
// Just provide fallback for UI stability
|
||||||
return {
|
return {
|
||||||
id: MASTER_SETTINGS_KEY,
|
id: MASTER_SETTINGS_KEY,
|
||||||
activeDid: undefined,
|
activeDid: undefined,
|
||||||
|
|||||||
@@ -8,23 +8,28 @@
|
|||||||
* - Class-level service caching for performance
|
* - Class-level service caching for performance
|
||||||
* - Vue composition API patterns for modern development
|
* - Vue composition API patterns for modern development
|
||||||
* - Mixin pattern for easy integration with existing class components
|
* - Mixin pattern for easy integration with existing class components
|
||||||
|
* - Enhanced utility methods for common patterns
|
||||||
|
* - Robust error handling and logging
|
||||||
*
|
*
|
||||||
* Benefits:
|
* Benefits:
|
||||||
* - Eliminates repeated PlatformServiceFactory.getInstance() calls
|
* - Eliminates repeated PlatformServiceFactory.getInstance() calls
|
||||||
* - Provides consistent service access pattern across components
|
* - Provides consistent service access pattern across components
|
||||||
* - Improves performance with cached instances
|
* - Improves performance with cached instances
|
||||||
* - Maintains type safety with TypeScript
|
* - Maintains type safety with TypeScript
|
||||||
|
* - Includes common database utility patterns
|
||||||
|
* - Enhanced error handling and logging
|
||||||
*
|
*
|
||||||
* @author Matthew Raymer
|
* @author Matthew Raymer
|
||||||
* @version 1.0.0
|
* @version 2.0.0
|
||||||
* @since 2025-07-02
|
* @since 2025-07-02
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||||
import type { PlatformService } from "@/services/PlatformService";
|
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:
|
* Usage:
|
||||||
* ```typescript
|
* ```typescript
|
||||||
@@ -33,8 +38,11 @@ import type { PlatformService } from "@/services/PlatformService";
|
|||||||
* })
|
* })
|
||||||
* export default class MyComponent extends Vue {
|
* export default class MyComponent extends Vue {
|
||||||
* async someMethod() {
|
* async someMethod() {
|
||||||
* // Access cached platform service directly
|
* // Direct access without type assertions
|
||||||
* const result = await this.platformService.dbQuery('SELECT * FROM users');
|
* 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: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Convenient database query method
|
* Enhanced database query method with error handling
|
||||||
* Shorthand for this.platformService.dbQuery()
|
* Prefixed with $ to avoid naming conflicts and improve discoverability
|
||||||
*/
|
*/
|
||||||
async dbQuery(sql: string, params?: unknown[]) {
|
async $dbQuery(sql: string, params?: unknown[]) {
|
||||||
|
try {
|
||||||
return await (this as any).platformService.dbQuery(sql, params);
|
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;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenient database execution method
|
* Enhanced database execution method with error handling
|
||||||
* Shorthand for this.platformService.dbExec()
|
|
||||||
*/
|
*/
|
||||||
async dbExec(sql: string, params?: unknown[]) {
|
async $dbExec(sql: string, params?: unknown[]) {
|
||||||
|
try {
|
||||||
return await (this as any).platformService.dbExec(sql, params);
|
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 single row method
|
* Enhanced database single row method with error handling
|
||||||
* Shorthand for this.platformService.dbGetOneRow()
|
|
||||||
*/
|
*/
|
||||||
async dbGetOneRow(sql: string, params?: unknown[]) {
|
async $dbGetOneRow(sql: string, params?: unknown[]) {
|
||||||
|
try {
|
||||||
return await (this as any).platformService.dbGetOneRow(sql, params);
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method for retrieving and parsing settings
|
||||||
|
* Common pattern used across many components
|
||||||
|
*/
|
||||||
|
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 {
|
export interface IPlatformServiceMixin {
|
||||||
platformService: PlatformService;
|
platformService: PlatformService;
|
||||||
dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
$dbQuery(sql: string, params?: unknown[]): Promise<any>;
|
||||||
dbExec(sql: string, params?: unknown[]): Promise<any>;
|
$dbExec(sql: string, params?: unknown[]): Promise<any>;
|
||||||
dbGetOneRow(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;
|
isCapacitor: boolean;
|
||||||
isWeb: boolean;
|
isWeb: boolean;
|
||||||
isElectron: boolean;
|
isElectron: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user