forked from trent_larson/crowd-funder-for-time-pwa
- 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.
326 lines
9.6 KiB
TypeScript
326 lines
9.6 KiB
TypeScript
/**
|
||
* 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);
|
||
}
|