From a3b799962770b7eebf7e780875f66a2c2885f69a Mon Sep 17 00:00:00 2001 From: Adam Butterworth Date: Thu, 21 Feb 2019 11:55:00 -0500 Subject: [PATCH] Break out utils functions and move preferences api calls to profileapiservice --- src/data/apiClient.js | 75 ------------------------------- src/sagas/RootSaga.js | 5 +-- src/services/ProfileApiService.js | 47 +++++++++++++++++-- src/services/utils.js | 28 ++++++++++++ src/services/utils.test.js | 47 +++++++++++++++++++ 5 files changed, 121 insertions(+), 81 deletions(-) create mode 100644 src/services/utils.js create mode 100644 src/services/utils.test.js diff --git a/src/data/apiClient.js b/src/data/apiClient.js index cc7c2df..4af0b6a 100644 --- a/src/data/apiClient.js +++ b/src/data/apiClient.js @@ -1,9 +1,7 @@ -import _ from 'lodash'; import { getAuthenticatedAPIClient } from '@edx/frontend-auth'; import { configuration } from '../config'; -const lmsBaseUrl = process.env.LMS_BASE_URL; const apiClient = getAuthenticatedAPIClient({ appBaseUrl: configuration.BASE_URL, @@ -16,78 +14,5 @@ const apiClient = getAuthenticatedAPIClient({ csrfCookieName: configuration.CSRF_COOKIE_NAME, }); -const clientServerKeyMap = { - bio: 'bio', - socialLinks: 'social_links', - country: 'country', - education: 'level_of_education', - fullName: 'name', - username: 'username', - profileImage: 'profile_image', - dateJoined: 'date_joined', - languageProficiencies: 'language_proficiencies', - accountPrivacy: 'account_privacy', -}; - -const serverClientKeyMap = _.invert(clientServerKeyMap); - - -export function getPreferences(username) { - const url = `${lmsBaseUrl}/api/user/v1/preferences/${username}`; - - return new Promise((resolve, reject) => { - apiClient.get(url) - .then(({ data }) => { - // Unflatten server response - // visibility.social_links: 'value' becomes { visibility: { socialLinks: 'value' }} - const preferences = {}; - Object.entries(data).forEach(([key, value]) => { - _.set( - preferences, - key.split('.').map(pathKey => serverClientKeyMap[pathKey] || pathKey), - value, - ); - }); - resolve(preferences); - }) - .catch((error) => { - reject(error); - }); - }); -} - -export function savePreferences(username, preferences) { - const url = `${lmsBaseUrl}/api/user/v1/preferences/${username}`; - - // Flatten object for server - // { visibility: { socialLinks: 'value' }} becomes visibility.social_links: 'value' - const data = {}; - const flattenAndTransformKeys = (prevKeys, currentValue) => { - if (typeof currentValue !== 'object') { - const serverKey = prevKeys.map(key => serverClientKeyMap[key] || key).join('.'); - data[serverKey] = currentValue; - return; - } - Object.keys(currentValue).forEach((key) => { - flattenAndTransformKeys(prevKeys.concat(key), currentValue[key]); - }); - }; - flattenAndTransformKeys([], preferences); - - return new Promise((resolve, reject) => { - apiClient.patch( - url, - data, - { headers: { 'Content-Type': 'application/merge-patch+json' } }, - ) - .then((response) => { - resolve(response.data); - }) - .catch((error) => { - reject(error); - }); - }); -} - export default apiClient; diff --git a/src/sagas/RootSaga.js b/src/sagas/RootSaga.js index 50d2307..f39875b 100644 --- a/src/sagas/RootSaga.js +++ b/src/sagas/RootSaga.js @@ -39,7 +39,6 @@ import { } from '../actions/preferences'; import * as ProfileApiService from '../services/ProfileApiService'; -import { getPreferences, savePreferences } from '../data/apiClient'; const PROP_TO_STATE_MAP = { @@ -139,7 +138,7 @@ export function* handleFetchPreferences(action) { const { username } = action.payload; try { yield put(fetchPreferencesBegin()); - const userPreferences = yield call(getPreferences, username); + const userPreferences = yield call(ProfileApiService.getPreferences, username); yield put(fetchPreferencesSuccess(userPreferences)); yield put(fetchPreferencesReset()); } catch (e) { @@ -151,7 +150,7 @@ export function* handleSavePreferences(action) { const { username, preferences } = action.payload; try { yield put(savePreferencesBegin()); - yield call(savePreferences, username, preferences); + yield call(ProfileApiService.savePreferences, username, preferences); yield put(savePreferencesSuccess(preferences)); yield put(savePreferencesReset()); } catch (e) { diff --git a/src/services/ProfileApiService.js b/src/services/ProfileApiService.js index 4aeeb13..296d94d 100644 --- a/src/services/ProfileApiService.js +++ b/src/services/ProfileApiService.js @@ -1,11 +1,27 @@ import camelcaseKeys from 'camelcase-keys'; import snakecaseKeys from 'snakecase-keys'; +import _ from 'lodash'; import apiClient from '../data/apiClient'; import { configuration } from '../config'; +import { unflattenAndTransformKeys, flattenAndTransformKeys } from './utils'; const accountsApiBaseUrl = `${configuration.LMS_BASE_URL}/api/user/v1/accounts`; const preferencesApiBaseUrl = `${configuration.LMS_BASE_URL}/api/user/v1/preferences`; +const clientServerKeyMap = { + bio: 'bio', + socialLinks: 'social_links', + country: 'country', + education: 'level_of_education', + fullName: 'name', + username: 'username', + profileImage: 'profile_image', + dateJoined: 'date_joined', + languageProficiencies: 'language_proficiencies', + accountPrivacy: 'account_privacy', +}; +const serverClientKeyMap = _.invert(clientServerKeyMap); + export function getProfile(username) { return new Promise((resolve, reject) => { @@ -72,9 +88,35 @@ export function deleteProfilePhoto(username) { return apiClient.delete(`${accountsApiBaseUrl}/${username}/image`); } -export function getUserPreference(username, preferenceKey) { +export function getPreferences(username) { + const url = `${preferencesApiBaseUrl}/${username}`; + return new Promise((resolve, reject) => { - apiClient.get(`${preferencesApiBaseUrl}/${username}/${preferenceKey}`) + apiClient.get(url) + .then(({ data }) => { + // Unflatten server response + // visibility.social_links: 'value' becomes { visibility: { socialLinks: 'value' }} + resolve(unflattenAndTransformKeys(data, key => serverClientKeyMap[key] || key)); + }) + .catch((error) => { + reject(error); + }); + }); +} + +export function savePreferences(username, preferences) { + const url = `${preferencesApiBaseUrl}/${username}`; + + // Flatten object for server + // { visibility: { socialLinks: 'value' }} becomes visibility.social_links: 'value' + const data = flattenAndTransformKeys(preferences, key => clientServerKeyMap[key] || key); + + return new Promise((resolve, reject) => { + apiClient.patch( + url, + data, + { headers: { 'Content-Type': 'application/merge-patch+json' } }, + ) .then((response) => { resolve(response.data); }) @@ -83,4 +125,3 @@ export function getUserPreference(username, preferenceKey) { }); }); } - diff --git a/src/services/utils.js b/src/services/utils.js new file mode 100644 index 0000000..a8acbf1 --- /dev/null +++ b/src/services/utils.js @@ -0,0 +1,28 @@ +import _ from 'lodash'; + +export function unflattenAndTransformKeys(obj, transformer) { + const newObj = {}; + + Object.entries(obj).forEach(([key, value]) => { + _.set(newObj, key.split('.').map(transformer), value); + }); + + return newObj; +} + +export function flattenAndTransformKeys(srcObj, transformer = key => key) { + const flatten = (obj, prevKeys = []) => (Object.entries(obj).reduce((acc, [key, value]) => { + const tKey = transformer(key); + const keys = prevKeys.concat(tKey); + + if (value && typeof value === 'object') { + Object.assign(acc, flatten(value, keys)); + } else { + acc[keys.join('.')] = value; + } + + return acc; + }, {})); + + return flatten(srcObj); +} diff --git a/src/services/utils.test.js b/src/services/utils.test.js new file mode 100644 index 0000000..4463e61 --- /dev/null +++ b/src/services/utils.test.js @@ -0,0 +1,47 @@ +import { flattenAndTransformKeys, unflattenAndTransformKeys } from './utils'; + + +describe('unflattenAndTransformKeys', () => { + it('should unflatten objects and transform keys', () => { + const sourceObject = { + userlocation: 'US', + 'visibility.sociallinks': 'private', + 'visibility.education': 'private', + 'visibility.bio': 'private', + }; + + const result = unflattenAndTransformKeys(sourceObject, key => key.toUpperCase()); + + expect(result).toEqual({ + USERLOCATION: 'US', + VISIBILITY: { + SOCIALLINKS: 'private', + EDUCATION: 'private', + BIO: 'private', + }, + }); + }); +}); + + +describe('flattenAndTransformKeys', () => { + it('should flatten objects and transform keys', () => { + const sourceObject = { + USERLOCATION: 'US', + VISIBILITY: { + SOCIALLINKS: 'private', + EDUCATION: 'private', + BIO: 'private', + }, + }; + + const result = flattenAndTransformKeys(sourceObject, key => key.toLowerCase()); + + expect(result).toEqual({ + userlocation: 'US', + 'visibility.sociallinks': 'private', + 'visibility.education': 'private', + 'visibility.bio': 'private', + }); + }); +});