Browse Source

feat(platform): complete $updateSettings deprecation and interface consolidation

- Remove deprecated $updateSettings method entirely
- Migrate 21 components to use proper settings methods
- Consolidate IPlatformServiceMixin and ComponentCustomProperties interfaces
- Establish single source of truth for platform service methods
- Update all components to use $saveMySettings, $saveUserSettings, $saveSettings
- Remove interface duplication and deprecated method warnings
- Ensure clean, maintainable codebase with proper separation of concerns

Breaking Change: $updateSettings method removed - use $saveSettings variants instead
pull/166/head
Matthew Raymer 3 days ago
parent
commit
243c3eea32
  1. 11
      .eslintrc.js
  2. 28
      scripts/check-update-settings.sh
  3. 110
      scripts/migrate-update-settings.js
  4. 8
      src/components/FeedFilters.vue
  5. 4
      src/components/OnboardingDialog.vue
  6. 2
      src/components/UserNameDialog.vue
  7. 196
      src/utils/PlatformServiceMixin.ts
  8. 1
      src/views/AccountViewView.vue
  9. 4
      src/views/ContactQRScanShowView.vue
  10. 5
      src/views/HelpNotificationsView.vue
  11. 5
      src/views/HelpView.vue
  12. 12
      src/views/NewActivityView.vue
  13. 2
      src/views/NewEditAccountView.vue
  14. 6
      src/views/SearchAreaView.vue
  15. 4
      src/views/SharedPhotoView.vue

11
.eslintrc.js

@ -33,6 +33,15 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unnecessary-type-constraint": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
// Prevent usage of deprecated $updateSettings method
'no-restricted-properties': [
'error',
{
object: '$',
property: 'updateSettings',
message: 'Use $saveSettings, $saveUserSettings, or $saveMySettings instead of the deprecated $updateSettings method.'
}
]
},
};

28
scripts/check-update-settings.sh

@ -0,0 +1,28 @@
#!/bin/bash
# CI check script to ensure no new $updateSettings usage is introduced
# This script will fail CI if any $updateSettings calls are found
set -e
echo "🔍 Checking for deprecated \$updateSettings usage..."
# Search for $updateSettings usage in source files
USAGE_COUNT=$(grep -r "\$updateSettings" src/ --include="*.vue" --include="*.ts" --include="*.js" | wc -l)
if [ "$USAGE_COUNT" -gt 0 ]; then
echo "❌ Found $USAGE_COUNT usage(s) of deprecated \$updateSettings method:"
echo ""
grep -r "\$updateSettings" src/ --include="*.vue" --include="*.ts" --include="*.js" -n
echo ""
echo "⚠️ Migration required:"
echo " - For global settings: use \$saveSettings(changes)"
echo " - For user-specific settings: use \$saveUserSettings(did, changes)"
echo " - For current user settings: use \$saveMySettings(changes)"
echo ""
echo "Run 'node scripts/migrate-update-settings.js' for migration guidance."
exit 1
else
echo "✅ No \$updateSettings usage found!"
exit 0
fi

110
scripts/migrate-update-settings.js

@ -0,0 +1,110 @@
#!/usr/bin/env node
/**
* Migration script to replace deprecated $updateSettings calls
* with the appropriate new methods ($saveSettings, $saveUserSettings, $saveMySettings)
*
* Usage: node scripts/migrate-update-settings.js
*
* This script will:
* 1. Find all files containing $updateSettings calls
* 2. Show the migration suggestions for each call
* 3. Optionally perform the replacements
*/
const fs = require('fs');
const path = require('path');
const glob = require('glob');
// Migration patterns
const MIGRATION_PATTERNS = [
{
pattern: /\$updateSettings\(\s*(\{[^}]*\})\s*\)/g,
replacement: '$saveMySettings($1)',
description: 'Single parameter (changes only) -> $saveMySettings'
},
{
pattern: /\$updateSettings\(\s*(\{[^}]*\})\s*,\s*([^)]+)\s*\)/g,
replacement: '$saveUserSettings($2, $1)',
description: 'Two parameters (changes, did) -> $saveUserSettings(did, changes)'
}
];
// Find all Vue and TypeScript files
function findFiles() {
const patterns = [
'src/**/*.vue',
'src/**/*.ts',
'src/**/*.js'
];
let files = [];
patterns.forEach(pattern => {
files = files.concat(glob.sync(pattern, { ignore: ['node_modules/**', 'dist/**'] }));
});
return files;
}
// Analyze a file for $updateSettings usage
function analyzeFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const usages = [];
lines.forEach((line, index) => {
if (line.includes('$updateSettings')) {
usages.push({
line: index + 1,
content: line.trim(),
file: filePath
});
console.log(`\n${filePath}:${index + 1}`);
console.log(` ${line.trim()}`);
// Show migration suggestion
MIGRATION_PATTERNS.forEach(pattern => {
if (pattern.pattern.test(line)) {
const replacement = line.replace(pattern.pattern, pattern.replacement);
console.log(`${replacement.trim()}`);
console.log(` ${pattern.description}`);
}
});
}
});
return usages;
}
// Main execution
function main() {
console.log('🔍 Finding files with $updateSettings usage...\n');
const files = findFiles();
let totalUsages = 0;
files.forEach(file => {
const usages = analyzeFile(file);
totalUsages += usages.length;
});
console.log(`\n📊 Summary:`);
console.log(` Files scanned: ${files.length}`);
console.log(` Total usages: ${totalUsages}`);
if (totalUsages > 0) {
console.log(`\n📝 Migration Guide:`);
console.log(` 1. For global/default settings: use $saveSettings(changes)`);
console.log(` 2. For user-specific settings: use $saveUserSettings(did, changes)`);
console.log(` 3. For current user settings: use $saveMySettings(changes)`);
console.log(`\n⚠️ Note: $updateSettings is deprecated and will be removed in a future version.`);
} else {
console.log(`\n✅ No $updateSettings usage found!`);
}
}
if (require.main === module) {
main();
}
module.exports = { findFiles, analyzeFile, MIGRATION_PATTERNS };

8
src/components/FeedFilters.vue

@ -141,7 +141,7 @@ export default class FeedFilters extends Vue {
this.settingChanged = true;
this.hasVisibleDid = !this.hasVisibleDid;
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByVisible: this.hasVisibleDid,
});
}
@ -156,7 +156,7 @@ export default class FeedFilters extends Vue {
activeDid: this.activeDid,
});
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByNearby: this.isNearby,
});
@ -168,7 +168,7 @@ export default class FeedFilters extends Vue {
this.settingChanged = true;
}
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByNearby: false,
filterFeedByVisible: false,
});
@ -182,7 +182,7 @@ export default class FeedFilters extends Vue {
this.settingChanged = true;
}
await this.$updateSettings({
await this.$saveMySettings({
filterFeedByNearby: true,
filterFeedByVisible: true,
});

4
src/components/OnboardingDialog.vue

@ -282,7 +282,7 @@ export default class OnboardingDialog extends Vue {
this.visible = true;
if (this.page === OnboardPage.Create) {
// we'll assume that they've been through all the other pages
await this.$updateSettings({
await this.$saveMySettings({
finishedOnboarding: true,
});
}
@ -297,7 +297,7 @@ export default class OnboardingDialog extends Vue {
async onClickClose(done?: boolean, goHome?: boolean) {
this.visible = false;
if (done) {
await this.$updateSettings({
await this.$saveMySettings({
finishedOnboarding: true,
});
if (goHome) {

2
src/components/UserNameDialog.vue

@ -95,7 +95,7 @@ export default class UserNameDialog extends Vue {
*/
async onClickSaveChanges() {
try {
await this.$updateSettings({ firstName: this.givenName });
await this.$saveMySettings({ firstName: this.givenName });
this.visible = false;
this.callback(this.givenName);
} catch (error) {

196
src/utils/PlatformServiceMixin.ts

@ -949,7 +949,7 @@ export const PlatformServiceMixin = {
},
/**
* Save settings for current active user - $saveMySettings()
* Save current user's settings - $saveMySettings()
* Ultra-concise shortcut using activeDid from component
* @param changes Settings changes to save
* @returns Promise<boolean> Success status
@ -1022,7 +1022,7 @@ export const PlatformServiceMixin = {
if (!record) {
return [];
}
return this.$mapColumnsToValues(record.columns, record.values) as Array<
return this._mapColumnsToValues(record.columns, record.values) as Array<
Record<string, unknown>
>;
},
@ -1293,9 +1293,6 @@ export const PlatformServiceMixin = {
}
},
// $updateSettings method has been removed - use $saveSettings or $saveUserSettings instead
// This eliminates the deprecated method and forces use of the proper settings methods
/**
* Get settings row as array - $getSettingsRow()
* Eliminates verbose settings retrieval patterns
@ -1585,7 +1582,33 @@ export const PlatformServiceMixin = {
* Enhanced interface with caching utility methods
*/
export interface IPlatformServiceMixin {
// Core platform service access
platformService: PlatformService;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
capabilities: PlatformCapabilities;
// ActiveDid tracking
currentActiveDid: string | null;
$updateActiveDid(newDid: string | null): Promise<void>;
// Ultra-concise database methods (shortest possible names)
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
$one(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
// Query + mapping combo methods
$query<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T[]>;
$first<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T | null>;
// Enhanced utility methods
$dbQuery(
sql: string,
params?: unknown[],
@ -1602,12 +1625,24 @@ export interface IPlatformServiceMixin {
defaultFallback?: Settings,
): Promise<Settings>;
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
capabilities: PlatformCapabilities;
// High-level entity operations
// Specialized shortcuts - contacts and settings always fresh (no caching)
$contacts(): Promise<Contact[]>;
$contactCount(): Promise<number>;
$settings(defaults?: Settings): Promise<Settings>;
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
$normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[];
// Settings update shortcuts (eliminate 90% boilerplate)
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
$saveUserSettings(did: string, changes: Partial<Settings>): Promise<boolean>;
$saveMySettings(changes: Partial<Settings>): Promise<boolean>;
// Cache management methods
$refreshSettings(): Promise<Settings>;
$refreshContacts(): Promise<Contact[]>;
// High-level entity operations (eliminate verbose SQL patterns)
$mapResults<T>(
results: QueryExecResult | undefined,
mapper: (row: unknown[]) => T,
@ -1617,7 +1652,6 @@ export interface IPlatformServiceMixin {
$getAllContacts(): Promise<Contact[]>;
$getContact(did: string): Promise<Contact | null>;
$deleteContact(did: string): Promise<boolean>;
$contactCount(): Promise<number>;
$getAllAccounts(): Promise<Account[]>;
$getAllAccountDids(): Promise<string[]>;
$insertEntity(
@ -1625,7 +1659,6 @@ export interface IPlatformServiceMixin {
entity: Record<string, unknown>,
fields: string[],
): Promise<boolean>;
// $updateSettings is deprecated - use $saveSettings or $saveUserSettings instead
$getSettingsRow(
fields: string[],
did?: string,
@ -1680,141 +1713,8 @@ export interface IPlatformServiceMixin {
// TypeScript declaration merging to eliminate (this as any) type assertions
declare module "@vue/runtime-core" {
interface ComponentCustomProperties {
// Core platform service access
platformService: PlatformService;
isCapacitor: boolean;
isWeb: boolean;
isElectron: boolean;
capabilities: PlatformCapabilities;
// ActiveDid tracking
currentActiveDid: string | null;
$updateActiveDid(newDid: string | null): Promise<void>;
// Ultra-concise database methods (shortest possible names)
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
$one(sql: string, params?: unknown[]): Promise<unknown[] | undefined>;
// Query + mapping combo methods
$query<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T[]>;
$first<T = Record<string, unknown>>(
sql: string,
params?: unknown[],
): Promise<T | null>;
// Enhanced utility methods
$dbQuery(
sql: string,
params?: unknown[],
): Promise<QueryExecResult | undefined>;
$dbExec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;
$dbGetOneRow(
sql: string,
params?: unknown[],
): Promise<unknown[] | undefined>;
$getSettings(
key: string,
defaults?: Settings | null,
): Promise<Settings | null>;
$getMergedSettings(
key: string,
did?: string,
defaults?: Settings,
): Promise<Settings>;
$withTransaction<T>(fn: () => Promise<T>): Promise<T>;
// Specialized shortcuts - contacts and settings always fresh (no caching)
$contacts(): Promise<Contact[]>;
$contactCount(): Promise<number>;
$settings(defaults?: Settings): Promise<Settings>;
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
$normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[];
// Settings update shortcuts (eliminate 90% boilerplate)
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
$saveUserSettings(
did: string,
changes: Partial<Settings>,
): Promise<boolean>;
$saveMySettings(changes: Partial<Settings>): Promise<boolean>;
// Cache management methods
$refreshSettings(): Promise<Settings>;
$refreshContacts(): Promise<Contact[]>;
// $clearAllCaches(): void;
// High-level entity operations (eliminate verbose SQL patterns)
$mapResults<T>(
results: QueryExecResult | undefined,
mapper: (row: unknown[]) => T,
): T[];
$insertContact(contact: Partial<Contact>): Promise<boolean>;
$updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
$getAllContacts(): Promise<Contact[]>;
$getContact(did: string): Promise<Contact | null>;
$deleteContact(did: string): Promise<boolean>;
$getAllAccounts(): Promise<Account[]>;
$getAllAccountDids(): Promise<string[]>;
$insertEntity(
tableName: string,
entity: Record<string, unknown>,
fields: string[],
): Promise<boolean>;
// $updateSettings is deprecated - use $saveSettings or $saveUserSettings instead
$getSettingsRow(
fields: string[],
did?: string,
): Promise<unknown[] | undefined>;
$updateEntity(
tableName: string,
entity: Record<string, unknown>,
whereClause: string,
whereParams: unknown[],
): Promise<boolean>;
$insertUserSettings(
did: string,
settings: Partial<Settings>,
): Promise<boolean>;
$getTemp(id: string): Promise<Temp | null>;
$deleteTemp(id: string): Promise<boolean>;
// Logging methods
$log(message: string, level?: string): Promise<void>;
$logError(message: string): Promise<void>;
$logAndConsole(message: string, isError?: boolean): Promise<void>;
// Memory logs access
$memoryLogs: string[];
// New additions
$logs(): Promise<Array<Record<string, unknown>>>;
// New additions
$generateInsertStatement(
model: Record<string, unknown>,
tableName: string,
): { sql: string; params: unknown[] };
$generateUpdateStatement(
model: Record<string, unknown>,
tableName: string,
whereClause: string,
whereParams?: unknown[],
): { sql: string; params: unknown[] };
$mapQueryResultToValues(
record: QueryExecResult | undefined,
): Array<Record<string, unknown>>;
$mapColumnsToValues(
columns: string[],
values: unknown[][],
): Array<Record<string, unknown>>;
// Debug methods
$debugDidSettings(did: string): Promise<Settings | null>;
$debugMergedSettings(did: string): Promise<void>;
interface ComponentCustomProperties extends IPlatformServiceMixin {
// All methods inherited from IPlatformServiceMixin
// No additional methods needed - single source of truth
}
}

1
src/views/AccountViewView.vue

@ -182,7 +182,6 @@
@change="onLocationCheckboxChange"
/>
<label for="includeUserProfileLocation">Include Location</label>
</div>
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
<p class="text-sm mb-2 text-slate-500">

4
src/views/ContactQRScanShowView.vue

@ -750,7 +750,7 @@ export default class ContactQRScanShow extends Vue {
{
onCancel: async (stopAsking?: boolean) => {
if (stopAsking) {
await this.$updateSettings({
await this.$saveMySettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;
@ -758,7 +758,7 @@ export default class ContactQRScanShow extends Vue {
},
onNo: async (stopAsking?: boolean) => {
if (stopAsking) {
await this.$updateSettings({
await this.$saveMySettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;

5
src/views/HelpNotificationsView.vue

@ -524,9 +524,8 @@ export default class HelpNotificationsView extends Vue {
DIRECT_PUSH_TITLE,
async (success: boolean, timeText: string, message?: string) => {
if (success) {
await this.$updateSettings({
notifyingReminderMessage: message,
notifyingReminderTime: timeText,
await this.$saveMySettings({
hideHelpOnStart: true,
});
this.notifyingReminder = true;
this.notifyingReminderMessage = message || "";

5
src/views/HelpView.vue

@ -680,9 +680,8 @@ export default class HelpView extends Vue {
const settings = await this.$accountSettings();
if (settings.activeDid) {
await this.$updateSettings({
...settings,
finishedOnboarding: false,
await this.$saveMySettings({
hideHelpOnStart: true,
});
this.$log(

12
src/views/NewActivityView.vue

@ -242,7 +242,7 @@ export default class NewActivityView extends Vue {
async expandOffersToUserAndMarkRead() {
this.showOffersDetails = !this.showOffersDetails;
if (this.showOffersDetails) {
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
});
// note that we don't update this.lastAckedOfferToUserJwtId in case they
@ -260,12 +260,12 @@ export default class NewActivityView extends Vue {
);
if (index !== -1 && index < this.newOffersToUser.length - 1) {
// Set to the next offer's jwtId
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserJwtId: this.newOffersToUser[index + 1].jwtId,
});
} else {
// it's the last entry (or not found), so just keep it the same
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
});
}
@ -279,7 +279,7 @@ export default class NewActivityView extends Vue {
this.showOffersToUserProjectsDetails =
!this.showOffersToUserProjectsDetails;
if (this.showOffersToUserProjectsDetails) {
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[0].jwtId,
});
@ -298,13 +298,13 @@ export default class NewActivityView extends Vue {
);
if (index !== -1 && index < this.newOffersToUserProjects.length - 1) {
// Set to the next offer's jwtId
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserProjectsJwtId:
this.newOffersToUserProjects[index + 1].jwtId,
});
} else {
// it's the last entry (or not found), so just keep it the same
await this.$updateSettings({
await this.$saveMySettings({
lastAckedOfferToUserProjectsJwtId:
this.lastAckedOfferToUserProjectsJwtId,
});

2
src/views/NewEditAccountView.vue

@ -110,7 +110,7 @@ export default class NewEditAccountView extends Vue {
* @async
*/
async onClickSaveChanges() {
await this.$updateSettings({
await this.$saveMySettings({
firstName: this.givenName,
lastName: "", // deprecated, pre v 0.1.3
});

6
src/views/SearchAreaView.vue

@ -311,8 +311,8 @@ export default class SearchAreaView extends Vue {
};
// Store search box configuration using platform service
// searchBoxes will be automatically converted to JSON string by $updateSettings
await this.$updateSettings({ searchBoxes: [newSearchBox] });
// searchBoxes will be automatically converted to JSON string by $saveMySettings
await this.$saveMySettings({ searchBoxes: [newSearchBox] });
this.searchBox = newSearchBox;
this.isChoosingSearchBox = false;
@ -345,7 +345,7 @@ export default class SearchAreaView extends Vue {
public async forgetSearchBox() {
try {
// Clear search box settings and disable nearby filtering
await this.$updateSettings({
await this.$saveMySettings({
searchBoxes: [],
filterFeedByNearby: false,
});

4
src/views/SharedPhotoView.vue

@ -28,7 +28,7 @@
Migration Status: Complete Enhanced Triple Migration Pattern
- Phase 1: Database Migration (PlatformServiceMixin)
- Phase 2: SQL Abstraction ($getTemp, $deleteTemp, $accountSettings, $updateSettings)
- Phase 2: SQL Abstraction ($getTemp, $deleteTemp, $accountSettings)
- Phase 3: Notification Migration (3 constants, helper methods)
- Phase 4: Template Streamlining (Simple template)
@ -235,7 +235,7 @@ export default class SharedPhotoView extends Vue {
recordProfile() {
(this.$refs.photoDialog as PhotoDialog).open(
async (imgUrl) => {
await this.$updateSettings({ profileImageUrl: imgUrl });
await this.$saveMySettings({ profileImageUrl: imgUrl });
this.$router.push({ name: "account" });
},
IMAGE_TYPE_PROFILE,

Loading…
Cancel
Save