You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
8.4 KiB
289 lines
8.4 KiB
/**
|
|
* 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);
|
|
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) {
|
|
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);
|
|
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
|
|
* @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 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) && (error as any).config) {
|
|
const config = (error as any).config as { url?: string };
|
|
return config.url;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Type guard for AxiosError
|
|
*/
|
|
private isAxiosError(error: unknown): error is AxiosError {
|
|
return error instanceof AxiosError;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory function to create a ProfileService instance
|
|
*/
|
|
export function createProfileService(
|
|
axios: AxiosInstance,
|
|
partnerApiServer: string,
|
|
): ProfileService {
|
|
return new ProfileService(axios, partnerApiServer);
|
|
}
|
|
|