From 5806c3047da430eabc070e454b568c0c93beb0b0 Mon Sep 17 00:00:00 2001 From: Shaun Hamilton Date: Tue, 17 Aug 2021 18:31:25 +0100 Subject: [PATCH] fix(client): convert challengeFiles->files before sending to api (#43204) * fix(client): convert challengeFiles->files before sending to api * update use of user.completeChallenges * parse response in ajax, pre-typing * add typing to getSessionUser * refactor: use Omit * fix: reorganise getSessionUser * refactor ajax for simplicity * refactor to be worse * allow for undefined completedChallenges Co-authored-by: Oliver Eyton-Williams Co-authored-by: Oliver Eyton-Williams --- api-server/src/server/boot/challenge.js | 8 +- .../Challenges/redux/completion-epic.js | 5 +- client/src/utils/ajax.ts | 95 +++++++++++++++++-- 3 files changed, 94 insertions(+), 14 deletions(-) diff --git a/api-server/src/server/boot/challenge.js b/api-server/src/server/boot/challenge.js index dd9b771db5..5cfea2df55 100644 --- a/api-server/src/server/boot/challenge.js +++ b/api-server/src/server/boot/challenge.js @@ -83,11 +83,9 @@ export function buildUserUpdate( if (jsProjects.includes(challengeId)) { completedChallenge = { ..._completedChallenge, - files: Object.keys(files) - .map(key => files[key]) - .map(file => - pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext']) - ) + files: files.map(file => + pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext']) + ) }; } else { completedChallenge = omit(_completedChallenge, ['files']); diff --git a/client/src/templates/Challenges/redux/completion-epic.js b/client/src/templates/Challenges/redux/completion-epic.js index f1f62870c4..2be69ddef7 100644 --- a/client/src/templates/Challenges/redux/completion-epic.js +++ b/client/src/templates/Challenges/redux/completion-epic.js @@ -67,7 +67,10 @@ function submitModern(type, state) { const { username } = userSelector(state); const challengeInfo = { id, - challengeFiles + files: challengeFiles.reduce( + (acc, { fileKey, ...curr }) => [...acc, { ...curr, key: fileKey }], + [] + ) }; const update = { endpoint: '/modern-challenge-completed', diff --git a/client/src/utils/ajax.ts b/client/src/utils/ajax.ts index b12cebdc72..26c91b34da 100644 --- a/client/src/utils/ajax.ts +++ b/client/src/utils/ajax.ts @@ -1,7 +1,11 @@ import cookies from 'browser-cookies'; import envData from '../../../config/env.json'; -import type { UserType } from '../redux/prop-types'; +import type { + ChallengeFile, + CompletedChallenge, + UserType +} from '../redux/prop-types'; const { apiLocation } = envData; @@ -51,16 +55,91 @@ async function request( /** GET **/ interface SessionUser { - user: UserType; + user?: { [username: string]: UserType }; sessionMeta: { activeDonations: number }; - result: string; -} -export function getSessionUser(): Promise { - return get('/user/get-session-user'); } -export function getUserProfile(username: string): Promise { - return get(`/api/users/get-public-profile?username=${username}`); +type challengeFilesForFiles = { + files: Array & { key: string }>; +} & Omit; + +type ApiSessionResponse = Omit; +type ApiUser = { + user: { + [username: string]: ApiUserType; + }; + result?: string; +}; + +type ApiUserType = Omit & { + completedChallenges?: challengeFilesForFiles[]; +}; + +type UserResponseType = { + user: { [username: string]: UserType } | Record; + result: string | undefined; +}; + +function parseApiResponseToClientUser(data: ApiUser): UserResponseType { + const userData = data.user?.[data?.result ?? '']; + let completedChallenges: CompletedChallenge[] = []; + if (userData) { + completedChallenges = + userData.completedChallenges?.reduce( + (acc: CompletedChallenge[], curr: challengeFilesForFiles) => { + return [ + ...acc, + { + ...curr, + challengeFiles: curr.files.map(({ key: fileKey, ...file }) => ({ + ...file, + fileKey + })) + } + ]; + }, + [] + ) ?? []; + } + return { + user: { [data.result ?? '']: { ...userData, completedChallenges } }, + result: data.result + }; +} + +export function getSessionUser(): Promise { + const response: Promise = get( + '/user/get-session-user' + ); + // TODO: Once DB is migrated, no longer need to parse `files` -> `challengeFiles` etc. + return response.then(data => { + const { result, user } = parseApiResponseToClientUser(data); + return { + sessionMeta: data.sessionMeta, + result, + user + }; + }); +} + +type UserProfileResponse = { + entities: Omit; + result: string | undefined; +}; +export function getUserProfile(username: string): Promise { + const response: Promise<{ entities?: ApiUser; result?: string }> = get( + `/api/users/get-public-profile?username=${username}` + ); + return response.then(data => { + const { result, user } = parseApiResponseToClientUser({ + user: data.entities?.user ?? {}, + result: data.result + }); + return { + entities: { user }, + result + }; + }); } interface Cert {