forked from trent_larson/crowd-funder-for-time-pwa
- Fix Leaflet icon initialization error causing "Cannot read properties of undefined (reading 'Default')" - Add proper Leaflet icon configuration with CDN fallbacks - Implement map ready state management to prevent infinite loading - Add comprehensive error handling and debugging for map lifecycle events - Fix profile deletion treating HTTP 204 (No Content) as error instead of success - Enhance error logging and user feedback throughout profile operations - Add fallback timeout mechanisms for map initialization failures - Improve error messages to show specific API failure reasons Resolves map rendering issues and profile deletion failures by properly handling HTTP status codes and Leaflet component initialization.
263 lines
7.9 KiB
TypeScript
263 lines
7.9 KiB
TypeScript
/**
|
|
* ProfileService - Handles user profile operations and API calls
|
|
* Extracted from AccountViewView.vue to improve separation of concerns
|
|
*/
|
|
|
|
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";
|
|
|
|
/**
|
|
* Profile data interface
|
|
*/
|
|
export interface ProfileData {
|
|
description: string;
|
|
latitude: number;
|
|
longitude: number;
|
|
includeLocation: boolean;
|
|
}
|
|
|
|
/**
|
|
* Profile service class
|
|
*/
|
|
export class ProfileService {
|
|
private axios: AxiosInstance;
|
|
private partnerApiServer: string;
|
|
|
|
constructor(axios: AxiosInstance, partnerApiServer: string) {
|
|
this.axios = axios;
|
|
this.partnerApiServer = partnerApiServer;
|
|
}
|
|
|
|
/**
|
|
* Load user profile from the server
|
|
* @param activeDid - The user's DID
|
|
* @returns ProfileData or null if profile doesn't exist
|
|
*/
|
|
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 },
|
|
);
|
|
|
|
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),
|
|
};
|
|
return profileData;
|
|
} else {
|
|
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.UNABLE_TO_LOAD_PROFILE);
|
|
}
|
|
} catch (error) {
|
|
if (this.isApiError(error) && error.response?.status === 404) {
|
|
// Profile doesn't exist yet - this is normal
|
|
return null;
|
|
}
|
|
|
|
logger.error("Error loading profile:", errorStringForLog(error));
|
|
handleApiError(error as AxiosError, "/api/partner/userProfileForIssuer");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
async saveProfile(
|
|
activeDid: string,
|
|
profileData: ProfileData,
|
|
): Promise<boolean> {
|
|
try {
|
|
const headers = await getHeaders(activeDid);
|
|
const payload: UserProfile = {
|
|
description: profileData.description,
|
|
issuerDid: activeDid,
|
|
};
|
|
|
|
// Add location data if location is included
|
|
if (
|
|
profileData.includeLocation &&
|
|
profileData.latitude &&
|
|
profileData.longitude
|
|
) {
|
|
payload.locLat = profileData.latitude;
|
|
payload.locLon = profileData.longitude;
|
|
}
|
|
|
|
const response = await this.axios.post(
|
|
`${this.partnerApiServer}/api/partner/userProfile`,
|
|
payload,
|
|
{ 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
logger.debug("Attempting to delete profile for DID:", activeDid);
|
|
logger.debug("Using partner API server:", this.partnerApiServer);
|
|
logger.debug("Request headers:", headers);
|
|
|
|
const url = `${this.partnerApiServer}/api/partner/userProfile`;
|
|
logger.debug("DELETE request URL:", url);
|
|
|
|
const response = await this.axios.delete(url, { headers });
|
|
|
|
if (response.status === 200 || response.status === 204) {
|
|
logger.debug("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 as any; // Type assertion for error response
|
|
logger.error("API error deleting profile:", {
|
|
status: response.status,
|
|
statusText: response.statusText,
|
|
data: response.data,
|
|
url: (error as any).config?.url
|
|
});
|
|
|
|
// Handle specific HTTP status codes
|
|
if (response.status === 204) {
|
|
logger.debug("Profile deleted successfully (204 No Content)");
|
|
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);
|
|
throw new Error(`Profile deletion failed: ${response.data?.message || 'Bad request'}`);
|
|
} 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
|
|
* @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,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Type guard for API errors
|
|
*/
|
|
private isApiError(
|
|
error: unknown,
|
|
): error is { response?: { status?: number } } {
|
|
return typeof error === "object" && error !== null && "response" in error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory function to create a ProfileService instance
|
|
*/
|
|
export function createProfileService(
|
|
axios: AxiosInstance,
|
|
partnerApiServer: string,
|
|
): ProfileService {
|
|
return new ProfileService(axios, partnerApiServer);
|
|
}
|