Browse Source
Removes over-engineered ProfileService and ServiceInitializationManager classes that were only used in one place. Inlines all profile logic directly into AccountViewView.vue to reduce complexity and improve maintainability. - Deletes ProfileService.ts (325 lines) - Deletes ServiceInitializationManager.ts (207 lines) - Inlines ProfileData interface and methods into AccountViewView - Maintains all existing functionality while reducing code footprint perf(logging): convert excessive info logs to debug level Reduces console noise by converting high-frequency, low-value logging from info to debug level across navigation, API calls, and component lifecycle operations. Improves performance and reduces log verbosity for normal application flow. - Router navigation guards: info → debug - Plan loading operations: info → debug - User registration checks: info → debug - Image server rate limits: info → debug - Component lifecycle events: info → debug - Settings loading operations: info → debug Maintains warn/error levels for actual issues while reducing noise from expected application behavior.pull/170/head
10 changed files with 259 additions and 587 deletions
@ -1,325 +0,0 @@ |
|||||
/** |
|
||||
* ProfileService - Handles user profile operations and API calls |
|
||||
* Extracted from AccountViewView.vue to improve separation of concerns |
|
||||
* |
|
||||
* @author Matthew Raymer |
|
||||
* @since 2025-08-25 |
|
||||
*/ |
|
||||
|
|
||||
import { AxiosInstance } from "axios"; |
|
||||
import { logger } from "../utils/logger"; |
|
||||
import { getServiceInitManager } from "./ServiceInitializationManager"; |
|
||||
import { |
|
||||
handleApiError, |
|
||||
createErrorContext, |
|
||||
createUserMessage, |
|
||||
} from "../utils/errorHandler"; |
|
||||
import { getHeaders } from "../libs/endorserServer"; |
|
||||
|
|
||||
/** |
|
||||
* Profile data structure |
|
||||
*/ |
|
||||
export interface ProfileData { |
|
||||
description: string; |
|
||||
latitude: number; |
|
||||
longitude: number; |
|
||||
includeLocation: boolean; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Profile service for managing user profile information |
|
||||
* |
|
||||
* @author Matthew Raymer |
|
||||
* @since 2025-08-25 |
|
||||
*/ |
|
||||
export class ProfileService { |
|
||||
private axios: AxiosInstance; |
|
||||
private partnerApiServer: string; |
|
||||
|
|
||||
constructor(axios: AxiosInstance, partnerApiServer: string) { |
|
||||
this.axios = axios; |
|
||||
this.partnerApiServer = partnerApiServer; |
|
||||
|
|
||||
// Register with service initialization manager
|
|
||||
const initManager = getServiceInitManager(); |
|
||||
initManager.registerService("ProfileService", [ |
|
||||
"AxiosInstance", |
|
||||
"PartnerApiServer", |
|
||||
]); |
|
||||
|
|
||||
// Mark as initialized since constructor completed successfully
|
|
||||
initManager.markInitialized("ProfileService"); |
|
||||
|
|
||||
logger.debug("[ProfileService] 🔧 Service initialized:", { |
|
||||
partnerApiServer, |
|
||||
hasAxios: !!axios, |
|
||||
timestamp: new Date().toISOString(), |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Load user profile from the partner API |
|
||||
* |
|
||||
* @param did - User's DID |
|
||||
* @returns Profile data or null if not found |
|
||||
* @throws Error if API call fails |
|
||||
*/ |
|
||||
async loadProfile(did: string): Promise<ProfileData | null> { |
|
||||
const operation = "Load Profile"; |
|
||||
const context = createErrorContext("ProfileService", operation, { |
|
||||
did, |
|
||||
partnerApiServer: this.partnerApiServer, |
|
||||
endpoint: `${this.partnerApiServer}/api/partner/userProfileForIssuer/${did}`, |
|
||||
}); |
|
||||
|
|
||||
try { |
|
||||
// Enhanced request tracking
|
|
||||
const requestId = `profile_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
|
||||
|
|
||||
logger.info("[ProfileService] 🔍 Loading profile:", { |
|
||||
requestId, |
|
||||
...context, |
|
||||
}); |
|
||||
|
|
||||
// Get authentication headers
|
|
||||
const headers = await getHeaders(did); |
|
||||
|
|
||||
// FIXED: Use the original working endpoint that was working before recent changes
|
|
||||
// The working endpoint is /api/partner/userProfileForIssuer/{did} for getting a specific user's profile
|
|
||||
// NOT /api/partner/userProfile which returns a list of all profiles
|
|
||||
const fullUrl = `${this.partnerApiServer}/api/partner/userProfileForIssuer/${did}`; |
|
||||
|
|
||||
logger.info("[ProfileService] 🔗 Making API request:", { |
|
||||
requestId, |
|
||||
did, |
|
||||
fullUrl, |
|
||||
partnerApiServer: this.partnerApiServer, |
|
||||
hasAuthHeader: !!headers.Authorization, |
|
||||
authHeaderLength: headers.Authorization?.length || 0, |
|
||||
}); |
|
||||
|
|
||||
const response = await this.axios.get(fullUrl, { headers }); |
|
||||
|
|
||||
logger.info("[ProfileService] ✅ Profile loaded successfully:", { |
|
||||
requestId, |
|
||||
...context, |
|
||||
status: response.status, |
|
||||
hasData: !!response.data, |
|
||||
dataKeys: response.data ? Object.keys(response.data) : [], |
|
||||
responseData: response.data, |
|
||||
responseDataType: typeof response.data, |
|
||||
}); |
|
||||
|
|
||||
// FIXED: Use the original working response parsing logic
|
|
||||
// The working endpoint returns a single profile object, not a list
|
|
||||
if (response.data && response.data.data) { |
|
||||
const profileData = response.data.data; |
|
||||
logger.info("[ProfileService] 🔍 Parsing profile data:", { |
|
||||
requestId, |
|
||||
profileData, |
|
||||
profileDataKeys: Object.keys(profileData), |
|
||||
locLat: profileData.locLat, |
|
||||
locLon: profileData.locLon, |
|
||||
description: profileData.description, |
|
||||
issuerDid: profileData.issuerDid, |
|
||||
hasLocationFields: !!(profileData.locLat || profileData.locLon), |
|
||||
}); |
|
||||
|
|
||||
const result = { |
|
||||
description: profileData.description || "", |
|
||||
latitude: profileData.locLat || 0, |
|
||||
longitude: profileData.locLon || 0, |
|
||||
includeLocation: !!(profileData.locLat && profileData.locLon), |
|
||||
}; |
|
||||
|
|
||||
logger.info("[ProfileService] 📊 Parsed profile result:", { |
|
||||
requestId, |
|
||||
result, |
|
||||
hasLocation: result.includeLocation, |
|
||||
locationValues: { |
|
||||
original: { locLat: profileData.locLat, locLon: profileData.locLon }, |
|
||||
parsed: { latitude: result.latitude, longitude: result.longitude }, |
|
||||
}, |
|
||||
}); |
|
||||
|
|
||||
return result; |
|
||||
} else { |
|
||||
logger.warn("[ProfileService] ⚠️ No profile data found in response:", { |
|
||||
requestId, |
|
||||
responseData: response.data, |
|
||||
hasData: !!response.data, |
|
||||
hasDataData: !!(response.data && response.data.data), |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
} catch (error: unknown) { |
|
||||
// Use standardized error handling
|
|
||||
const errorInfo = handleApiError(error, context, operation); |
|
||||
|
|
||||
// Handle specific HTTP status codes
|
|
||||
if (errorInfo.errorType === "AxiosError" && errorInfo.status === 404) { |
|
||||
logger.info( |
|
||||
"[ProfileService] ℹ️ Profile not found (404) - this is normal for new users", |
|
||||
); |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
// Create user-friendly error message
|
|
||||
const userMessage = createUserMessage( |
|
||||
errorInfo, |
|
||||
"Failed to load profile", |
|
||||
); |
|
||||
throw new Error(userMessage); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Save user profile to the partner API |
|
||||
* |
|
||||
* @param did - User's DID |
|
||||
* @param profileData - Profile data to save |
|
||||
* @returns Success status |
|
||||
* @throws Error if API call fails |
|
||||
*/ |
|
||||
async saveProfile(did: string, profileData: ProfileData): Promise<boolean> { |
|
||||
const operation = "Save Profile"; |
|
||||
const context = createErrorContext("ProfileService", operation, { |
|
||||
did, |
|
||||
partnerApiServer: this.partnerApiServer, |
|
||||
endpoint: `${this.partnerApiServer}/api/partner/userProfile`, |
|
||||
profileData, |
|
||||
}); |
|
||||
|
|
||||
try { |
|
||||
// Enhanced request tracking
|
|
||||
const requestId = `profile_save_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
|
||||
|
|
||||
logger.info("[ProfileService] 💾 Saving profile:", { |
|
||||
requestId, |
|
||||
...context, |
|
||||
}); |
|
||||
|
|
||||
// Get authentication headers
|
|
||||
const headers = await getHeaders(did); |
|
||||
|
|
||||
// Prepare payload in the format expected by the partner API
|
|
||||
const payload = { |
|
||||
description: profileData.description, |
|
||||
issuerDid: did, |
|
||||
...(profileData.includeLocation && |
|
||||
profileData.latitude && |
|
||||
profileData.longitude |
|
||||
? { |
|
||||
locLat: profileData.latitude, |
|
||||
locLon: profileData.longitude, |
|
||||
} |
|
||||
: {}), |
|
||||
}; |
|
||||
|
|
||||
logger.info("[ProfileService] 📤 Sending payload to server:", { |
|
||||
requestId, |
|
||||
payload, |
|
||||
hasLocation: profileData.includeLocation, |
|
||||
latitude: profileData.latitude, |
|
||||
longitude: profileData.longitude, |
|
||||
payloadKeys: Object.keys(payload), |
|
||||
}); |
|
||||
|
|
||||
const response = await this.axios.post( |
|
||||
`${this.partnerApiServer}/api/partner/userProfile`, |
|
||||
payload, |
|
||||
{ headers }, |
|
||||
); |
|
||||
|
|
||||
logger.info("[ProfileService] ✅ Profile saved successfully:", { |
|
||||
requestId, |
|
||||
...context, |
|
||||
status: response.status, |
|
||||
hasData: !!response.data, |
|
||||
responseData: response.data, |
|
||||
responseDataKeys: response.data ? Object.keys(response.data) : [], |
|
||||
}); |
|
||||
|
|
||||
return true; |
|
||||
} catch (error: unknown) { |
|
||||
// Use standardized error handling
|
|
||||
const errorInfo = handleApiError(error, context, operation); |
|
||||
|
|
||||
// Create user-friendly error message
|
|
||||
const userMessage = createUserMessage( |
|
||||
errorInfo, |
|
||||
"Failed to save profile", |
|
||||
); |
|
||||
throw new Error(userMessage); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Toggle profile location visibility |
|
||||
* |
|
||||
* @param profileData - Current profile data |
|
||||
* @returns Updated profile data |
|
||||
*/ |
|
||||
toggleProfileLocation(profileData: ProfileData): ProfileData { |
|
||||
const includeLocation = !profileData.includeLocation; |
|
||||
return { |
|
||||
...profileData, |
|
||||
latitude: includeLocation ? profileData.latitude : 0, |
|
||||
longitude: includeLocation ? profileData.longitude : 0, |
|
||||
includeLocation, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Clear profile location |
|
||||
* |
|
||||
* @param profileData - Current profile data |
|
||||
* @returns Updated profile data |
|
||||
*/ |
|
||||
clearProfileLocation(profileData: ProfileData): ProfileData { |
|
||||
return { |
|
||||
...profileData, |
|
||||
latitude: 0, |
|
||||
longitude: 0, |
|
||||
includeLocation: false, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Reset profile to default state |
|
||||
* |
|
||||
* @returns Default profile data |
|
||||
*/ |
|
||||
getDefaultProfile(): ProfileData { |
|
||||
return { |
|
||||
description: "", |
|
||||
latitude: 0, |
|
||||
longitude: 0, |
|
||||
includeLocation: false, |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Factory function to create a ProfileService instance |
|
||||
* |
|
||||
* @param axios - Axios instance for HTTP requests |
|
||||
* @param partnerApiServer - Partner API server URL |
|
||||
* @returns ProfileService instance |
|
||||
*/ |
|
||||
export function createProfileService( |
|
||||
axios: AxiosInstance, |
|
||||
partnerApiServer: string, |
|
||||
): ProfileService { |
|
||||
// Register dependencies with service initialization manager
|
|
||||
const initManager = getServiceInitManager(); |
|
||||
initManager.registerService("AxiosInstance", []); |
|
||||
initManager.registerService("PartnerApiServer", []); |
|
||||
|
|
||||
// Mark dependencies as initialized
|
|
||||
initManager.markInitialized("AxiosInstance"); |
|
||||
initManager.markInitialized("PartnerApiServer"); |
|
||||
|
|
||||
return new ProfileService(axios, partnerApiServer); |
|
||||
} |
|
@ -1,207 +0,0 @@ |
|||||
/** |
|
||||
* Service Initialization Manager |
|
||||
* |
|
||||
* Manages the proper initialization order of services to prevent race conditions |
|
||||
* and ensure dependencies are available when services are created. |
|
||||
* |
|
||||
* @author Matthew Raymer |
|
||||
* @since 2025-08-25 |
|
||||
*/ |
|
||||
|
|
||||
import { logger } from "../utils/logger"; |
|
||||
|
|
||||
/** |
|
||||
* Service initialization status tracking |
|
||||
*/ |
|
||||
interface ServiceStatus { |
|
||||
name: string; |
|
||||
initialized: boolean; |
|
||||
dependencies: string[]; |
|
||||
error?: string; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Service initialization manager to prevent race conditions |
|
||||
*/ |
|
||||
export class ServiceInitializationManager { |
|
||||
private static instance: ServiceInitializationManager; |
|
||||
private serviceStatuses = new Map<string, ServiceStatus>(); |
|
||||
private initializationPromise: Promise<void> | null = null; |
|
||||
|
|
||||
private constructor() {} |
|
||||
|
|
||||
/** |
|
||||
* Get singleton instance |
|
||||
*/ |
|
||||
static getInstance(): ServiceInitializationManager { |
|
||||
if (!ServiceInitializationManager.instance) { |
|
||||
ServiceInitializationManager.instance = |
|
||||
new ServiceInitializationManager(); |
|
||||
} |
|
||||
return ServiceInitializationManager.instance; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Register a service that needs initialization |
|
||||
*/ |
|
||||
registerService(name: string, dependencies: string[] = []): void { |
|
||||
this.serviceStatuses.set(name, { |
|
||||
name, |
|
||||
initialized: false, |
|
||||
dependencies, |
|
||||
}); |
|
||||
|
|
||||
logger.debug("[ServiceInit] 🔧 Service registered:", { |
|
||||
name, |
|
||||
dependencies, |
|
||||
totalServices: this.serviceStatuses.size, |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Mark a service as initialized |
|
||||
*/ |
|
||||
markInitialized(name: string): void { |
|
||||
const status = this.serviceStatuses.get(name); |
|
||||
if (status) { |
|
||||
status.initialized = true; |
|
||||
logger.debug("[ServiceInit] ✅ Service initialized:", { |
|
||||
name, |
|
||||
totalInitialized: this.getInitializedCount(), |
|
||||
totalServices: this.serviceStatuses.size, |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Mark a service as failed |
|
||||
*/ |
|
||||
markFailed(name: string, error: string): void { |
|
||||
const status = this.serviceStatuses.get(name); |
|
||||
if (status) { |
|
||||
status.error = error; |
|
||||
logger.error("[ServiceInit] ❌ Service failed:", { |
|
||||
name, |
|
||||
error, |
|
||||
totalFailed: this.getFailedCount(), |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Get count of initialized services |
|
||||
*/ |
|
||||
private getInitializedCount(): number { |
|
||||
return Array.from(this.serviceStatuses.values()).filter( |
|
||||
(s) => s.initialized, |
|
||||
).length; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Get count of failed services |
|
||||
*/ |
|
||||
private getFailedCount(): number { |
|
||||
return Array.from(this.serviceStatuses.values()).filter((s) => s.error) |
|
||||
.length; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Wait for all services to be initialized |
|
||||
*/ |
|
||||
async waitForInitialization(): Promise<void> { |
|
||||
if (this.initializationPromise) { |
|
||||
return this.initializationPromise; |
|
||||
} |
|
||||
|
|
||||
this.initializationPromise = new Promise((resolve, reject) => { |
|
||||
const checkInterval = setInterval(() => { |
|
||||
const totalServices = this.serviceStatuses.size; |
|
||||
const initializedCount = this.getInitializedCount(); |
|
||||
const failedCount = this.getFailedCount(); |
|
||||
|
|
||||
logger.debug("[ServiceInit] 🔍 Initialization progress:", { |
|
||||
totalServices, |
|
||||
initializedCount, |
|
||||
failedCount, |
|
||||
remaining: totalServices - initializedCount - failedCount, |
|
||||
}); |
|
||||
|
|
||||
if (failedCount > 0) { |
|
||||
clearInterval(checkInterval); |
|
||||
const failedServices = Array.from(this.serviceStatuses.values()) |
|
||||
.filter((s) => s.error) |
|
||||
.map((s) => `${s.name}: ${s.error}`); |
|
||||
|
|
||||
const error = new Error( |
|
||||
`Service initialization failed: ${failedServices.join(", ")}`, |
|
||||
); |
|
||||
logger.error("[ServiceInit] ❌ Initialization failed:", error); |
|
||||
reject(error); |
|
||||
} else if (initializedCount === totalServices) { |
|
||||
clearInterval(checkInterval); |
|
||||
logger.info( |
|
||||
"[ServiceInit] 🎉 All services initialized successfully:", |
|
||||
{ |
|
||||
totalServices, |
|
||||
initializedCount, |
|
||||
}, |
|
||||
); |
|
||||
resolve(); |
|
||||
} |
|
||||
}, 100); |
|
||||
|
|
||||
// Timeout after 30 seconds
|
|
||||
setTimeout(() => { |
|
||||
clearInterval(checkInterval); |
|
||||
const error = new Error( |
|
||||
"Service initialization timeout after 30 seconds", |
|
||||
); |
|
||||
logger.error("[ServiceInit] ⏰ Initialization timeout:", error); |
|
||||
reject(error); |
|
||||
}, 30000); |
|
||||
}); |
|
||||
|
|
||||
return this.initializationPromise; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Get initialization status summary |
|
||||
*/ |
|
||||
getStatusSummary(): { |
|
||||
total: number; |
|
||||
initialized: number; |
|
||||
failed: number; |
|
||||
pending: number; |
|
||||
services: ServiceStatus[]; |
|
||||
} { |
|
||||
const services = Array.from(this.serviceStatuses.values()); |
|
||||
const total = services.length; |
|
||||
const initialized = services.filter((s) => s.initialized).length; |
|
||||
const failed = services.filter((s) => s.error).length; |
|
||||
const pending = total - initialized - failed; |
|
||||
|
|
||||
return { |
|
||||
total, |
|
||||
initialized, |
|
||||
failed, |
|
||||
pending, |
|
||||
services, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Reset the manager (useful for testing) |
|
||||
*/ |
|
||||
reset(): void { |
|
||||
this.serviceStatuses.clear(); |
|
||||
this.initializationPromise = null; |
|
||||
logger.debug("[ServiceInit] 🔄 Manager reset"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Convenience function to get the service initialization manager |
|
||||
*/ |
|
||||
export const getServiceInitManager = (): ServiceInitializationManager => { |
|
||||
return ServiceInitializationManager.getInstance(); |
|
||||
}; |
|
Loading…
Reference in new issue