diff --git a/src/hooks/mention.test.ts b/src/hooks/mention.test.ts index a5d8644a..916cdb9f 100644 --- a/src/hooks/mention.test.ts +++ b/src/hooks/mention.test.ts @@ -18,7 +18,7 @@ describe('Chat Mention Hooks', () => { }); describe('useMentions', () => { - const currentMemberRoute = `/${buildGetCurrentMemberRoute()}`; + const currentMemberRoute = `/api/${buildGetCurrentMemberRoute()}`; const route = `/${buildGetMemberMentionsRoute()}`; const key = buildMentionKey(); diff --git a/src/member/api.ts b/src/member/api.ts index fad728ce..27f584bc 100644 --- a/src/member/api.ts +++ b/src/member/api.ts @@ -11,7 +11,8 @@ import { UUID, } from '@graasp/sdk'; -import { AxiosProgressEvent } from 'axios'; +import axios from 'axios'; +import type { AxiosProgressEvent } from 'axios'; import { StatusCodes } from 'http-status-codes'; import { verifyAuthentication } from '../api/axios.js'; @@ -25,7 +26,6 @@ import { buildGetMemberRoute, buildGetMemberStorageFilesRoute, buildGetMemberStorageRoute, - buildGetMembersByEmailRoute, buildGetMembersByIdRoute, buildPatchCurrentMemberRoute, buildPatchMemberPasswordRoute, @@ -34,37 +34,18 @@ import { buildUploadAvatarRoute, } from './routes.js'; -export const getMembersByEmail = async ( - { emails }: { emails: string[] }, - { API_HOST, axios }: PartialQueryConfigForApi, -) => - axios - .get>(`${API_HOST}/${buildGetMembersByEmailRoute(emails)}`) - .then(({ data }) => data); - -export const getMember = async ( - { id }: { id: UUID }, - { API_HOST, axios }: PartialQueryConfigForApi, -) => - axios - .get(`${API_HOST}/${buildGetMemberRoute(id)}`) - .then(({ data }) => data); +export const getMember = async ({ id }: { id: UUID }) => + axios.get(`/api/${buildGetMemberRoute(id)}`).then(({ data }) => data); -export const getMembers = ( - { ids }: { ids: UUID[] }, - { API_HOST, axios }: PartialQueryConfigForApi, -) => +export const getMembers = ({ ids }: { ids: UUID[] }) => axios - .get>(`${API_HOST}/${buildGetMembersByIdRoute(ids)}`) + .get>(`/api/${buildGetMembersByIdRoute(ids)}`) .then(({ data }) => data); -export const getCurrentMember = async ({ - API_HOST, - axios, -}: PartialQueryConfigForApi) => +export const getCurrentMember = async () => verifyAuthentication(() => axios - .get(`${API_HOST}/${buildGetCurrentMemberRoute()}`) + .get(`/api/${buildGetCurrentMemberRoute()}`) .then(({ data }) => data) .catch((error) => { if (error.response) { @@ -78,24 +59,18 @@ export const getCurrentMember = async ({ }), ); -export const getMemberStorage = async ({ - API_HOST, - axios, -}: PartialQueryConfigForApi) => +export const getMemberStorage = async () => verifyAuthentication(() => axios - .get(`${API_HOST}/${buildGetMemberStorageRoute()}`) + .get(`/api/${buildGetMemberStorageRoute()}`) .then(({ data }) => data), ); -export const getMemberStorageFiles = async ( - pagination: Partial, - { API_HOST, axios }: PartialQueryConfigForApi, -) => +export const getMemberStorageFiles = async (pagination: Partial) => axios .get< Paginated - >(`${API_HOST}/${buildGetMemberStorageFilesRoute(pagination)}`) + >(`/api/${buildGetMemberStorageFilesRoute(pagination)}`) .then(({ data }) => data); export const editCurrentMember = async ( @@ -108,7 +83,7 @@ export const editCurrentMember = async ( name?: string; enableSaveActions?: boolean; }, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => { const url = new URL(buildPatchCurrentMemberRoute(), API_HOST); const body: Partial< @@ -135,7 +110,6 @@ export const editCurrentMember = async ( export const deleteCurrentMember = async ({ API_HOST, - axios, }: PartialQueryConfigForApi) => verifyAuthentication(() => axios @@ -145,7 +119,7 @@ export const deleteCurrentMember = async ({ export const createPassword = async ( payload: { password: Password }, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => axios .post(`${API_HOST}/${buildPostMemberPasswordRoute()}`, payload) @@ -153,7 +127,7 @@ export const createPassword = async ( export const updatePassword = async ( payload: { password: Password; currentPassword: Password }, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => axios .patch(`${API_HOST}/${buildPatchMemberPasswordRoute()}`, payload) @@ -164,7 +138,7 @@ export const uploadAvatar = async ( file: Blob; onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; }, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => { const { file } = args; const itemPayload = new FormData(); @@ -186,7 +160,7 @@ export const uploadAvatar = async ( export const downloadAvatar = async ( { id, size = DEFAULT_THUMBNAIL_SIZE }: { id: UUID; size?: string }, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => axios .get( @@ -197,19 +171,22 @@ export const downloadAvatar = async ( ) .then(({ data }) => data); -export const downloadAvatarUrl = async ( - { id, size = DEFAULT_THUMBNAIL_SIZE }: { id: UUID; size?: string }, - { API_HOST, axios }: PartialQueryConfigForApi, -) => +export const downloadAvatarUrl = async ({ + id, + size = DEFAULT_THUMBNAIL_SIZE, +}: { + id: UUID; + size?: string; +}) => axios .get( - `${API_HOST}/${buildDownloadAvatarRoute({ id, size, replyUrl: true })}`, + `/api/${buildDownloadAvatarRoute({ id, size, replyUrl: true })}`, ) .then(({ data }) => data); export const updateEmail = async ( email: string, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => axios.post(`${API_HOST}/${buildPostMemberEmailUpdateRoute()}`, { email, @@ -217,7 +194,7 @@ export const updateEmail = async ( export const validateEmailUpdate = async ( token: string, - { API_HOST, axios }: PartialQueryConfigForApi, + { API_HOST }: PartialQueryConfigForApi, ) => axios.patch( `${API_HOST}/${buildPostMemberEmailUpdateRoute()}`, @@ -229,7 +206,6 @@ export const validateEmailUpdate = async ( // Define the function to export member data export const exportMemberData = async ({ API_HOST, - axios, }: PartialQueryConfigForApi) => axios .post(`${API_HOST}/${buildExportMemberDataRoute()}`) diff --git a/src/member/hooks.test.ts b/src/member/hooks.test.ts index 418c0421..391b20f4 100644 --- a/src/member/hooks.test.ts +++ b/src/member/hooks.test.ts @@ -15,7 +15,6 @@ import nock from 'nock'; import { afterEach, describe, expect, it } from 'vitest'; import { - AVATAR_BLOB_RESPONSE, AVATAR_URL_RESPONSE, FILE_NOT_FOUND_RESPONSE, UNAUTHORIZED_RESPONSE, @@ -42,7 +41,7 @@ describe('Member Hooks', () => { }); describe('useCurrentMember', () => { - const route = `/${buildGetCurrentMemberRoute()}`; + const route = `/api/${buildGetCurrentMemberRoute()}`; const hook = () => hooks.useCurrentMember(); it(`Receive current member`, async () => { @@ -85,7 +84,7 @@ describe('Member Hooks', () => { it(`Receive member id = ${id}`, async () => { const endpoints = [ { - route: `/${buildGetMemberRoute(id)}`, + route: `/api/${buildGetMemberRoute(id)}`, response, }, ]; @@ -107,7 +106,7 @@ describe('Member Hooks', () => { const hook = () => hooks.useMember(id); const endpoints = [ { - route: `/${buildGetMemberRoute(id)}`, + route: `/api/${buildGetMemberRoute(id)}`, response: UNAUTHORIZED_RESPONSE, statusCode: StatusCodes.UNAUTHORIZED, }, @@ -135,7 +134,7 @@ describe('Member Hooks', () => { const emptyIds: UUID[] = []; const endpoints = [ { - route: `/${buildGetMembersByIdRoute(emptyIds)}`, + route: `/api/${buildGetMembersByIdRoute(emptyIds)}`, response, }, ]; @@ -158,7 +157,7 @@ describe('Member Hooks', () => { const oneMemberIds = [m.id]; const endpoints = [ { - route: `/${buildGetMembersByIdRoute(oneMemberIds)}`, + route: `/api/${buildGetMembersByIdRoute(oneMemberIds)}`, response: oneMemberResponse, }, ]; @@ -182,7 +181,7 @@ describe('Member Hooks', () => { const endpointResponse = buildResultOfData(twoMembers); const endpoints = [ { - route: `/${buildGetMembersByIdRoute(twoIds)}`, + route: `/api/${buildGetMembersByIdRoute(twoIds)}`, response: endpointResponse, }, ]; @@ -204,7 +203,7 @@ describe('Member Hooks', () => { const endpoints = splitEndpointByIds( ids, MAX_TARGETS_FOR_READ_REQUEST, - (chunk) => `/${buildGetMembersByIdRoute(chunk)}`, + (chunk) => `/api/${buildGetMembersByIdRoute(chunk)}`, response, ); const fullResponse = buildResultOfData(response); @@ -227,7 +226,7 @@ describe('Member Hooks', () => { const hook = () => hooks.useMembers(ids); const endpoints = [ { - route: `/${buildGetMembersByIdRoute(ids)}`, + route: `/api/${buildGetMembersByIdRoute(ids)}`, response: UNAUTHORIZED_RESPONSE, statusCode: StatusCodes.UNAUTHORIZED, }, @@ -252,116 +251,11 @@ describe('Member Hooks', () => { // TODO: errors }); - describe('useAvatar', () => { - const account = AccountFactory(); - const replyUrl = false; - const response = AVATAR_BLOB_RESPONSE; - const route = `/${buildDownloadAvatarRoute({ id: account.id, replyUrl })}`; - const hook = () => hooks.useAvatar({ id: account.id }); - const key = memberKeys.single(account.id).avatar({ replyUrl }); - - it(`Receive default avatar`, async () => { - const endpoints = [ - { route, response, headers: { 'Content-Type': 'image/jpeg' } }, - ]; - const { data } = await mockHook({ endpoints, hook, wrapper }); - - expect(data).toBeTruthy(); - // verify cache keys - expect(queryClient.getQueryData(key)).toBeTruthy(); - }); - - it(`Receive large avatar`, async () => { - const size = ThumbnailSize.Large; - const routeLarge = `/${buildDownloadAvatarRoute({ - id: account.id, - replyUrl, - size, - })}`; - const hookLarge = () => hooks.useAvatar({ id: account.id, size }); - const keyLarge = memberKeys.single(account.id).avatar({ size, replyUrl }); - - const endpoints = [ - { - route: routeLarge, - response, - headers: { 'Content-Type': 'image/jpeg' }, - }, - ]; - const { data } = await mockHook({ - endpoints, - hook: hookLarge, - wrapper, - }); - - expect(data).toBeTruthy(); - // verify cache keys - expect(queryClient.getQueryData(keyLarge)).toBeTruthy(); - }); - - it(`Undefined id does not fetch`, async () => { - const endpoints = [ - { - route, - response, - }, - ]; - const { data, isFetched } = await mockHook({ - endpoints, - hook: () => hooks.useAvatar({ id: undefined }), - wrapper, - enabled: false, - }); - - expect(data).toBeFalsy(); - expect(isFetched).toBeFalsy(); - // verify cache keys - expect(queryClient.getQueryData(key)).toBeFalsy(); - }); - - it(`Error fetching avatar`, async () => { - const endpoints = [ - { - route, - response: FILE_NOT_FOUND_RESPONSE, - statusCode: StatusCodes.NOT_FOUND, - }, - ]; - const { data, isFetched, isError } = await mockHook({ - endpoints, - hook: () => hooks.useAvatar({ id: account.id }), - wrapper, - }); - - expect(data).toBeFalsy(); - expect(isFetched).toBeTruthy(); - expect(isError).toBeTruthy(); - // verify cache keys - expect(queryClient.getQueryData(key)).toBeFalsy(); - }); - - it(`Unauthorized`, async () => { - const endpoints = [ - { - route, - response: UNAUTHORIZED_RESPONSE, - statusCode: StatusCodes.UNAUTHORIZED, - }, - ]; - const { data, isError } = await mockHook({ endpoints, hook, wrapper }); - - expect(data).toBeFalsy(); - expect(isError).toBeTruthy(); - // verify cache keys - expect(queryClient.getQueryData(key)).toBeFalsy(); - }); - }); - describe('useAvatarUrl', () => { const member = AccountFactory(); const replyUrl = true; const response = AVATAR_URL_RESPONSE; - const route = `/${buildDownloadAvatarRoute({ id: member.id, replyUrl })}`; + const route = `/api/${buildDownloadAvatarRoute({ id: member.id, replyUrl })}`; const hook = () => hooks.useAvatarUrl({ id: member.id }); const key = memberKeys.single(member.id).avatar({ replyUrl }); @@ -375,7 +269,7 @@ describe('Member Hooks', () => { it(`Receive large avatar url`, async () => { const size = ThumbnailSize.Large; - const routeLarge = `/${buildDownloadAvatarRoute({ + const routeLarge = `/api/${buildDownloadAvatarRoute({ id: member.id, replyUrl, size, @@ -409,7 +303,7 @@ describe('Member Hooks', () => { ]; const { data, isFetched } = await mockHook({ endpoints, - hook: () => hooks.useAvatar({ id: undefined }), + hook: () => hooks.useAvatarUrl({ id: undefined }), wrapper, enabled: false, }); @@ -430,7 +324,7 @@ describe('Member Hooks', () => { ]; const { data, isFetched, isError } = await mockHook({ endpoints, - hook: () => hooks.useAvatar({ id: member.id }), + hook: () => hooks.useAvatarUrl({ id: member.id }), wrapper, }); @@ -460,7 +354,7 @@ describe('Member Hooks', () => { describe('useMemberStorage', () => { const response: MemberStorage = { current: 123, maximum: 123 }; - const route = `/${buildGetMemberStorageRoute()}`; + const route = `/api/${buildGetMemberStorageRoute()}`; const hook = () => hooks.useMemberStorage(); const key = memberKeys.current().storage; @@ -515,7 +409,7 @@ describe('Member Hooks', () => { page: 1, pageSize: 10, }; - const route = `/${buildGetMemberStorageFilesRoute(mockPagination)}`; + const route = `/api/${buildGetMemberStorageFilesRoute(mockPagination)}`; const hook = () => hooks.useMemberStorageFiles(mockPagination); const key = memberKeys.current().storageFiles(mockPagination); diff --git a/src/member/hooks.ts b/src/member/hooks.ts index fb2b3646..2a3e5215 100644 --- a/src/member/hooks.ts +++ b/src/member/hooks.ts @@ -26,7 +26,7 @@ export default (queryConfig: QueryClientConfig) => { useCurrentMember: () => useQuery({ queryKey: memberKeys.current().content, - queryFn: () => Api.getCurrentMember(queryConfig), + queryFn: () => Api.getCurrentMember(), ...defaultQueryOptions, }), @@ -37,7 +37,7 @@ export default (queryConfig: QueryClientConfig) => { if (!id) { throw new UndefinedArgument(); } - return Api.getMember({ id }, queryConfig); + return Api.getMember({ id }); }, enabled: Boolean(id), ...defaultQueryOptions, @@ -50,7 +50,7 @@ export default (queryConfig: QueryClientConfig) => { splitRequestByIdsAndReturn( ids, MAX_TARGETS_FOR_READ_REQUEST, - (chunk) => Api.getMembers({ ids: chunk }, queryConfig), + (chunk) => Api.getMembers({ ids: chunk }), ), meta: { routine: getMembersRoutine, @@ -60,39 +60,6 @@ export default (queryConfig: QueryClientConfig) => { }); }, - useAvatar: ({ - id, - size = DEFAULT_THUMBNAIL_SIZE, - }: { - id?: UUID; - size?: string; - }) => { - const queryClient = useQueryClient(); - let shouldFetch = true; - if (id) { - // TODO: this casting is totally wrong, but allows to work for current member - // to be fixed - shouldFetch = - ( - queryClient.getQueryData( - memberKeys.single(id).content, - ) as CompleteMember - )?.extra?.hasAvatar ?? true; - } - return useQuery({ - queryKey: memberKeys.single(id).avatar({ size, replyUrl: false }), - queryFn: () => { - if (!id) { - throw new UndefinedArgument(); - } - return Api.downloadAvatar({ id, size }, queryConfig); - }, - ...defaultQueryOptions, - enabled: Boolean(id) && shouldFetch, - staleTime: CONSTANT_KEY_STALE_TIME_MILLISECONDS, - }); - }, - // use another hook because of key content useAvatarUrl: ({ id, @@ -119,7 +86,7 @@ export default (queryConfig: QueryClientConfig) => { if (!id) { throw new UndefinedArgument(); } - return Api.downloadAvatarUrl({ id, size }, queryConfig); + return Api.downloadAvatarUrl({ id, size }); }, ...defaultQueryOptions, enabled: Boolean(id) && shouldFetch, @@ -130,14 +97,14 @@ export default (queryConfig: QueryClientConfig) => { useMemberStorage: () => useQuery({ queryKey: memberKeys.current().storage, - queryFn: () => Api.getMemberStorage(queryConfig), + queryFn: () => Api.getMemberStorage(), ...defaultQueryOptions, }), useMemberStorageFiles: (pagination: Partial) => useQuery({ queryKey: memberKeys.current().storageFiles(pagination), - queryFn: () => Api.getMemberStorageFiles(pagination, queryConfig), + queryFn: () => Api.getMemberStorageFiles(pagination), ...defaultQueryOptions, }), }; diff --git a/src/member/routes.ts b/src/member/routes.ts index 274e22d1..cac5b547 100644 --- a/src/member/routes.ts +++ b/src/member/routes.ts @@ -10,9 +10,6 @@ export const buildPatchCurrentMemberRoute = () => `${MEMBERS_ROUTE}/current`; export const buildPostMemberEmailUpdateRoute = () => `${MEMBERS_ROUTE}/current/email/change`; -export const buildGetMembersByEmailRoute = (emails: string[]) => - `${MEMBERS_ROUTE}/search?${new URLSearchParams(emails.map((e) => ['email', e.toLowerCase()]))}`; - export const buildGetMembersByIdRoute = (ids: UUID[]) => `${MEMBERS_ROUTE}?${new URLSearchParams(ids.map((id) => ['id', id]))}`; diff --git a/src/mutations/membership.test.ts b/src/mutations/membership.test.ts index ffb388d8..00b6d1eb 100644 --- a/src/mutations/membership.test.ts +++ b/src/mutations/membership.test.ts @@ -17,22 +17,14 @@ import { OK_RESPONSE, UNAUTHORIZED_RESPONSE, buildMockInvitations, - buildResultOfData, } from '../../test/constants.js'; -import { - Endpoint, - mockMutation, - setUpTest, - waitForMutation, -} from '../../test/utils.js'; +import { mockMutation, setUpTest, waitForMutation } from '../../test/utils.js'; import { itemKeys } from '../keys.js'; -import { buildGetMembersByEmailRoute } from '../member/routes.js'; import { buildDeleteItemMembershipRoute, buildEditItemMembershipRoute, buildPostInvitationsRoute, buildPostItemMembershipRoute, - buildPostManyItemMembershipsRoute, } from '../routes.js'; import { deleteItemMembershipRoutine, @@ -360,119 +352,6 @@ describe('Membership Mutations', () => { }); }); - it('Unauthorized to search members', async () => { - // set data in cache - items.forEach((i) => { - const itemKey = itemKeys.single(i.id).content; - queryClient.setQueryData(itemKey, i); - }); - queryClient.setQueryData(itemKeys.accessiblePage({}, {}), items); - queryClient.setQueryData( - itemKeys.single(itemId).memberships, - ITEM_MEMBERSHIPS_RESPONSE, - ); - queryClient.setQueryData( - itemKeys.single(itemId).invitation, - initialInvitations, - ); - - const endpoints = [ - { - response: UNAUTHORIZED_RESPONSE, - statusCode: StatusCodes.UNAUTHORIZED, - method: HttpMethod.Get, - route: `/${buildGetMembersByEmailRoute(emails)}`, - }, - ]; - - const mockedMutation = await mockMutation({ - endpoints, - mutation, - wrapper, - }); - - const invitations = emails.map((email) => ({ email, permission })); - await act(async () => { - mockedMutation.mutate({ itemId, invitations }); - await waitForMutation(); - }); - - // check invalidations - const mem = queryClient.getQueryState( - itemKeys.single(itemId).memberships, - ); - expect(mem?.isInvalidated).toBeTruthy(); - const inv = queryClient.getQueryState(itemKeys.single(itemId).invitation); - expect(inv?.isInvalidated).toBeTruthy(); - - // check notification trigger - expect(mockedNotifier).toHaveBeenCalledWith({ - type: shareItemRoutine.FAILURE, - payload: expect.anything(), - }); - }); - - it('Unauthorized to post memberships', async () => { - // set data in cache - items.forEach((i) => { - const itemKey = itemKeys.single(i.id).content; - queryClient.setQueryData(itemKey, i); - }); - queryClient.setQueryData(itemKeys.accessiblePage({}, {}), items); - queryClient.setQueryData( - itemKeys.single(itemId).memberships, - ITEM_MEMBERSHIPS_RESPONSE, - ); - queryClient.setQueryData( - itemKeys.single(itemId).invitation, - initialInvitations, - ); - - const endpoints: Endpoint[] = [ - { - response: buildResultOfData([{ email: emails[0], id: emails[0] }]), - method: HttpMethod.Get, - route: `/${buildGetMembersByEmailRoute(emails)}`, - }, - { - response: UNAUTHORIZED_RESPONSE, - statusCode: StatusCodes.UNAUTHORIZED, - method: HttpMethod.Post, - route: `/${buildPostManyItemMembershipsRoute(itemId)}`, - }, - { - response: buildResultOfData(initialInvitations), - method: HttpMethod.Post, - route: `/${buildPostInvitationsRoute(itemId)}`, - }, - ]; - - const mockedMutation = await mockMutation({ - endpoints, - mutation, - wrapper, - }); - - const invitations = emails.map((email) => ({ email, permission })); - await act(async () => { - mockedMutation.mutate({ itemId, invitations }); - await waitForMutation(); - }); - - // check invalidations - const mem = queryClient.getQueryState( - itemKeys.single(itemId).memberships, - ); - expect(mem?.isInvalidated).toBeTruthy(); - const inv = queryClient.getQueryState(itemKeys.single(itemId).invitation); - expect(inv?.isInvalidated).toBeTruthy(); - - // check notification trigger - expect(mockedNotifier).toHaveBeenCalledWith( - expect.objectContaining({ type: shareItemRoutine.SUCCESS }), - ); - }); - it('Unauthorized to post invitations', async () => { // set data in cache items.forEach((i) => {