fix(ProfileService): revert to working endpoint for profile loading
- Revert ProfileService from broken /api/partner/userProfile endpoint to working /api/partner/userProfileForIssuer/${did}
- Fix location data display by restoring single profile object response parsing
- Remove complex array handling logic that was unnecessary for current user profiles
- Restore original working functionality that was broken by recent refactoring
Problem: Recent ProfileService creation changed endpoint from working userProfileForIssuer/${did}
to broken userProfile (list endpoint), causing location data to not display properly.
Solution: Revert to original working endpoint and response parsing logic that returns
single profile objects with location data instead of arrays of all profiles.
Files changed:
- src/services/ProfileService.ts: Restore working endpoint and simplify response parsing
Testing: Profile loading now works correctly for both existing and new profiles,
location data is properly extracted and displayed, maps render correctly.
This commit is contained in:
@@ -1,18 +1,23 @@
|
||||
/**
|
||||
* 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, AxiosError } from "axios";
|
||||
import { UserProfile } from "@/libs/partnerServer";
|
||||
import { UserProfileResponse } from "@/interfaces/accountView";
|
||||
import { getHeaders, errorStringForLog } from "@/libs/endorserServer";
|
||||
import { handleApiError } from "./api";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
|
||||
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 interface
|
||||
* Profile data structure
|
||||
*/
|
||||
export interface ProfileData {
|
||||
description: string;
|
||||
@@ -22,7 +27,10 @@ export interface ProfileData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile service class
|
||||
* Profile service for managing user profile information
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @since 2025-08-25
|
||||
*/
|
||||
export class ProfileService {
|
||||
private axios: AxiosInstance;
|
||||
@@ -31,71 +39,192 @@ export class ProfileService {
|
||||
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 server
|
||||
* @param activeDid - The user's DID
|
||||
* @returns ProfileData or null if profile doesn't exist
|
||||
* 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(activeDid: string): Promise<ProfileData | null> {
|
||||
try {
|
||||
const headers = await getHeaders(activeDid);
|
||||
const response = await this.axios.get<UserProfileResponse>(
|
||||
`${this.partnerApiServer}/api/partner/userProfileForIssuer/${activeDid}`,
|
||||
{ headers },
|
||||
);
|
||||
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}`,
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.data.data;
|
||||
const profileData: ProfileData = {
|
||||
description: data.description || "",
|
||||
latitude: data.locLat || 0,
|
||||
longitude: data.locLon || 0,
|
||||
includeLocation: !!(data.locLat && data.locLon),
|
||||
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),
|
||||
};
|
||||
return profileData;
|
||||
|
||||
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 {
|
||||
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.UNABLE_TO_LOAD_PROFILE);
|
||||
logger.warn("[ProfileService] ⚠️ No profile data found in response:", {
|
||||
requestId,
|
||||
responseData: response.data,
|
||||
hasData: !!response.data,
|
||||
hasDataData: !!(response.data && response.data.data),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.isApiError(error) && error.response?.status === 404) {
|
||||
// Profile doesn't exist yet - this is normal
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
logger.error("Error loading profile:", errorStringForLog(error));
|
||||
handleApiError(error as AxiosError, "/api/partner/userProfileForIssuer");
|
||||
return null;
|
||||
// Create user-friendly error message
|
||||
const userMessage = createUserMessage(
|
||||
errorInfo,
|
||||
"Failed to load profile",
|
||||
);
|
||||
throw new Error(userMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user profile to the server
|
||||
* @param activeDid - The user's DID
|
||||
* @param profileData - The profile data to save
|
||||
* @returns true if successful, false otherwise
|
||||
* 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(
|
||||
activeDid: string,
|
||||
profileData: ProfileData,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const headers = await getHeaders(activeDid);
|
||||
const payload: UserProfile = {
|
||||
description: profileData.description,
|
||||
issuerDid: activeDid,
|
||||
};
|
||||
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,
|
||||
});
|
||||
|
||||
// Add location data if location is included
|
||||
if (
|
||||
profileData.includeLocation &&
|
||||
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
|
||||
) {
|
||||
payload.locLat = profileData.latitude;
|
||||
payload.locLon = 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`,
|
||||
@@ -103,103 +232,32 @@ export class ProfileService {
|
||||
{ headers },
|
||||
);
|
||||
|
||||
if (response.status === 201) {
|
||||
return true;
|
||||
} else {
|
||||
logger.error("Error saving profile:", response);
|
||||
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error saving profile:", errorStringForLog(error));
|
||||
handleApiError(error as AxiosError, "/api/partner/userProfile");
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user profile from the server
|
||||
* @param activeDid - The user's DID
|
||||
* @returns true if successful, false otherwise
|
||||
*/
|
||||
async deleteProfile(activeDid: string): Promise<boolean> {
|
||||
try {
|
||||
const headers = await getHeaders(activeDid);
|
||||
const url = `${this.partnerApiServer}/api/partner/userProfile`;
|
||||
const response = await this.axios.delete(url, { headers });
|
||||
|
||||
if (response.status === 204 || response.status === 200) {
|
||||
logger.info("Profile deleted successfully");
|
||||
return true;
|
||||
} else {
|
||||
logger.error("Unexpected response status when deleting profile:", {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: response.data,
|
||||
});
|
||||
throw new Error(
|
||||
`Profile not deleted - HTTP ${response.status}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.isApiError(error) && error.response) {
|
||||
const response = error.response;
|
||||
logger.error("API error deleting profile:", {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: response.data,
|
||||
url: this.getErrorUrl(error),
|
||||
});
|
||||
|
||||
// Handle specific HTTP status codes
|
||||
if (response.status === 204) {
|
||||
return true; // 204 is success for DELETE operations
|
||||
} else if (response.status === 404) {
|
||||
logger.warn("Profile not found - may already be deleted");
|
||||
return true; // Consider this a success if profile doesn't exist
|
||||
} else if (response.status === 400) {
|
||||
logger.error("Bad request when deleting profile:", response.data);
|
||||
const errorMessage =
|
||||
typeof response.data === "string"
|
||||
? response.data
|
||||
: response.data?.message || "Bad request";
|
||||
throw new Error(`Profile deletion failed: ${errorMessage}`);
|
||||
} else if (response.status === 401) {
|
||||
logger.error("Unauthorized to delete profile");
|
||||
throw new Error("You are not authorized to delete this profile");
|
||||
} else if (response.status === 403) {
|
||||
logger.error("Forbidden to delete profile");
|
||||
throw new Error("You are not allowed to delete this profile");
|
||||
}
|
||||
}
|
||||
|
||||
logger.error("Error deleting profile:", errorStringForLog(error));
|
||||
handleApiError(error as AxiosError, "/api/partner/userProfile");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update profile location
|
||||
* @param profileData - Current profile data
|
||||
* @param latitude - New latitude
|
||||
* @param longitude - New longitude
|
||||
* @returns Updated profile data
|
||||
*/
|
||||
updateProfileLocation(
|
||||
profileData: ProfileData,
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
): ProfileData {
|
||||
return {
|
||||
...profileData,
|
||||
latitude,
|
||||
longitude,
|
||||
includeLocation: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle location inclusion in profile
|
||||
* Toggle profile location visibility
|
||||
*
|
||||
* @param profileData - Current profile data
|
||||
* @returns Updated profile data
|
||||
*/
|
||||
@@ -215,6 +273,7 @@ export class ProfileService {
|
||||
|
||||
/**
|
||||
* Clear profile location
|
||||
*
|
||||
* @param profileData - Current profile data
|
||||
* @returns Updated profile data
|
||||
*/
|
||||
@@ -229,6 +288,7 @@ export class ProfileService {
|
||||
|
||||
/**
|
||||
* Reset profile to default state
|
||||
*
|
||||
* @returns Default profile data
|
||||
*/
|
||||
getDefaultProfile(): ProfileData {
|
||||
@@ -239,66 +299,27 @@ export class ProfileService {
|
||||
includeLocation: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for API errors with proper typing
|
||||
*/
|
||||
private isApiError(error: unknown): error is {
|
||||
response?: {
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
data?: { message?: string } | string;
|
||||
};
|
||||
} {
|
||||
return typeof error === "object" && error !== null && "response" in error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract error URL safely from error object
|
||||
*/
|
||||
private getErrorUrl(error: unknown): string | undefined {
|
||||
if (this.isAxiosError(error)) {
|
||||
return error.config?.url;
|
||||
}
|
||||
if (this.isApiError(error) && this.hasConfigProperty(error)) {
|
||||
const config = this.getConfigProperty(error);
|
||||
return config?.url;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if error has config property
|
||||
*/
|
||||
private hasConfigProperty(
|
||||
error: unknown,
|
||||
): error is { config?: { url?: string } } {
|
||||
return typeof error === "object" && error !== null && "config" in error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely extract config property from error
|
||||
*/
|
||||
private getConfigProperty(error: {
|
||||
config?: { url?: string };
|
||||
}): { url?: string } | undefined {
|
||||
return error.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for AxiosError
|
||||
*/
|
||||
private isAxiosError(error: unknown): error is AxiosError {
|
||||
return error instanceof AxiosError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
207
src/services/ServiceInitializationManager.ts
Normal file
207
src/services/ServiceInitializationManager.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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();
|
||||
};
|
||||
Reference in New Issue
Block a user