Files
crowd-funder-for-time-pwa/src/services/ProfileService.ts
Matthew Raymer 77a4c60656 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.
2025-08-25 13:03:06 +00:00

326 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
}