/** * 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 { try { const headers = await getHeaders(activeDid); const response = await this.axios.get( `${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 { 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 === 200) { return true; } else { 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 { try { const headers = await getHeaders(activeDid); const response = await this.axios.delete( `${this.partnerApiServer}/api/partner/userProfile`, { headers }, ); if (response.status === 200) { return true; } else { throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_DELETED); } } catch (error) { 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); }