Skip to content

feat: use v5 member api to save traits and skills #332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 10, 2022
Merged
2 changes: 2 additions & 0 deletions __tests__/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
@@ -174,7 +174,9 @@ Object {
"updatePasswordDone": [Function],
"updatePasswordInit": [Function],
"updateProfileDone": [Function],
"updateProfileDoneV5": [Function],
"updateProfileInit": [Function],
"updateProfileInitV5": [Function],
"uploadPhotoDone": [Function],
"uploadPhotoInit": [Function],
"verifyMemberNewEmailDone": [Function],
2 changes: 2 additions & 0 deletions __tests__/actions/__snapshots__/profile.js.snap
Original file line number Diff line number Diff line change
@@ -44,7 +44,9 @@ Object {
"updatePasswordDone": [Function],
"updatePasswordInit": [Function],
"updateProfileDone": [Function],
"updateProfileDoneV5": [Function],
"updateProfileInit": [Function],
"updateProfileInitV5": [Function],
"uploadPhotoDone": [Function],
"uploadPhotoInit": [Function],
"verifyMemberNewEmailDone": [Function],
4 changes: 2 additions & 2 deletions __tests__/actions/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const MOCK_GROUPS_REQ_URL = 'https://api.topcoder-dev.com/v5/groups?memberId=12345&membershipType=user';
const MOCK_PROFILE_REQ_URL = 'https://api.topcoder-dev.com/v3/members/username12345';
const MOCK_PROFILE_REQ_URL = 'https://api.topcoder-dev.com/v5/members/username12345';

jest.mock('isomorphic-fetch', () => jest.fn(url => Promise.resolve({
ok: true,
@@ -10,7 +10,7 @@ jest.mock('isomorphic-fetch', () => jest.fn(url => Promise.resolve({
content = ['Group1', 'Group2'];
break;
case MOCK_PROFILE_REQ_URL:
content = { result: { content: { userId: 12345 }, status: 200 } };
content = Promise.resolve({ userId: 12345 });
break;
default: throw new Error('Unexpected URL!');
}
4 changes: 0 additions & 4 deletions __tests__/actions/profile.js
Original file line number Diff line number Diff line change
@@ -18,8 +18,6 @@ const linkedAccounts = [{

// Mock services
const mockMembersService = {
getPresignedUrl: jest.fn().mockReturnValue(Promise.resolve()),
uploadFileToS3: jest.fn().mockReturnValue(Promise.resolve()),
updateMemberPhoto: jest.fn().mockReturnValue(Promise.resolve('url-of-photo')),
updateMemberProfile: jest.fn().mockReturnValue(Promise.resolve(profile)),
addSkill: jest.fn().mockReturnValue(Promise.resolve({ skills: [skill] })),
@@ -47,8 +45,6 @@ test('Module exports', () => expect(actions).toMatchSnapshot());
test('profile.uploadPhotoDone', async () => {
const actionResult = await redux.resolveAction(actions.profile.uploadPhotoDone(handle, tokenV3));
expect(actionResult).toMatchSnapshot();
expect(mockMembersService.getPresignedUrl).toBeCalled();
expect(mockMembersService.uploadFileToS3).toBeCalled();
expect(mockMembersService.updateMemberPhoto).toBeCalled();
});

40 changes: 6 additions & 34 deletions docs/services.members.md
Original file line number Diff line number Diff line change
@@ -25,9 +25,7 @@ members via API V3.
* [.addSkill(handle, skillTagId)](#module_services.members..MembersService+addSkill) ⇒ <code>Promise</code>
* [.hideSkill(handle, skillTagId)](#module_services.members..MembersService+hideSkill) ⇒ <code>Promise</code>
* [.updateMemberProfile(profile)](#module_services.members..MembersService+updateMemberProfile) ⇒ <code>Promise</code>
* [.getPresignedUrl(userHandle, file)](#module_services.members..MembersService+getPresignedUrl) ⇒ <code>Promise</code>
* [.updateMemberPhoto(S3Response)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
* [.uploadFileToS3(presignedUrlResponse)](#module_services.members..MembersService+uploadFileToS3) ⇒ <code>Promise</code>
* [.updateMemberPhoto(userHandle, file)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
* [.verifyMemberNewEmail(handle, emailVerifyToken)](#module_services.members..MembersService+verifyMemberNewEmail) ⇒ <code>Promise</code>

<a name="module_services.members.getService"></a>
@@ -65,9 +63,7 @@ Service class.
* [.addSkill(handle, skillTagId)](#module_services.members..MembersService+addSkill) ⇒ <code>Promise</code>
* [.hideSkill(handle, skillTagId)](#module_services.members..MembersService+hideSkill) ⇒ <code>Promise</code>
* [.updateMemberProfile(profile)](#module_services.members..MembersService+updateMemberProfile) ⇒ <code>Promise</code>
* [.getPresignedUrl(userHandle, file)](#module_services.members..MembersService+getPresignedUrl) ⇒ <code>Promise</code>
* [.updateMemberPhoto(S3Response)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
* [.uploadFileToS3(presignedUrlResponse)](#module_services.members..MembersService+uploadFileToS3) ⇒ <code>Promise</code>
* [.updateMemberPhoto(userHandle, file)](#module_services.members..MembersService+updateMemberPhoto) ⇒ <code>Promise</code>
* [.verifyMemberNewEmail(handle, emailVerifyToken)](#module_services.members..MembersService+verifyMemberNewEmail) ⇒ <code>Promise</code>

<a name="new_module_services.members..MembersService_new"></a>
@@ -256,42 +252,18 @@ Updates member profile.
| --- | --- | --- |
| profile | <code>Object</code> | The profile to update. |

<a name="module_services.members..MembersService+getPresignedUrl"></a>

#### membersService.getPresignedUrl(userHandle, file) ⇒ <code>Promise</code>
Gets presigned url for member photo file.

**Kind**: instance method of [<code>MembersService</code>](#module_services.members..MembersService)
**Returns**: <code>Promise</code> - Resolves to the api response content

| Param | Type | Description |
| --- | --- | --- |
| userHandle | <code>String</code> | The user handle |
| file | <code>File</code> | The file to get its presigned url |

<a name="module_services.members..MembersService+updateMemberPhoto"></a>

#### membersService.updateMemberPhoto(S3Response) ⇒ <code>Promise</code>
Updates member photo.
#### membersService.updateMemberPhoto(userHandle, file) ⇒ <code>Promise</code>
Uploads and updates member photo.

**Kind**: instance method of [<code>MembersService</code>](#module_services.members..MembersService)
**Returns**: <code>Promise</code> - Resolves to the api response content

| Param | Type | Description |
| --- | --- | --- |
| S3Response | <code>Object</code> | The response from uploadFileToS3() function. |

<a name="module_services.members..MembersService+uploadFileToS3"></a>

#### membersService.uploadFileToS3(presignedUrlResponse) ⇒ <code>Promise</code>
Uploads file to S3.

**Kind**: instance method of [<code>MembersService</code>](#module_services.members..MembersService)
**Returns**: <code>Promise</code> - Resolves to the api response content

| Param | Type | Description |
| --- | --- | --- |
| presignedUrlResponse | <code>Object</code> | The presigned url response from getPresignedUrl() function. |
| userHandle | <code>String</code> | The user handle |
| file | <code>File</code> | The file to be uploaded |

<a name="module_services.members..MembersService+verifyMemberNewEmail"></a>

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@
"lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .",
"test": "npm run lint && npm run jest"
},
"version": "1.2.4",
"version": "1.2.5",
"dependencies": {
"auth0-js": "^6.8.4",
"config": "^3.2.0",
8 changes: 4 additions & 4 deletions src/actions/auth.js
Original file line number Diff line number Diff line change
@@ -5,9 +5,10 @@

import { createActions } from 'redux-actions';
import { decodeToken } from '@topcoder-platform/tc-auth-lib';
import { getApiV3, getApiV5 } from '../services/api';
import { getApiV5 } from '../services/api';
import { setErrorIcon, ERROR_ICON_TYPES } from '../utils/errors';
import { getService } from '../services/groups';
import { handleApiResponse } from '../utils/tc';

/**
* Helper method that checks for HTTP error response v5 and throws Error in this case.
@@ -41,11 +42,10 @@ async function checkErrorV5(res) {
function loadProfileDone(userTokenV3) {
if (!userTokenV3) return Promise.resolve(null);
const user = decodeToken(userTokenV3);
const apiV3 = getApiV3(userTokenV3);
const apiV5 = getApiV5(userTokenV3);
return Promise.all([
apiV3.get(`/members/${user.handle}`)
.then(res => res.json()).then(res => (res.result.status === 200 ? res.result.content : {})),
apiV5.get(`/members/${user.handle}`)
.then(handleApiResponse),
apiV5.get(`/groups?memberId=${user.userId}&membershipType=user`)
.then(checkErrorV5).then(res => res.result || []),
]).then(([profile, groups]) => ({ ...profile, groups }));
25 changes: 22 additions & 3 deletions src/actions/profile.js
Original file line number Diff line number Diff line change
@@ -216,9 +216,7 @@ function uploadPhotoInit() {}
*/
function uploadPhotoDone(handle, tokenV3, file) {
const service = getMembersService(tokenV3);
return service.getPresignedUrl(handle, file)
.then(res => service.uploadFileToS3(res))
.then(res => service.updateMemberPhoto(res))
return service.updateMemberPhoto(handle, file)
.then(photoURL => ({ handle, photoURL }));
}

@@ -248,6 +246,25 @@ function updateProfileDone(profile, tokenV3) {
return service.updateMemberProfile(profile);
}

/**
* @static
* @desc Creates an action that signals beginning of updating user's profile.
* @return {Action}
*/
function updateProfileInitV5() {}

/**
* @static
* @desc Creates an action that updates user's profile.
* @param {String} profile Topcoder user profile.
* @param {String} tokenV5 Topcoder auth token v5.
* @return {Action}
*/
function updateProfileDoneV5(profile, handle, tokenV3) {
const service = getMembersService(tokenV3);
return service.updateMemberProfileV5(profile, handle);
}

/**
* @static
* @desc Creates an action that signals beginning of adding user's skill.
@@ -485,6 +502,8 @@ export default createActions({
DELETE_PHOTO_DONE: updateProfileDone,
UPDATE_PROFILE_INIT: updateProfileInit,
UPDATE_PROFILE_DONE: updateProfileDone,
UPDATE_PROFILE_INIT_V5: updateProfileInitV5,
UPDATE_PROFILE_DONE_V5: updateProfileDoneV5,
ADD_SKILL_INIT: addSkillInit,
ADD_SKILL_DONE: addSkillDone,
HIDE_SKILL_INIT: hideSkillInit,
15 changes: 15 additions & 0 deletions src/reducers/auth.js
Original file line number Diff line number Diff line change
@@ -106,6 +106,21 @@ function create(initialState) {
},
};
},
[profileActions.profile.updateProfileDoneV5]: (state, { payload, error }) => {
if (error) {
return state;
}
if (!state.profile || state.profile.handle !== payload.handle) {
return state;
}
return {
...state,
profile: {
...state.profile,
...payload,
},
};
},
}, _.defaults(initialState, {
authenticating: true,
profile: null,
45 changes: 45 additions & 0 deletions src/reducers/profile.js
Original file line number Diff line number Diff line change
@@ -266,6 +266,49 @@ function onUpdateProfileDone(state, { payload, error }) {
};
}

/**
* Handles PROFILE/UPDATE_PROFILE_DONE_V5 action.
* @param {Object} state
* @param {Object} action Payload will be JSON from api call
* @return {Object} New state
*/
function onUpdateProfileDoneV5(state, { payload, error }) {
const newState = { ...state, updatingProfile: false };

if (payload.isEmailConflict) {
return {
...newState,
isEmailConflict: true,
updateProfileSuccess: false,
};
}

if (error) {
logger.error('Failed to update user profile', payload);
fireErrorMessage('ERROR: Failed to update user profile!');
return {
...newState,
updateProfileSuccess: false,
};
}

if (!newState.info || newState.info.handle !== payload.handle) {
return {
...newState,
updateProfileSuccess: true,
};
}

return {
...newState,
info: {
...newState.info,
...payload,
},
updateProfileSuccess: true,
};
}

/**
* Handles PROFILE/ADD_SKILL_DONE action.
* @param {Object} state
@@ -530,6 +573,8 @@ function create(initialState) {
[a.deletePhotoDone]: onDeletePhotoDone,
[a.updateProfileInit]: state => ({ ...state, updatingProfile: true }),
[a.updateProfileDone]: onUpdateProfileDone,
[a.updateProfileInitV5]: state => ({ ...state, updatingProfile: true }),
[a.updateProfileDoneV5]: onUpdateProfileDoneV5,
[a.addSkillInit]: state => ({ ...state, addingSkill: true }),
[a.addSkillDone]: onAddSkillDone,
[a.hideSkillInit]: state => ({ ...state, hidingSkill: true }),
Loading