import { ensureConfig, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient as getHttpClient } from '@edx/frontend-platform/auth'; import { logError } from '@edx/frontend-platform/logging'; import { camelCaseObject, convertKeyNames, snakeCaseObject } from '../utils'; import { FIELD_LABELS } from './constants'; ensureConfig(['LMS_BASE_URL'], 'Profile API service'); function processAccountData(data) { const processedData = camelCaseObject(data); return { ...processedData, socialLinks: Array.isArray(processedData.socialLinks) ? processedData.socialLinks : [], languageProficiencies: Array.isArray(processedData.languageProficiencies) ? processedData.languageProficiencies : [], name: processedData.name || null, bio: processedData.bio || null, country: processedData.country || null, levelOfEducation: processedData.levelOfEducation || null, profileImage: processedData.profileImage || {}, yearOfBirth: processedData.yearOfBirth || null, }; } function processAndThrowError(error, errorDataProcessor) { const processedError = Object.create(error); if (error.response && error.response.data && typeof error.response.data === 'object') { processedError.processedData = errorDataProcessor(error.response.data); throw processedError; } else { throw error; } } export async function getAccount(username) { const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`); return processAccountData(data); } export async function patchProfile(username, params) { const processedParams = snakeCaseObject(params); const { data } = await getHttpClient() .patch(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`, processedParams, { headers: { 'Content-Type': 'application/merge-patch+json', }, }) .catch((error) => { processAndThrowError(error, processAccountData); }); return processAccountData(data); } export async function postProfilePhoto(username, formData) { // eslint-disable-next-line no-unused-vars const { data } = await getHttpClient().post( `${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }, ).catch((error) => { processAndThrowError(error, camelCaseObject); }); // TODO: Someday in the future the POST photo endpoint // will return the new values. At that time we should // use the commented line below instead of the separate // getAccount request that follows. // return camelCaseObject(data); const updatedData = await getAccount(username); return updatedData.profileImage; } export async function deleteProfilePhoto(username) { // eslint-disable-next-line no-unused-vars const { data } = await getHttpClient().delete(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}/image`); // TODO: Someday in the future the POST photo endpoint // will return the new values. At that time we should // use the commented line below instead of the separate // getAccount request that follows. // return camelCaseObject(data); const updatedData = await getAccount(username); return updatedData.profileImage; } export async function getPreferences(username) { const { data } = await getHttpClient().get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`); return camelCaseObject(data); } export async function patchPreferences(username, params) { let processedParams = snakeCaseObject(params); processedParams = convertKeyNames(processedParams, { visibility_bio: 'visibility.bio', visibility_course_certificates: 'visibility.course_certificates', visibility_country: 'visibility.country', visibility_date_joined: 'visibility.date_joined', visibility_level_of_education: 'visibility.level_of_education', visibility_language_proficiencies: 'visibility.language_proficiencies', visibility_name: 'visibility.name', visibility_social_links: 'visibility.social_links', visibility_time_zone: 'visibility.time_zone', }); await getHttpClient().patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, { headers: { 'Content-Type': 'application/merge-patch+json' }, }); return params; // TODO: Once the server returns the updated preferences object, return that. } function transformCertificateData(data) { const transformedData = []; data.forEach((cert) => { // download_url may be full url or absolute path. // note: using the URL() api breaks in ie 11 const urlIsPath = typeof cert.download_url === 'string' && cert.download_url.search(/http[s]?:\/\//) !== 0; const downloadUrl = urlIsPath ? `${getConfig().LMS_BASE_URL}${cert.download_url}` : cert.download_url; transformedData.push({ ...camelCaseObject(cert), certificateType: cert.certificate_type, downloadUrl, }); }); return transformedData; } export async function getCourseCertificates(username) { const url = `${getConfig().LMS_BASE_URL}/api/certificates/v0/certificates/${username}/`; try { const { data } = await getHttpClient().get(url); return transformCertificateData(data); } catch (e) { logError(e); return []; } } function extractCountryList(data) { return data?.fields .find(({ name }) => name === FIELD_LABELS.COUNTRY) ?.options?.map(({ value }) => (value)) || []; } export async function getCountryList() { const url = `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`; try { const { data } = await getHttpClient().get(url); return extractCountryList(data); } catch (e) { logError(e); return []; } }