forked from jsnbuchanan/crowd-funder-for-time-pwa
feat: implement ActiveDid migration to active_identity table
- Add $getActiveIdentity() method to PlatformServiceMixin interface - Update HomeView.vue to use new active_identity API methods - Update ContactsView.vue to use new active_identity API methods - Fix apiServer default handling in PlatformServiceMixin - Ensure DEFAULT_ENDORSER_API_SERVER is used when apiServer is empty - Add comprehensive logging for debugging ActiveDid migration - Resolve TypeScript interface issues with Vue mixins
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
} from "../services/migrationService";
|
} from "../services/migrationService";
|
||||||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
||||||
import { arrayBufferToBase64 } from "@/libs/crypto";
|
import { arrayBufferToBase64 } from "@/libs/crypto";
|
||||||
|
import { logger } from "@/utils/logger";
|
||||||
|
|
||||||
// Generate a random secret for the secret table
|
// Generate a random secret for the secret table
|
||||||
|
|
||||||
@@ -151,6 +152,50 @@ const MIGRATIONS = [
|
|||||||
AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '');
|
AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '');
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "004_remove_activeDid_from_settings",
|
||||||
|
sql: `
|
||||||
|
-- Remove activeDid column from settings table (moved to active_identity)
|
||||||
|
-- Note: SQLite doesn't support DROP COLUMN in older versions
|
||||||
|
-- This migration will be skipped if DROP COLUMN is not supported
|
||||||
|
-- The activeDid column will remain but won't be used by the application
|
||||||
|
|
||||||
|
-- Try to drop the activeDid column (works in SQLite 3.35.0+)
|
||||||
|
ALTER TABLE settings DROP COLUMN activeDid;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "005_eliminate_master_settings_key",
|
||||||
|
sql: `
|
||||||
|
-- Eliminate MASTER_SETTINGS_KEY concept - remove confusing id=1 row
|
||||||
|
-- This creates clean separation: active_identity for current identity, settings for identity config
|
||||||
|
|
||||||
|
-- Delete the confusing MASTER_SETTINGS_KEY row (id=1 with accountDid=NULL)
|
||||||
|
DELETE FROM settings WHERE id = 1 AND accountDid IS NULL;
|
||||||
|
|
||||||
|
-- Reset auto-increment to start from 1 again
|
||||||
|
DELETE FROM sqlite_sequence WHERE name = 'settings';
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "006_add_unique_constraint_accountDid",
|
||||||
|
sql: `
|
||||||
|
-- Add unique constraint to prevent duplicate accountDid values
|
||||||
|
-- This ensures data integrity: each identity can only have one settings record
|
||||||
|
|
||||||
|
-- First, remove any duplicate accountDid entries (keep the most recent one)
|
||||||
|
DELETE FROM settings
|
||||||
|
WHERE id NOT IN (
|
||||||
|
SELECT MAX(id)
|
||||||
|
FROM settings
|
||||||
|
WHERE accountDid IS NOT NULL
|
||||||
|
GROUP BY accountDid
|
||||||
|
) AND accountDid IS NOT NULL;
|
||||||
|
|
||||||
|
-- Add unique constraint on accountDid
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_settings_accountDid_unique ON settings(accountDid);
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,8 +207,14 @@ export async function runMigrations<T>(
|
|||||||
sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
|
sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
|
||||||
extractMigrationNames: (result: T) => Set<string>,
|
extractMigrationNames: (result: T) => Set<string>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
logger.info("[Migration] Starting database migrations");
|
||||||
|
|
||||||
for (const migration of MIGRATIONS) {
|
for (const migration of MIGRATIONS) {
|
||||||
|
logger.debug("[Migration] Registering migration:", migration.name);
|
||||||
registerMigration(migration);
|
registerMigration(migration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("[Migration] Running migration service");
|
||||||
await runMigrationsService(sqlExec, sqlQuery, extractMigrationNames);
|
await runMigrationsService(sqlExec, sqlQuery, extractMigrationNames);
|
||||||
|
logger.info("[Migration] Database migrations completed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import type {
|
|||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "@/services/PlatformService";
|
} from "@/services/PlatformService";
|
||||||
import {
|
import {
|
||||||
MASTER_SETTINGS_KEY,
|
|
||||||
type Settings,
|
type Settings,
|
||||||
type SettingsWithJsonStrings,
|
type SettingsWithJsonStrings,
|
||||||
} from "@/db/tables/settings";
|
} from "@/db/tables/settings";
|
||||||
@@ -58,8 +57,6 @@ import {
|
|||||||
generateInsertStatement,
|
generateInsertStatement,
|
||||||
generateUpdateStatement,
|
generateUpdateStatement,
|
||||||
} from "@/utils/sqlHelpers";
|
} from "@/utils/sqlHelpers";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
import { ActiveIdentity } from "@/db/tables/activeIdentity";
|
|
||||||
|
|
||||||
// =================================================
|
// =================================================
|
||||||
// TYPESCRIPT INTERFACES
|
// TYPESCRIPT INTERFACES
|
||||||
@@ -198,6 +195,80 @@ export const PlatformServiceMixin = {
|
|||||||
// SELF-CONTAINED UTILITY METHODS (no databaseUtil dependency)
|
// SELF-CONTAINED UTILITY METHODS (no databaseUtil dependency)
|
||||||
// =================================================
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure active_identity table is populated with data from settings
|
||||||
|
* This is a one-time fix for the migration gap
|
||||||
|
*/
|
||||||
|
async $ensureActiveIdentityPopulated(): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info(
|
||||||
|
"[PlatformServiceMixin] $ensureActiveIdentityPopulated() called",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if active_identity has data
|
||||||
|
const activeIdentity = await this.$dbQuery(
|
||||||
|
"SELECT activeDid FROM active_identity WHERE id = 1",
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentActiveDid = activeIdentity?.values?.[0]?.[0] as string;
|
||||||
|
logger.info(
|
||||||
|
"[PlatformServiceMixin] Current active_identity table state:",
|
||||||
|
{ currentActiveDid, hasData: !!currentActiveDid },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentActiveDid) {
|
||||||
|
logger.info(
|
||||||
|
"[PlatformServiceMixin] Active identity table empty, populating from settings",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get activeDid from settings (any row with accountDid)
|
||||||
|
const settings = await this.$dbQuery(
|
||||||
|
"SELECT accountDid FROM settings WHERE accountDid IS NOT NULL LIMIT 1",
|
||||||
|
);
|
||||||
|
|
||||||
|
const settingsAccountDid = settings?.values?.[0]?.[0] as string;
|
||||||
|
logger.info("[PlatformServiceMixin] Found settings accountDid:", {
|
||||||
|
settingsAccountDid,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (settingsAccountDid) {
|
||||||
|
await this.$dbExec(
|
||||||
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||||
|
[settingsAccountDid],
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
`[PlatformServiceMixin] Populated active_identity with: ${settingsAccountDid}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// If no settings found, try to get any account DID
|
||||||
|
const accounts = await this.$dbQuery(
|
||||||
|
"SELECT did FROM accounts LIMIT 1",
|
||||||
|
);
|
||||||
|
const accountDid = accounts?.values?.[0]?.[0] as string;
|
||||||
|
|
||||||
|
if (accountDid) {
|
||||||
|
await this.$dbExec(
|
||||||
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||||
|
[accountDid],
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
`[PlatformServiceMixin] Populated active_identity with account DID: ${accountDid}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
"[PlatformServiceMixin] No accountDid found in settings or accounts table",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(
|
||||||
|
"[PlatformServiceMixin] Failed to populate active_identity:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the current activeDid and trigger change detection
|
* Update the current activeDid and trigger change detection
|
||||||
* This method should be called when the user switches identities
|
* This method should be called when the user switches identities
|
||||||
@@ -213,22 +284,18 @@ export const PlatformServiceMixin = {
|
|||||||
`[PlatformServiceMixin] ActiveDid updated from ${oldDid} to ${newDid}`,
|
`[PlatformServiceMixin] ActiveDid updated from ${oldDid} to ${newDid}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Dual-write to both tables for backward compatibility
|
// Write only to active_identity table (single source of truth)
|
||||||
try {
|
try {
|
||||||
await this.$dbExec(
|
await this.$dbExec(
|
||||||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||||
[newDid || ""],
|
[newDid || ""],
|
||||||
);
|
);
|
||||||
await this.$dbExec("UPDATE settings SET activeDid = ? WHERE id = ?", [
|
|
||||||
newDid || "",
|
|
||||||
MASTER_SETTINGS_KEY,
|
|
||||||
]);
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[PlatformServiceMixin] ActiveDid dual-write completed for ${newDid}`,
|
`[PlatformServiceMixin] ActiveDid updated in active_identity table: ${newDid}`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`[PlatformServiceMixin] Error in dual-write for activeDid ${newDid}:`,
|
`[PlatformServiceMixin] Error updating activeDid in active_identity table ${newDid}:`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
// Continue with in-memory update even if database write fails
|
// Continue with in-memory update even if database write fails
|
||||||
@@ -468,10 +535,18 @@ export const PlatformServiceMixin = {
|
|||||||
fallback: Settings | null = null,
|
fallback: Settings | null = null,
|
||||||
): Promise<Settings | null> {
|
): Promise<Settings | null> {
|
||||||
try {
|
try {
|
||||||
// Master settings: query by id
|
// Get current active identity
|
||||||
|
const activeIdentity = await this.$getActiveIdentity();
|
||||||
|
const activeDid = activeIdentity.activeDid;
|
||||||
|
|
||||||
|
if (!activeDid) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get identity-specific settings
|
||||||
const result = await this.$dbQuery(
|
const result = await this.$dbQuery(
|
||||||
"SELECT * FROM settings WHERE id = ?",
|
"SELECT * FROM settings WHERE accountDid = ?",
|
||||||
[MASTER_SETTINGS_KEY],
|
[activeDid],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result?.values?.length) {
|
if (!result?.values?.length) {
|
||||||
@@ -508,7 +583,6 @@ export const PlatformServiceMixin = {
|
|||||||
* Handles the common pattern of layered settings
|
* Handles the common pattern of layered settings
|
||||||
*/
|
*/
|
||||||
async $getMergedSettings(
|
async $getMergedSettings(
|
||||||
defaultKey: string,
|
|
||||||
accountDid?: string,
|
accountDid?: string,
|
||||||
defaultFallback: Settings = {},
|
defaultFallback: Settings = {},
|
||||||
): Promise<Settings> {
|
): Promise<Settings> {
|
||||||
@@ -564,7 +638,6 @@ export const PlatformServiceMixin = {
|
|||||||
return mergedSettings;
|
return mergedSettings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[Settings Trace] ❌ Failed to get merged settings:`, {
|
logger.error(`[Settings Trace] ❌ Failed to get merged settings:`, {
|
||||||
defaultKey,
|
|
||||||
accountDid,
|
accountDid,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
@@ -578,12 +651,29 @@ export const PlatformServiceMixin = {
|
|||||||
*/
|
*/
|
||||||
async $getActiveIdentity(): Promise<{ activeDid: string }> {
|
async $getActiveIdentity(): Promise<{ activeDid: string }> {
|
||||||
try {
|
try {
|
||||||
|
logger.info(
|
||||||
|
"[PlatformServiceMixin] $getActiveIdentity() called - API layer verification",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure the table is populated before reading
|
||||||
|
await this.$ensureActiveIdentityPopulated();
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] Getting active identity from active_identity table",
|
||||||
|
);
|
||||||
const result = await this.$dbQuery(
|
const result = await this.$dbQuery(
|
||||||
"SELECT activeDid FROM active_identity WHERE id = 1",
|
"SELECT activeDid FROM active_identity WHERE id = 1",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result?.values?.length) {
|
if (result?.values?.length) {
|
||||||
const activeDid = result.values[0][0] as string;
|
const activeDid = result.values[0][0] as string;
|
||||||
|
logger.debug("[PlatformServiceMixin] Active identity found:", {
|
||||||
|
activeDid,
|
||||||
|
});
|
||||||
|
logger.info(
|
||||||
|
"[PlatformServiceMixin] $getActiveIdentity(): activeDid resolved",
|
||||||
|
{ activeDid },
|
||||||
|
);
|
||||||
|
|
||||||
// Validate activeDid exists in accounts
|
// Validate activeDid exists in accounts
|
||||||
if (activeDid) {
|
if (activeDid) {
|
||||||
@@ -593,9 +683,15 @@ export const PlatformServiceMixin = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (accountExists?.values?.length) {
|
if (accountExists?.values?.length) {
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] Active identity validated in accounts",
|
||||||
|
);
|
||||||
return { activeDid };
|
return { activeDid };
|
||||||
} else {
|
} else {
|
||||||
// Clear corrupted activeDid
|
// Clear corrupted activeDid
|
||||||
|
logger.warn(
|
||||||
|
"[PlatformServiceMixin] Active identity not found in accounts, clearing",
|
||||||
|
);
|
||||||
await this.$dbExec(
|
await this.$dbExec(
|
||||||
"UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1",
|
"UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1",
|
||||||
);
|
);
|
||||||
@@ -604,6 +700,9 @@ export const PlatformServiceMixin = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] No active identity found, returning empty",
|
||||||
|
);
|
||||||
return { activeDid: "" };
|
return { activeDid: "" };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -825,14 +924,14 @@ export const PlatformServiceMixin = {
|
|||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXED: Remove forced override - respect user preferences
|
// FIXED: Set default apiServer for all platforms, not just Electron
|
||||||
// Only set default if no user preference exists
|
// Only set default if no user preference exists
|
||||||
if (!settings.apiServer && process.env.VITE_PLATFORM === "electron") {
|
if (!settings.apiServer) {
|
||||||
// Import constants dynamically to get platform-specific values
|
// Import constants dynamically to get platform-specific values
|
||||||
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
||||||
"../constants/app"
|
"../constants/app"
|
||||||
);
|
);
|
||||||
// Only set if user hasn't specified a preference
|
// Set default for all platforms when apiServer is empty
|
||||||
settings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
settings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,10 +957,9 @@ export const PlatformServiceMixin = {
|
|||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine which DID to use - prioritize new active_identity table, fallback to settings
|
// Get DID from active_identity table (single source of truth)
|
||||||
const activeIdentity = await this.$getActiveIdentity();
|
const activeIdentity = await this.$getActiveIdentity();
|
||||||
const targetDid =
|
const targetDid = did || activeIdentity.activeDid;
|
||||||
did || activeIdentity.activeDid || defaultSettings.activeDid;
|
|
||||||
|
|
||||||
// If no target DID, return default settings
|
// If no target DID, return default settings
|
||||||
if (!targetDid) {
|
if (!targetDid) {
|
||||||
@@ -870,27 +968,29 @@ export const PlatformServiceMixin = {
|
|||||||
|
|
||||||
// Get merged settings using existing method
|
// Get merged settings using existing method
|
||||||
const mergedSettings = await this.$getMergedSettings(
|
const mergedSettings = await this.$getMergedSettings(
|
||||||
MASTER_SETTINGS_KEY,
|
|
||||||
targetDid,
|
targetDid,
|
||||||
defaultSettings,
|
defaultSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure activeDid comes from new table when available
|
// Set activeDid from active_identity table (single source of truth)
|
||||||
if (activeIdentity.activeDid) {
|
mergedSettings.activeDid = activeIdentity.activeDid;
|
||||||
mergedSettings.activeDid = activeIdentity.activeDid;
|
logger.debug(
|
||||||
}
|
"[PlatformServiceMixin] Using activeDid from active_identity table:",
|
||||||
|
{ activeDid: activeIdentity.activeDid },
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
"[PlatformServiceMixin] $accountSettings() returning activeDid:",
|
||||||
|
{ activeDid: mergedSettings.activeDid },
|
||||||
|
);
|
||||||
|
|
||||||
// FIXED: Remove forced override - respect user preferences
|
// FIXED: Set default apiServer for all platforms, not just Electron
|
||||||
// Only set default if no user preference exists
|
// Only set default if no user preference exists
|
||||||
if (
|
if (!mergedSettings.apiServer) {
|
||||||
!mergedSettings.apiServer &&
|
|
||||||
process.env.VITE_PLATFORM === "electron"
|
|
||||||
) {
|
|
||||||
// Import constants dynamically to get platform-specific values
|
// Import constants dynamically to get platform-specific values
|
||||||
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
const { DEFAULT_ENDORSER_API_SERVER } = await import(
|
||||||
"../constants/app"
|
"../constants/app"
|
||||||
);
|
);
|
||||||
// Only set if user hasn't specified a preference
|
// Set default for all platforms when apiServer is empty
|
||||||
mergedSettings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
mergedSettings.apiServer = DEFAULT_ENDORSER_API_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -928,16 +1028,36 @@ export const PlatformServiceMixin = {
|
|||||||
async $saveSettings(changes: Partial<Settings>): Promise<boolean> {
|
async $saveSettings(changes: Partial<Settings>): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// Remove fields that shouldn't be updated
|
// Remove fields that shouldn't be updated
|
||||||
const { accountDid, id, ...safeChanges } = changes;
|
const {
|
||||||
|
accountDid,
|
||||||
|
id,
|
||||||
|
activeDid: activeDidField,
|
||||||
|
...safeChanges
|
||||||
|
} = changes;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
void accountDid;
|
void accountDid;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
void id;
|
void id;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
void activeDidField;
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] $saveSettings - Original changes:",
|
||||||
|
changes,
|
||||||
|
);
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] $saveSettings - Safe changes:",
|
||||||
|
safeChanges,
|
||||||
|
);
|
||||||
|
|
||||||
if (Object.keys(safeChanges).length === 0) return true;
|
if (Object.keys(safeChanges).length === 0) return true;
|
||||||
|
|
||||||
// Convert settings for database storage (handles searchBoxes conversion)
|
// Convert settings for database storage (handles searchBoxes conversion)
|
||||||
const convertedChanges = this._convertSettingsForStorage(safeChanges);
|
const convertedChanges = this._convertSettingsForStorage(safeChanges);
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] $saveSettings - Converted changes:",
|
||||||
|
convertedChanges,
|
||||||
|
);
|
||||||
|
|
||||||
const setParts: string[] = [];
|
const setParts: string[] = [];
|
||||||
const params: unknown[] = [];
|
const params: unknown[] = [];
|
||||||
@@ -949,17 +1069,33 @@ export const PlatformServiceMixin = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[PlatformServiceMixin] $saveSettings - Set parts:",
|
||||||
|
setParts,
|
||||||
|
);
|
||||||
|
logger.debug("[PlatformServiceMixin] $saveSettings - Params:", params);
|
||||||
|
|
||||||
if (setParts.length === 0) return true;
|
if (setParts.length === 0) return true;
|
||||||
|
|
||||||
params.push(MASTER_SETTINGS_KEY);
|
// Get current active DID and update that identity's settings
|
||||||
await this.$dbExec(
|
const activeIdentity = await this.$getActiveIdentity();
|
||||||
`UPDATE settings SET ${setParts.join(", ")} WHERE id = ?`,
|
const currentActiveDid = activeIdentity.activeDid;
|
||||||
params,
|
|
||||||
);
|
if (currentActiveDid) {
|
||||||
|
params.push(currentActiveDid);
|
||||||
|
await this.$dbExec(
|
||||||
|
`UPDATE settings SET ${setParts.join(", ")} WHERE accountDid = ?`,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
"[PlatformServiceMixin] No active DID found, cannot save settings",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update activeDid tracking if it changed
|
// Update activeDid tracking if it changed
|
||||||
if (changes.activeDid !== undefined) {
|
if (activeDidField !== undefined) {
|
||||||
await this.$updateActiveDid(changes.activeDid);
|
await this.$updateActiveDid(activeDidField);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1409,13 +1545,16 @@ export const PlatformServiceMixin = {
|
|||||||
fields: string[],
|
fields: string[],
|
||||||
did?: string,
|
did?: string,
|
||||||
): Promise<unknown[] | undefined> {
|
): Promise<unknown[] | undefined> {
|
||||||
// Use correct settings table schema
|
// Use current active DID if no specific DID provided
|
||||||
const whereClause = did ? "WHERE accountDid = ?" : "WHERE id = ?";
|
const targetDid = did || (await this.$getActiveIdentity()).activeDid;
|
||||||
const params = did ? [did] : [MASTER_SETTINGS_KEY];
|
|
||||||
|
if (!targetDid) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return await this.$one(
|
return await this.$one(
|
||||||
`SELECT ${fields.join(", ")} FROM settings ${whereClause}`,
|
`SELECT ${fields.join(", ")} FROM settings WHERE accountDid = ?`,
|
||||||
params,
|
[targetDid],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1655,7 +1794,6 @@ export const PlatformServiceMixin = {
|
|||||||
|
|
||||||
// Get merged settings
|
// Get merged settings
|
||||||
const mergedSettings = await this.$getMergedSettings(
|
const mergedSettings = await this.$getMergedSettings(
|
||||||
MASTER_SETTINGS_KEY,
|
|
||||||
did,
|
did,
|
||||||
defaultSettings || {},
|
defaultSettings || {},
|
||||||
);
|
);
|
||||||
@@ -1697,6 +1835,7 @@ export interface IPlatformServiceMixin {
|
|||||||
accountDid?: string,
|
accountDid?: string,
|
||||||
defaultFallback?: Settings,
|
defaultFallback?: Settings,
|
||||||
): Promise<Settings>;
|
): Promise<Settings>;
|
||||||
|
$getActiveIdentity(): Promise<{ activeDid: string }>;
|
||||||
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
|
$withTransaction<T>(callback: () => Promise<T>): Promise<T>;
|
||||||
isCapacitor: boolean;
|
isCapacitor: boolean;
|
||||||
isWeb: boolean;
|
isWeb: boolean;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ import { logger } from "../utils/logger";
|
|||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
import { isDatabaseError } from "@/interfaces/common";
|
import { isDatabaseError } from "@/interfaces/common";
|
||||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
import { APP_SERVER } from "@/constants/app";
|
import { APP_SERVER, DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
||||||
import { QRNavigationService } from "@/services/QRNavigationService";
|
import { QRNavigationService } from "@/services/QRNavigationService";
|
||||||
import {
|
import {
|
||||||
NOTIFY_CONTACT_NO_INFO,
|
NOTIFY_CONTACT_NO_INFO,
|
||||||
@@ -294,10 +294,19 @@ export default class ContactsView extends Vue {
|
|||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
// Get activeDid from active_identity table (single source of truth)
|
||||||
this.apiServer = settings.apiServer || "";
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const activeIdentity = await (this as any).$getActiveIdentity();
|
||||||
|
this.activeDid = activeIdentity.activeDid || "";
|
||||||
|
this.apiServer = settings.apiServer || DEFAULT_ENDORSER_API_SERVER;
|
||||||
this.isRegistered = !!settings.isRegistered;
|
this.isRegistered = !!settings.isRegistered;
|
||||||
|
|
||||||
|
logger.info("[ContactsView] Created with settings:", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
apiServer: this.apiServer,
|
||||||
|
isRegistered: this.isRegistered,
|
||||||
|
});
|
||||||
|
|
||||||
// if these detect a query parameter, they can and then redirect to this URL without a query parameter
|
// if these detect a query parameter, they can and then redirect to this URL without a query parameter
|
||||||
// to avoid problems when they reload or they go forward & back and it tries to reprocess
|
// to avoid problems when they reload or they go forward & back and it tries to reprocess
|
||||||
await this.processContactJwt();
|
await this.processContactJwt();
|
||||||
@@ -346,15 +355,37 @@ export default class ContactsView extends Vue {
|
|||||||
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
|
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
|
||||||
this.notify.error(NOTIFY_BLANK_INVITE.message, TIMEOUTS.VERY_LONG);
|
this.notify.error(NOTIFY_BLANK_INVITE.message, TIMEOUTS.VERY_LONG);
|
||||||
} else if (importedInviteJwt) {
|
} else if (importedInviteJwt) {
|
||||||
|
logger.info("[ContactsView] Processing invite JWT, current activeDid:", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure active_identity is populated before processing invite
|
||||||
|
await this.$ensureActiveIdentityPopulated();
|
||||||
|
|
||||||
|
// Re-fetch settings after ensuring active_identity is populated
|
||||||
|
const updatedSettings = await this.$accountSettings();
|
||||||
|
this.activeDid = updatedSettings.activeDid || "";
|
||||||
|
this.apiServer = updatedSettings.apiServer || DEFAULT_ENDORSER_API_SERVER;
|
||||||
|
|
||||||
// Identity creation should be handled by router guard, but keep as fallback for invite processing
|
// Identity creation should be handled by router guard, but keep as fallback for invite processing
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
logger.info(
|
logger.info(
|
||||||
"[ContactsView] No active DID found, creating identity as fallback for invite processing",
|
"[ContactsView] No active DID found, creating identity as fallback for invite processing",
|
||||||
);
|
);
|
||||||
this.activeDid = await generateSaveAndActivateIdentity();
|
this.activeDid = await generateSaveAndActivateIdentity();
|
||||||
|
logger.info("[ContactsView] Created new identity:", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// send invite directly to server, with auth for this user
|
// send invite directly to server, with auth for this user
|
||||||
const headers = await getHeaders(this.activeDid);
|
const headers = await getHeaders(this.activeDid);
|
||||||
|
logger.info("[ContactsView] Making API request to claim invite:", {
|
||||||
|
apiServer: this.apiServer,
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
hasApiServer: !!this.apiServer,
|
||||||
|
apiServerLength: this.apiServer?.length || 0,
|
||||||
|
fullUrl: this.apiServer + "/api/v2/claim",
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const response = await this.axios.post(
|
const response = await this.axios.post(
|
||||||
this.apiServer + "/api/v2/claim",
|
this.apiServer + "/api/v2/claim",
|
||||||
@@ -376,6 +407,9 @@ export default class ContactsView extends Vue {
|
|||||||
const payload: JWTPayload =
|
const payload: JWTPayload =
|
||||||
decodeEndorserJwt(importedInviteJwt).payload;
|
decodeEndorserJwt(importedInviteJwt).payload;
|
||||||
const registration = payload as VerifiableCredential;
|
const registration = payload as VerifiableCredential;
|
||||||
|
logger.info(
|
||||||
|
"[ContactsView] Opening ContactNameDialog for invite processing",
|
||||||
|
);
|
||||||
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
||||||
"Who Invited You?",
|
"Who Invited You?",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ Raymer * @version 1.0.0 */
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { UAParser } from "ua-parser-js";
|
import { UAParser } from "ua-parser-js";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue, Watch } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
//import App from "../App.vue";
|
//import App from "../App.vue";
|
||||||
@@ -283,6 +283,7 @@ import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
|||||||
import { NOTIFY_CONTACT_LOADING_ISSUE } from "@/constants/notifications";
|
import { NOTIFY_CONTACT_LOADING_ISSUE } from "@/constants/notifications";
|
||||||
import * as Package from "../../package.json";
|
import * as Package from "../../package.json";
|
||||||
import { UNNAMED_ENTITY_NAME } from "@/constants/entities";
|
import { UNNAMED_ENTITY_NAME } from "@/constants/entities";
|
||||||
|
import { errorStringForLog } from "../libs/endorserServer";
|
||||||
|
|
||||||
// consolidate this with GiveActionClaim in src/interfaces/claims.ts
|
// consolidate this with GiveActionClaim in src/interfaces/claims.ts
|
||||||
interface Claim {
|
interface Claim {
|
||||||
@@ -399,6 +400,44 @@ export default class HomeView extends Vue {
|
|||||||
newOffersToUserProjectsHitLimit: boolean = false;
|
newOffersToUserProjectsHitLimit: boolean = false;
|
||||||
numNewOffersToUser: number = 0; // number of new offers-to-user
|
numNewOffersToUser: number = 0; // number of new offers-to-user
|
||||||
numNewOffersToUserProjects: number = 0; // number of new offers-to-user's-projects
|
numNewOffersToUserProjects: number = 0; // number of new offers-to-user's-projects
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRITICAL VUE REACTIVITY BUG WORKAROUND
|
||||||
|
*
|
||||||
|
* This watcher is required for the component to render correctly.
|
||||||
|
* Without it, the newDirectOffersActivityNumber element fails to render
|
||||||
|
* even when numNewOffersToUser has the correct value.
|
||||||
|
*
|
||||||
|
* This appears to be a Vue reactivity issue where property changes
|
||||||
|
* don't trigger proper template updates.
|
||||||
|
*
|
||||||
|
* DO NOT REMOVE until the underlying Vue reactivity issue is resolved.
|
||||||
|
*
|
||||||
|
* See: doc/activeDid-migration-plan.md for details
|
||||||
|
*/
|
||||||
|
@Watch("numNewOffersToUser")
|
||||||
|
onNumNewOffersToUserChange(newValue: number, oldValue: number) {
|
||||||
|
logger.debug("[HomeView] numNewOffersToUser changed", {
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
willRender: !!newValue,
|
||||||
|
vIfCondition: `v-if="numNewOffersToUser"`,
|
||||||
|
elementTestId: "newDirectOffersActivityNumber",
|
||||||
|
shouldShowElement: newValue > 0,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get shouldShowNewOffersToUser() {
|
||||||
|
// const shouldShow = !!this.numNewOffersToUser;
|
||||||
|
// logger.debug("[HomeView] shouldShowNewOffersToUser computed", {
|
||||||
|
// numNewOffersToUser: this.numNewOffersToUser,
|
||||||
|
// shouldShow,
|
||||||
|
// timestamp: new Date().toISOString()
|
||||||
|
// });
|
||||||
|
// return shouldShow;
|
||||||
|
// }
|
||||||
|
|
||||||
searchBoxes: Array<{
|
searchBoxes: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
bbox: BoundingBox;
|
bbox: BoundingBox;
|
||||||
@@ -432,13 +471,44 @@ export default class HomeView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
|
logger.info("[HomeView] mounted() - component lifecycle started", {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
componentName: "HomeView",
|
||||||
|
});
|
||||||
|
|
||||||
await this.initializeIdentity();
|
await this.initializeIdentity();
|
||||||
// Settings already loaded in initializeIdentity()
|
// Settings already loaded in initializeIdentity()
|
||||||
await this.loadContacts();
|
await this.loadContacts();
|
||||||
// Registration check already handled in initializeIdentity()
|
// Registration check already handled in initializeIdentity()
|
||||||
await this.loadFeedData();
|
await this.loadFeedData();
|
||||||
|
|
||||||
|
logger.info("[HomeView] mounted() - about to call loadNewOffers()", {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
hasActiveDid: !!this.activeDid,
|
||||||
|
});
|
||||||
|
|
||||||
await this.loadNewOffers();
|
await this.loadNewOffers();
|
||||||
|
|
||||||
|
logger.info("[HomeView] mounted() - loadNewOffers() completed", {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
numNewOffersToUser: this.numNewOffersToUser,
|
||||||
|
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
|
||||||
|
shouldShowElement:
|
||||||
|
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
|
||||||
|
});
|
||||||
|
|
||||||
await this.checkOnboarding();
|
await this.checkOnboarding();
|
||||||
|
|
||||||
|
logger.info("[HomeView] mounted() - component lifecycle completed", {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
finalState: {
|
||||||
|
numNewOffersToUser: this.numNewOffersToUser,
|
||||||
|
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
|
||||||
|
shouldShowElement:
|
||||||
|
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
this.handleError(err);
|
this.handleError(err);
|
||||||
}
|
}
|
||||||
@@ -515,7 +585,18 @@ export default class HomeView extends Vue {
|
|||||||
// **CRITICAL**: Ensure correct API server for platform
|
// **CRITICAL**: Ensure correct API server for platform
|
||||||
await this.ensureCorrectApiServer();
|
await this.ensureCorrectApiServer();
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
// Get activeDid from active_identity table (single source of truth)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const activeIdentity = await (this as any).$getActiveIdentity();
|
||||||
|
this.activeDid = activeIdentity.activeDid || "";
|
||||||
|
logger.info("[HomeView] ActiveDid migration - using new API", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
source: "active_identity table",
|
||||||
|
hasActiveDid: !!this.activeDid,
|
||||||
|
activeIdentityResult: activeIdentity,
|
||||||
|
isRegistered: this.isRegistered,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
// Load contacts with graceful fallback
|
// Load contacts with graceful fallback
|
||||||
try {
|
try {
|
||||||
@@ -654,24 +735,100 @@ export default class HomeView extends Vue {
|
|||||||
* @requires Active DID
|
* @requires Active DID
|
||||||
*/
|
*/
|
||||||
private async loadNewOffers() {
|
private async loadNewOffers() {
|
||||||
if (this.activeDid) {
|
logger.info("[HomeView] loadNewOffers() called with activeDid:", {
|
||||||
const offersToUserData = await getNewOffersToUser(
|
activeDid: this.activeDid,
|
||||||
this.axios,
|
hasActiveDid: !!this.activeDid,
|
||||||
this.apiServer,
|
length: this.activeDid?.length || 0,
|
||||||
this.activeDid,
|
});
|
||||||
this.lastAckedOfferToUserJwtId,
|
|
||||||
);
|
|
||||||
this.numNewOffersToUser = offersToUserData.data.length;
|
|
||||||
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
|
|
||||||
|
|
||||||
const offersToUserProjects = await getNewOffersToUserProjects(
|
if (this.activeDid) {
|
||||||
this.axios,
|
logger.info("[HomeView] loadNewOffers() - activeDid found, calling API", {
|
||||||
this.apiServer,
|
activeDid: this.activeDid,
|
||||||
this.activeDid,
|
apiServer: this.apiServer,
|
||||||
this.lastAckedOfferToUserProjectsJwtId,
|
isRegistered: this.isRegistered,
|
||||||
);
|
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
|
||||||
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
|
});
|
||||||
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
|
|
||||||
|
try {
|
||||||
|
const offersToUserData = await getNewOffersToUser(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.activeDid,
|
||||||
|
this.lastAckedOfferToUserJwtId,
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
"[HomeView] loadNewOffers() - getNewOffersToUser successful",
|
||||||
|
{
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
dataLength: offersToUserData.data.length,
|
||||||
|
hitLimit: offersToUserData.hitLimit,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.numNewOffersToUser = offersToUserData.data.length;
|
||||||
|
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
|
||||||
|
|
||||||
|
logger.info("[HomeView] loadNewOffers() - updated component state", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
numNewOffersToUser: this.numNewOffersToUser,
|
||||||
|
newOffersToUserHitLimit: this.newOffersToUserHitLimit,
|
||||||
|
willRender: !!this.numNewOffersToUser,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const offersToUserProjects = await getNewOffersToUserProjects(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
this.activeDid,
|
||||||
|
this.lastAckedOfferToUserProjectsJwtId,
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
"[HomeView] loadNewOffers() - getNewOffersToUserProjects successful",
|
||||||
|
{
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
dataLength: offersToUserProjects.data.length,
|
||||||
|
hitLimit: offersToUserProjects.hitLimit,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
|
||||||
|
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
|
||||||
|
|
||||||
|
logger.info("[HomeView] loadNewOffers() - all API calls completed", {
|
||||||
|
numNewOffersToUser: this.numNewOffersToUser,
|
||||||
|
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
|
||||||
|
shouldRenderElement: !!this.numNewOffersToUser,
|
||||||
|
elementTestId: "newDirectOffersActivityNumber",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Additional logging for template rendering debugging
|
||||||
|
logger.info("[HomeView] loadNewOffers() - template rendering check", {
|
||||||
|
numNewOffersToUser: this.numNewOffersToUser,
|
||||||
|
numNewOffersToUserProjects: this.numNewOffersToUserProjects,
|
||||||
|
totalNewOffers:
|
||||||
|
this.numNewOffersToUser + this.numNewOffersToUserProjects,
|
||||||
|
shouldShowElement:
|
||||||
|
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
|
||||||
|
vIfCondition: `v-if="numNewOffersToUser + numNewOffersToUserProjects"`,
|
||||||
|
elementWillRender:
|
||||||
|
this.numNewOffersToUser + this.numNewOffersToUserProjects > 0,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[HomeView] loadNewOffers() - API call failed", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
apiServer: this.apiServer,
|
||||||
|
isRegistered: this.isRegistered,
|
||||||
|
error: errorStringForLog(error),
|
||||||
|
errorMessage: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("[HomeView] loadNewOffers() - no activeDid available", {
|
||||||
|
activeDid: this.activeDid,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user