From 26014fe308aedb1d29e8d2f3cb469ad30bad9d53 Mon Sep 17 00:00:00 2001 From: kim Date: Mon, 22 Jan 2024 17:34:44 +0100 Subject: [PATCH 01/14] feat: add mutations and hooks for geolocation --- src/api/index.ts | 1 + src/api/itemGeolocation.ts | 56 ++++++++++++++++++++++ src/api/routes.ts | 22 +++++++++ src/config/errors.ts | 4 +- src/config/keys.ts | 20 ++++++++ src/hooks/itemGeolocation.ts | 54 +++++++++++++++++++++ src/mutations/index.ts | 2 + src/mutations/itemGeolocation.ts | 80 ++++++++++++++++++++++++++++++++ src/routines/index.ts | 1 + src/routines/itemGeolocation.ts | 8 ++++ 10 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/api/itemGeolocation.ts create mode 100644 src/hooks/itemGeolocation.ts create mode 100644 src/mutations/itemGeolocation.ts create mode 100644 src/routines/itemGeolocation.ts diff --git a/src/api/index.ts b/src/api/index.ts index caae5e7c..e5a31908 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -21,3 +21,4 @@ export * from './search'; export * from './subscription'; export * from './publicProfile'; export * from './shortLink'; +export * from './itemGeolocation'; diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts new file mode 100644 index 00000000..f28ad94d --- /dev/null +++ b/src/api/itemGeolocation.ts @@ -0,0 +1,56 @@ +import { UUID } from '@graasp/sdk'; + +import { PartialQueryConfigForApi } from '../types'; +import { verifyAuthentication } from './axios'; +import { + buildDeleteItemGeolocationRoute, + buildGetItemGeolocationRoute, + buildGetItemsInMapRoute, + buildPutItemGeolocationRoute, +} from './routes'; + +type ItemGeolocation = {}; + +// eslint-disable-next-line import/prefer-default-export +export const getItemGeolocation = async ( + { API_HOST, axios }: PartialQueryConfigForApi, + id: UUID, +) => + axios + .get(`${API_HOST}/${buildGetItemGeolocationRoute(id)}`) + .then(({ data }) => data); + +export const putItemGeolocation = async ( + payload: { itemId: UUID; lat: number; lng: number }, + { API_HOST, axios }: PartialQueryConfigForApi, +) => + verifyAuthentication(() => + axios + .put( + `${API_HOST}/${buildPutItemGeolocationRoute(payload.itemId)}`, + { lat: payload.lat, lng: payload.lng }, + ) + .then(({ data }) => data), + ); + +export const getItemsInMap = async ( + payload: { lat1: number; lat2: number; lng1: number; lng2: number }, + { API_HOST, axios }: PartialQueryConfigForApi, +) => + verifyAuthentication(() => + axios + .get(`${API_HOST}/${buildGetItemsInMapRoute(payload)}`) + .then(({ data }) => data), + ); + +export const deleteItemGeolocation = async ( + payload: { itemId: UUID }, + { API_HOST, axios }: PartialQueryConfigForApi, +) => + verifyAuthentication(() => + axios + .delete( + `${API_HOST}/${buildDeleteItemGeolocationRoute(payload.itemId)}`, + ) + .then(({ data }) => data), + ); diff --git a/src/api/routes.ts b/src/api/routes.ts index 7898e2ca..8fab9b25 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -441,6 +441,24 @@ export const buildPostShortLinkRoute = () => `${SHORT_LINKS_ROUTE}`; export const buildPatchShortLinkRoute = (alias: string) => `${SHORT_LINKS_ROUTE}/${alias}`; +export const buildGetItemGeolocationRoute = (itemId: UUID) => + `${ITEMS_ROUTE}/${itemId}/geolocation`; +export const buildPutItemGeolocationRoute = (itemId: UUID) => + `${ITEMS_ROUTE}/${itemId}/geolocation`; +export const buildDeleteItemGeolocationRoute = (itemId: UUID) => + `${ITEMS_ROUTE}/${itemId}/geolocation`; +export const buildGetItemsInMapRoute = ({ + lat1, + lat2, + lng1, + lng2, +}: { + lat1: number; + lat2: number; + lng1: number; + lng2: number; +}) => `${ITEMS_ROUTE}/map?lat1=${lat1}&lat2=${lat2}&lng1=${lng1}&lng2=${lng2}`; + export const API_ROUTES = { APPS_ROUTE, buildAppListRoute, @@ -553,4 +571,8 @@ export const API_ROUTES = { PUBLIC_PROFILE_ROUTE, GET_OWN_PROFILE, buildGetPublicProfileRoute, + buildGetItemsInMapRoute, + buildGetItemGeolocationRoute, + buildDeleteItemGeolocationRoute, + buildPutItemGeolocationRoute, }; diff --git a/src/config/errors.ts b/src/config/errors.ts index dc3d32d6..72b485e7 100644 --- a/src/config/errors.ts +++ b/src/config/errors.ts @@ -11,9 +11,9 @@ export class UserIsSignedOut extends Error { } export class UndefinedArgument extends Error { - constructor() { + constructor(data?: object) { super(); - this.message = 'UnexpectedInput'; + this.message = `UnexpectedInput ${JSON.stringify(data ?? {})}`; this.name = 'UnexpectedInput'; this.stack = new Error().stack; } diff --git a/src/config/keys.ts b/src/config/keys.ts index f53149bb..902327b0 100644 --- a/src/config/keys.ts +++ b/src/config/keys.ts @@ -279,6 +279,24 @@ export const CURRENT_MEMBER_STORAGE_KEY = [ export const OWN_PUBLIC_PROFILE_KEY = ['own-profile']; export const buildPublicProfileKey = (memberId: UUID) => ['profile', memberId]; + +export const buildItemsInMapKey = ({ + lat1, + lat2, + lng1, + lng2, +}: { + lat1: number; + lat2: number; + lng1: number; + lng2: number; +}) => [ITEMS_CONTEXT, 'map', { lat1, lat2, lng1, lng2 }]; +export const buildItemGeolocationKey = (itemId?: UUID) => [ + ITEMS_CONTEXT, + itemId, + 'geolocation', +]; + export const DATA_KEYS = { APPS_KEY, buildItemKey, @@ -320,4 +338,6 @@ export const DATA_KEYS = { buildSearchPublishedItemsKey, OWN_PUBLIC_PROFILE_KEY, buildPublicProfileKey, + buildItemsInMapKey, + buildItemGeolocationKey, }; diff --git a/src/hooks/itemGeolocation.ts b/src/hooks/itemGeolocation.ts new file mode 100644 index 00000000..ea17f4da --- /dev/null +++ b/src/hooks/itemGeolocation.ts @@ -0,0 +1,54 @@ +import { UUID } from '@graasp/sdk'; + +import { useQuery } from 'react-query'; + +import * as Api from '../api'; +import { UndefinedArgument } from '../config/errors'; +import { buildItemGeolocationKey, buildItemsInMapKey } from '../config/keys'; +import { getItemGeolocationRoutine } from '../routines/itemGeolocation'; +import { QueryClientConfig } from '../types'; + +export default (queryConfig: QueryClientConfig) => { + const { notifier, defaultQueryOptions } = queryConfig; + + const useItemGeolocation = (id?: UUID) => + useQuery({ + queryKey: buildItemGeolocationKey(id), + queryFn: () => { + if (!id) { + throw new UndefinedArgument(); + } + return Api.getItemGeolocation(queryConfig, id); + }, + ...defaultQueryOptions, + enabled: Boolean(id), + onError: (error) => { + notifier?.({ + type: getItemGeolocationRoutine.FAILURE, + payload: { error }, + }); + }, + }); + + const useItemsInMap = ( + lat1: number, + lat2: number, + lng1: number, + lng2: number, + ) => + useQuery({ + queryKey: buildItemsInMapKey({ lat1, lat2, lng1, lng2 }), + queryFn: () => { + if (!lat1 || !lat2 || !lng1 || !lng2) { + throw new UndefinedArgument({ lat1, lat2, lng1, lng2 }); + } + + return Api.getItemsInMap({ lat1, lat2, lng1, lng2 }, queryConfig); + }, + // question: cat lat or lng be 0? + enabled: Boolean(lat1 && lat2 && lng1 && lng2), + ...defaultQueryOptions, + }); + + return { useItemGeolocation, useItemsInMap }; +}; diff --git a/src/mutations/index.ts b/src/mutations/index.ts index 9787bf2d..c307a803 100644 --- a/src/mutations/index.ts +++ b/src/mutations/index.ts @@ -8,6 +8,7 @@ import itemCategoryMutations from './itemCategory'; import itemExportMutations from './itemExport'; import itemFavoriteMutations from './itemFavorite'; import flagsMutations from './itemFlag'; +import itemGeolocationMutations from './itemGeolocation'; import itemLikeMutations from './itemLike'; import itemLoginMutations from './itemLogin'; import itemPublishMutations from './itemPublish'; @@ -41,6 +42,7 @@ const configureMutations = (queryConfig: QueryClientConfig) => ({ ...itemPublishMutations(queryConfig), ...publicProfileMutations(queryConfig), ...shortLinksMutations(queryConfig), + ...itemGeolocationMutations(queryConfig), }); export default configureMutations; diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts new file mode 100644 index 00000000..2364e447 --- /dev/null +++ b/src/mutations/itemGeolocation.ts @@ -0,0 +1,80 @@ +import { UUID } from '@graasp/sdk'; + +import { useMutation, useQueryClient } from 'react-query'; + +import { deleteItemGeolocation, putItemGeolocation } from '../api'; +import { buildItemGeolocationKey } from '../config/keys'; +import { + deleteItemGeolocationRoutine, + putItemGeolocationRoutine, +} from '../routines'; +import { QueryClientConfig } from '../types'; + +export default (queryConfig: QueryClientConfig) => { + const usePutItemGeolocation = () => { + const queryClient = useQueryClient(); + return useMutation( + (payload: { itemId: UUID; lat: number; lng: number }) => + putItemGeolocation(payload, queryConfig), + { + onSuccess: () => { + queryConfig.notifier?.({ + type: putItemGeolocationRoutine.SUCCESS, + }); + }, + onError: (error: Error) => { + queryConfig.notifier?.({ + type: putItemGeolocationRoutine.FAILURE, + payload: { error }, + }); + }, + onSettled: (_data, _error, { itemId }) => { + queryClient.invalidateQueries(buildItemGeolocationKey(itemId)); + }, + }, + ); + }; + + const useDeleteItemGeolocation = () => { + const queryClient = useQueryClient(); + return useMutation( + (payload: { itemId: UUID }) => + deleteItemGeolocation(payload, queryConfig), + { + onMutate: async ({ id, itemId }: { id: UUID; itemId: UUID }) => { + const key = buildItemGeolocationKey(itemId); + + const prevValue = queryClient.getQueryData(key); + + // remove ItemGeolocation from list + if (prevValue) { + queryClient.setQueryData( + key, + prevValue.filter(({ id: iId }) => iId !== id), + ); + return { prevValue }; + } + return {}; + }, + onSuccess: () => { + queryConfig.notifier?.({ + type: deleteItemGeolocationRoutine.SUCCESS, + }); + }, + onError: (error: Error, { itemId }, context) => { + const key = buildItemGeolocationKey(itemId); + queryClient.setQueryData(key, context?.prevValue); + queryConfig.notifier?.({ + type: deleteItemGeolocationRoutine.FAILURE, + payload: { error }, + }); + }, + onSettled: (_data, _error, { itemId }) => { + queryClient.invalidateQueries(buildItemGeolocationKey(itemId)); + }, + }, + ); + }; + + return { usePutItemGeolocation, useDeleteItemGeolocation }; +}; diff --git a/src/routines/index.ts b/src/routines/index.ts index 4fc28be2..21e40228 100644 --- a/src/routines/index.ts +++ b/src/routines/index.ts @@ -17,3 +17,4 @@ export * from './authentication'; export * from './plan'; export * from './itemPublish'; export * from './publicProfile'; +export * from './itemGeolocation'; diff --git a/src/routines/itemGeolocation.ts b/src/routines/itemGeolocation.ts new file mode 100644 index 00000000..2f3c9eae --- /dev/null +++ b/src/routines/itemGeolocation.ts @@ -0,0 +1,8 @@ +import createRoutine from './utils'; + +export const getItemGeolocationRoutine = createRoutine('GET_ITEM_GEOLOCATION'); +export const deleteItemGeolocationRoutine = createRoutine( + 'DELETE_ITEM_GEOLOCATION', +); +export const putItemGeolocationRoutine = createRoutine('PUT_ITEM_GEOLOCATION'); +export const getItemsInMapRoutine = createRoutine('GET_ITEMS_IN_MAP'); From fd750308effa1d4cd9b2b235cd0df8372779ea44 Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 14:31:44 +0100 Subject: [PATCH 02/14] refactor: fix type --- src/api/itemGeolocation.ts | 11 +++++++++-- src/mutations/itemGeolocation.ts | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index f28ad94d..688a8bb8 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { UUID } from '@graasp/sdk'; +import { DiscriminatedItem, UUID } from '@graasp/sdk'; import { PartialQueryConfigForApi } from '../types'; import { verifyAuthentication } from './axios'; @@ -9,7 +9,14 @@ import { buildPutItemGeolocationRoute, } from './routes'; -type ItemGeolocation = {}; +// TODO: sdk +export type ItemGeolocation = { + id: UUID; + lat: number; + lng: number; + item: DiscriminatedItem; + country: string; +}; // eslint-disable-next-line import/prefer-default-export export const getItemGeolocation = async ( diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index 2364e447..cfb0b6e9 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -2,7 +2,11 @@ import { UUID } from '@graasp/sdk'; import { useMutation, useQueryClient } from 'react-query'; -import { deleteItemGeolocation, putItemGeolocation } from '../api'; +import { + ItemGeolocation, + deleteItemGeolocation, + putItemGeolocation, +} from '../api'; import { buildItemGeolocationKey } from '../config/keys'; import { deleteItemGeolocationRoutine, From 923a5da2fc1261b8ae2efd8d108ef0b59decc30a Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 15:00:39 +0100 Subject: [PATCH 03/14] refactor: export hooks --- src/hooks/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 0ea8df43..27f8d88c 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -8,6 +8,7 @@ import configureChatHooks from './chat'; import configureInvitationHooks from './invitation'; import configureItemHooks from './item'; import configureItemFavoriteHooks from './itemFavorite'; +import configureItemGeolocationHooks from './itemGeolocation'; import configureItemLikeHooks from './itemLike'; import configureItemLoginHooks from './itemLogin'; import configureItemPublishedHooks from './itemPublish'; @@ -55,5 +56,6 @@ export default ( ...configurePlanHooks(queryConfig), ...configurePublicProfileHooks(queryConfig), ...configureShortLinkHooks(queryConfig), + ...configureItemGeolocationHooks(queryConfig), }; }; From 30bc10ccb322803c09624bb3ec505b865d32b992 Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 15:22:44 +0100 Subject: [PATCH 04/14] refactor: update itemsInMap --- src/hooks/itemGeolocation.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/hooks/itemGeolocation.ts b/src/hooks/itemGeolocation.ts index ea17f4da..6150f329 100644 --- a/src/hooks/itemGeolocation.ts +++ b/src/hooks/itemGeolocation.ts @@ -30,12 +30,17 @@ export default (queryConfig: QueryClientConfig) => { }, }); - const useItemsInMap = ( - lat1: number, - lat2: number, - lng1: number, - lng2: number, - ) => + const useItemsInMap = ({ + lat1, + lat2, + lng1, + lng2, + }: { + lat1: number; + lat2: number; + lng1: number; + lng2: number; + }) => useQuery({ queryKey: buildItemsInMapKey({ lat1, lat2, lng1, lng2 }), queryFn: () => { From 01f7ecd1e6c0f2f29e66ced065099aeeeb7bea4d Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 15:36:00 +0100 Subject: [PATCH 05/14] refactor: fix get items in map --- src/api/routes.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/routes.ts b/src/api/routes.ts index 8fab9b25..b5765cd5 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -457,7 +457,8 @@ export const buildGetItemsInMapRoute = ({ lat2: number; lng1: number; lng2: number; -}) => `${ITEMS_ROUTE}/map?lat1=${lat1}&lat2=${lat2}&lng1=${lng1}&lng2=${lng2}`; +}) => + `${ITEMS_ROUTE}/geolocation?lat1=${lat1}&lat2=${lat2}&lng1=${lng1}&lng2=${lng2}`; export const API_ROUTES = { APPS_ROUTE, From 316285da4dca339e13a427795fcab088c2085210 Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 16:56:36 +0100 Subject: [PATCH 06/14] refactor: add post item with geoloc mutation --- src/api/itemGeolocation.ts | 13 +++++++++++++ src/api/routes.ts | 2 ++ src/mutations/itemGeolocation.ts | 31 ++++++++++++++++++++++++++++++- src/routines/itemGeolocation.ts | 3 +++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index 688a8bb8..3a27fe05 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -40,6 +40,19 @@ export const putItemGeolocation = async ( .then(({ data }) => data), ); +export const postItemWithGeolocation = async ( + payload: { itemId: UUID; lat: number; lng: number }, + { API_HOST, axios }: PartialQueryConfigForApi, +) => + verifyAuthentication(() => + axios + .post( + `${API_HOST}/${buildPutItemGeolocationRoute(payload.itemId)}`, + { lat: payload.lat, lng: payload.lng }, + ) + .then(({ data }) => data), + ); + export const getItemsInMap = async ( payload: { lat1: number; lat2: number; lng1: number; lng2: number }, { API_HOST, axios }: PartialQueryConfigForApi, diff --git a/src/api/routes.ts b/src/api/routes.ts index b5765cd5..074bf1ad 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -445,6 +445,8 @@ export const buildGetItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; export const buildPutItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; +export const buildPostItemWithGeolocationRoute = (itemId: UUID) => + `${ITEMS_ROUTE}/map${itemId ? `?id=${itemId}` : ''}`; export const buildDeleteItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; export const buildGetItemsInMapRoute = ({ diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index cfb0b6e9..53853944 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -5,11 +5,13 @@ import { useMutation, useQueryClient } from 'react-query'; import { ItemGeolocation, deleteItemGeolocation, + postItemWithGeolocation, putItemGeolocation, } from '../api'; import { buildItemGeolocationKey } from '../config/keys'; import { deleteItemGeolocationRoutine, + postItemWithGeolocationRoutine, putItemGeolocationRoutine, } from '../routines'; import { QueryClientConfig } from '../types'; @@ -38,6 +40,29 @@ export default (queryConfig: QueryClientConfig) => { }, ); }; + const usePostItemWithGeolocation = () => { + const queryClient = useQueryClient(); + return useMutation( + (payload: { itemId: UUID; lat: number; lng: number }) => + postItemWithGeolocation(payload, queryConfig), + { + onSuccess: () => { + queryConfig.notifier?.({ + type: postItemWithGeolocationRoutine.SUCCESS, + }); + }, + onError: (error: Error) => { + queryConfig.notifier?.({ + type: postItemWithGeolocationRoutine.FAILURE, + payload: { error }, + }); + }, + onSettled: (_data, _error, { itemId }) => { + queryClient.invalidateQueries(buildItemGeolocationKey(itemId)); + }, + }, + ); + }; const useDeleteItemGeolocation = () => { const queryClient = useQueryClient(); @@ -80,5 +105,9 @@ export default (queryConfig: QueryClientConfig) => { ); }; - return { usePutItemGeolocation, useDeleteItemGeolocation }; + return { + usePostItemWithGeolocation, + usePutItemGeolocation, + useDeleteItemGeolocation, + }; }; diff --git a/src/routines/itemGeolocation.ts b/src/routines/itemGeolocation.ts index 2f3c9eae..b2c649f8 100644 --- a/src/routines/itemGeolocation.ts +++ b/src/routines/itemGeolocation.ts @@ -5,4 +5,7 @@ export const deleteItemGeolocationRoutine = createRoutine( 'DELETE_ITEM_GEOLOCATION', ); export const putItemGeolocationRoutine = createRoutine('PUT_ITEM_GEOLOCATION'); +export const postItemWithGeolocationRoutine = createRoutine( + 'POST_ITEM_WITH_GEOLOCATION', +); export const getItemsInMapRoutine = createRoutine('GET_ITEMS_IN_MAP'); From a6d4bfe8971f2301f98b611721876dc07779029d Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 17:03:07 +0100 Subject: [PATCH 07/14] refactor: item geoloc post item --- src/api/itemGeolocation.ts | 11 +++++++---- src/config/keys.ts | 1 + src/mutations/itemGeolocation.ts | 19 +++++++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index 3a27fe05..0be5ebf0 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { DiscriminatedItem, UUID } from '@graasp/sdk'; +import { DiscriminatedItem, Item, UUID } from '@graasp/sdk'; import { PartialQueryConfigForApi } from '../types'; import { verifyAuthentication } from './axios'; @@ -41,14 +41,17 @@ export const putItemGeolocation = async ( ); export const postItemWithGeolocation = async ( - payload: { itemId: UUID; lat: number; lng: number }, + payload: { lat: number; lng: number; parentItemId: UUID } & Partial, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => axios .post( - `${API_HOST}/${buildPutItemGeolocationRoute(payload.itemId)}`, - { lat: payload.lat, lng: payload.lng }, + `${API_HOST}/${buildPutItemGeolocationRoute(payload.parentItemId)}`, + { + lat: payload.lat, + lng: payload.lng, + }, ) .then(({ data }) => data), ); diff --git a/src/config/keys.ts b/src/config/keys.ts index 902327b0..6039b895 100644 --- a/src/config/keys.ts +++ b/src/config/keys.ts @@ -280,6 +280,7 @@ export const CURRENT_MEMBER_STORAGE_KEY = [ export const OWN_PUBLIC_PROFILE_KEY = ['own-profile']; export const buildPublicProfileKey = (memberId: UUID) => ['profile', memberId]; +// TODO: all key export const buildItemsInMapKey = ({ lat1, lat2, diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index 53853944..192e6c01 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { UUID } from '@graasp/sdk'; +import { Item, UUID } from '@graasp/sdk'; import { useMutation, useQueryClient } from 'react-query'; @@ -40,11 +40,17 @@ export default (queryConfig: QueryClientConfig) => { }, ); }; + // eslint-disable-next-line arrow-body-style const usePostItemWithGeolocation = () => { - const queryClient = useQueryClient(); + // const queryClient = useQueryClient(); return useMutation( - (payload: { itemId: UUID; lat: number; lng: number }) => - postItemWithGeolocation(payload, queryConfig), + ( + payload: { + lat: number; + lng: number; + parentItemId: UUID; + } & Partial, + ) => postItemWithGeolocation(payload, queryConfig), { onSuccess: () => { queryConfig.notifier?.({ @@ -57,8 +63,9 @@ export default (queryConfig: QueryClientConfig) => { payload: { error }, }); }, - onSettled: (_data, _error, { itemId }) => { - queryClient.invalidateQueries(buildItemGeolocationKey(itemId)); + onSettled: (_data, _error) => { + // TODO + // queryClient.invalidateQueries(buildItemsInMapKey()); }, }, ); From 45d91c9ea25329dca743af2eb593534a2b10b60d Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 17:08:05 +0100 Subject: [PATCH 08/14] refactor: item geoloc post item --- src/api/itemGeolocation.ts | 13 ++++++++++--- src/api/routes.ts | 2 +- src/mutations/itemGeolocation.ts | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index 0be5ebf0..f2ecf34c 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { DiscriminatedItem, Item, UUID } from '@graasp/sdk'; +import { DiscriminatedItem, UUID } from '@graasp/sdk'; import { PartialQueryConfigForApi } from '../types'; import { verifyAuthentication } from './axios'; @@ -6,6 +6,7 @@ import { buildDeleteItemGeolocationRoute, buildGetItemGeolocationRoute, buildGetItemsInMapRoute, + buildPostItemWithGeolocationRoute, buildPutItemGeolocationRoute, } from './routes'; @@ -41,13 +42,19 @@ export const putItemGeolocation = async ( ); export const postItemWithGeolocation = async ( - payload: { lat: number; lng: number; parentItemId: UUID } & Partial, + payload: { + lat: number; + lng: number; + parentItemId?: UUID; + } & Partial, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => axios .post( - `${API_HOST}/${buildPutItemGeolocationRoute(payload.parentItemId)}`, + `${API_HOST}/${buildPostItemWithGeolocationRoute( + payload.parentItemId, + )}`, { lat: payload.lat, lng: payload.lng, diff --git a/src/api/routes.ts b/src/api/routes.ts index 074bf1ad..077f91ec 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -445,7 +445,7 @@ export const buildGetItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; export const buildPutItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; -export const buildPostItemWithGeolocationRoute = (itemId: UUID) => +export const buildPostItemWithGeolocationRoute = (itemId?: UUID) => `${ITEMS_ROUTE}/map${itemId ? `?id=${itemId}` : ''}`; export const buildDeleteItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index 192e6c01..efe2d416 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { Item, UUID } from '@graasp/sdk'; +import { DiscriminatedItem, UUID } from '@graasp/sdk'; import { useMutation, useQueryClient } from 'react-query'; @@ -48,8 +48,8 @@ export default (queryConfig: QueryClientConfig) => { payload: { lat: number; lng: number; - parentItemId: UUID; - } & Partial, + parentItemId?: UUID; + } & Partial, ) => postItemWithGeolocation(payload, queryConfig), { onSuccess: () => { From 64088858c3ed35526800475b21a00841044fcc30 Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 17:10:17 +0100 Subject: [PATCH 09/14] refactor: item geoloc post item --- src/api/itemGeolocation.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index f2ecf34c..c40f21b8 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -55,10 +55,7 @@ export const postItemWithGeolocation = async ( `${API_HOST}/${buildPostItemWithGeolocationRoute( payload.parentItemId, )}`, - { - lat: payload.lat, - lng: payload.lng, - }, + payload, ) .then(({ data }) => data), ); From 91f046a18ea88ba0f4e305333e9d9418aaf1d88b Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 23 Jan 2024 17:29:02 +0100 Subject: [PATCH 10/14] refactor: invalidate query on post --- package.json | 2 +- src/api/itemGeolocation.ts | 11 +---------- src/config/keys.ts | 29 ++++++++++++++++------------- src/hooks/itemGeolocation.ts | 4 ++-- src/mutations/itemGeolocation.ts | 10 ++++------ yarn.lock | 18 +++++++++--------- 6 files changed, 33 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index aa27d3d2..ab6ddd57 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "Alexandre Chau" ], "dependencies": { - "@graasp/sdk": "3.5.0", + "@graasp/sdk": "github:graasp/graasp-sdk#item-geolocation", "@graasp/translations": "1.22.1", "axios": "0.27.2", "crypto-js": "4.2.0", diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index c40f21b8..80881e15 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { DiscriminatedItem, UUID } from '@graasp/sdk'; +import { DiscriminatedItem, ItemGeolocation, UUID } from '@graasp/sdk'; import { PartialQueryConfigForApi } from '../types'; import { verifyAuthentication } from './axios'; @@ -10,15 +10,6 @@ import { buildPutItemGeolocationRoute, } from './routes'; -// TODO: sdk -export type ItemGeolocation = { - id: UUID; - lat: number; - lng: number; - item: DiscriminatedItem; - country: string; -}; - // eslint-disable-next-line import/prefer-default-export export const getItemGeolocation = async ( { API_HOST, axios }: PartialQueryConfigForApi, diff --git a/src/config/keys.ts b/src/config/keys.ts index 6039b895..08ad2bb8 100644 --- a/src/config/keys.ts +++ b/src/config/keys.ts @@ -280,18 +280,21 @@ export const CURRENT_MEMBER_STORAGE_KEY = [ export const OWN_PUBLIC_PROFILE_KEY = ['own-profile']; export const buildPublicProfileKey = (memberId: UUID) => ['profile', memberId]; -// TODO: all key -export const buildItemsInMapKey = ({ - lat1, - lat2, - lng1, - lng2, -}: { - lat1: number; - lat2: number; - lng1: number; - lng2: number; -}) => [ITEMS_CONTEXT, 'map', { lat1, lat2, lng1, lng2 }]; +export const buildItemsInMapKeys = { + all: [ITEMS_CONTEXT, 'map'], + single: ({ + lat1, + lat2, + lng1, + lng2, + }: { + lat1: number; + lat2: number; + lng1: number; + lng2: number; + }) => [...buildItemsInMapKeys.all, { lat1, lat2, lng1, lng2 }], +}; + export const buildItemGeolocationKey = (itemId?: UUID) => [ ITEMS_CONTEXT, itemId, @@ -339,6 +342,6 @@ export const DATA_KEYS = { buildSearchPublishedItemsKey, OWN_PUBLIC_PROFILE_KEY, buildPublicProfileKey, - buildItemsInMapKey, + buildItemsInMapKeys, buildItemGeolocationKey, }; diff --git a/src/hooks/itemGeolocation.ts b/src/hooks/itemGeolocation.ts index 6150f329..f358386f 100644 --- a/src/hooks/itemGeolocation.ts +++ b/src/hooks/itemGeolocation.ts @@ -4,7 +4,7 @@ import { useQuery } from 'react-query'; import * as Api from '../api'; import { UndefinedArgument } from '../config/errors'; -import { buildItemGeolocationKey, buildItemsInMapKey } from '../config/keys'; +import { buildItemGeolocationKey, buildItemsInMapKeys } from '../config/keys'; import { getItemGeolocationRoutine } from '../routines/itemGeolocation'; import { QueryClientConfig } from '../types'; @@ -42,7 +42,7 @@ export default (queryConfig: QueryClientConfig) => { lng2: number; }) => useQuery({ - queryKey: buildItemsInMapKey({ lat1, lat2, lng1, lng2 }), + queryKey: buildItemsInMapKeys.single({ lat1, lat2, lng1, lng2 }), queryFn: () => { if (!lat1 || !lat2 || !lng1 || !lng2) { throw new UndefinedArgument({ lat1, lat2, lng1, lng2 }); diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index efe2d416..9029788c 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -1,14 +1,13 @@ -import { DiscriminatedItem, UUID } from '@graasp/sdk'; +import { DiscriminatedItem, ItemGeolocation, UUID } from '@graasp/sdk'; import { useMutation, useQueryClient } from 'react-query'; import { - ItemGeolocation, deleteItemGeolocation, postItemWithGeolocation, putItemGeolocation, } from '../api'; -import { buildItemGeolocationKey } from '../config/keys'; +import { buildItemGeolocationKey, buildItemsInMapKeys } from '../config/keys'; import { deleteItemGeolocationRoutine, postItemWithGeolocationRoutine, @@ -42,7 +41,7 @@ export default (queryConfig: QueryClientConfig) => { }; // eslint-disable-next-line arrow-body-style const usePostItemWithGeolocation = () => { - // const queryClient = useQueryClient(); + const queryClient = useQueryClient(); return useMutation( ( payload: { @@ -64,8 +63,7 @@ export default (queryConfig: QueryClientConfig) => { }); }, onSettled: (_data, _error) => { - // TODO - // queryClient.invalidateQueries(buildItemsInMapKey()); + queryClient.invalidateQueries(buildItemsInMapKeys.all); }, }, ); diff --git a/yarn.lock b/yarn.lock index 7977e246..93073f04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1791,7 +1791,7 @@ __metadata: "@babel/preset-typescript": "npm:7.23.3" "@commitlint/cli": "npm:18.6.0" "@commitlint/config-conventional": "npm:18.6.0" - "@graasp/sdk": "npm:3.5.0" + "@graasp/sdk": "github:graasp/graasp-sdk#item-geolocation" "@graasp/translations": "npm:1.22.1" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.3.0" @@ -1843,15 +1843,15 @@ __metadata: languageName: unknown linkType: soft -"@graasp/sdk@npm:3.5.0": +"@graasp/sdk@github:graasp/graasp-sdk#item-geolocation": version: 3.5.0 - resolution: "@graasp/sdk@npm:3.5.0" + resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=7f9b1c7e88c8f22fa9beeb871d3a577c110cc0eb" dependencies: - date-fns: "npm:3.2.0" + date-fns: "npm:3.3.1" js-cookie: "npm:3.0.5" uuid: "npm:9.0.1" validator: "npm:13.11.0" - checksum: dfbb9977904336214e411c93a33084fca8e07cb73145f655a070dfdb81a772d6a0412849ee07c8ee2e74497d59f960a8ef1989062a4e41d78ad5d22b24e8a8db + checksum: 95a6309e11f70364c0f5ccb46317e16a3ebc8bf4766aaf0cac092e59b6f74b935253a9d1b62ed254879f62e64cb093b081935b553f71e61c9ab9823ca9ddb01c languageName: node linkType: hard @@ -3881,10 +3881,10 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:3.2.0": - version: 3.2.0 - resolution: "date-fns@npm:3.2.0" - checksum: 2f36cb9165a066ae90c0b6f0c25b3c70eb75bc52e64ba6dd8680964ebda144851a244ff4ed0b2afb4a6bab796a1af6a0e6c7c2814529d04c72fa434f931d4e81 +"date-fns@npm:3.3.1": + version: 3.3.1 + resolution: "date-fns@npm:3.3.1" + checksum: 98231936765dfb6fc6897676319b500a06a39f051b2c3ecbdd541a07ce9b1344b770277b8bfb1049fb7a2f70bf365ac8e6f1e2bb452b10e1a8101d518ca7f95d languageName: node linkType: hard From 552b7c60b2b402caa033b8c9824c7102d433c7df Mon Sep 17 00:00:00 2001 From: kim Date: Wed, 24 Jan 2024 10:16:04 +0100 Subject: [PATCH 11/14] feat: remove postinmap mutation --- src/api/item.ts | 14 ++++++++--- src/api/itemGeolocation.ts | 22 +---------------- src/api/routes.ts | 2 -- src/config/keys.ts | 6 ++--- src/hooks/itemGeolocation.ts | 7 ++++-- src/mutations/item.ts | 8 ++++++- src/mutations/itemGeolocation.ts | 41 +++----------------------------- src/routines/itemGeolocation.ts | 3 --- 8 files changed, 30 insertions(+), 73 deletions(-) diff --git a/src/api/item.ts b/src/api/item.ts index d1aa74e5..68e1966d 100644 --- a/src/api/item.ts +++ b/src/api/item.ts @@ -1,5 +1,6 @@ import { DiscriminatedItem, + ItemGeolocation, RecycledItemData, ResultOf, UUID, @@ -74,14 +75,19 @@ export const getAccessibleItems = async ( .then(({ data }) => data), ); +type AllOrNothing = T | Partial>; export type PostItemPayloadType = Partial & Pick & { parentId?: UUID; - }; -// payload = {name, type, description, extra} + } & AllOrNothing<{ + lat: ItemGeolocation['lat']; + lng: ItemGeolocation['lng']; + }>; + +// payload = {name, type, description, extra, lat, lng} // querystring = {parentId} export const postItem = async ( - { name, type, description, extra, parentId }: PostItemPayloadType, + { name, type, description, extra, parentId, lat, lng }: PostItemPayloadType, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => @@ -91,6 +97,8 @@ export const postItem = async ( type, description, extra, + lat, + lng, }) .then(({ data }) => data), ); diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index 80881e15..c5d071c0 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { DiscriminatedItem, ItemGeolocation, UUID } from '@graasp/sdk'; +import { ItemGeolocation, UUID } from '@graasp/sdk'; import { PartialQueryConfigForApi } from '../types'; import { verifyAuthentication } from './axios'; @@ -6,7 +6,6 @@ import { buildDeleteItemGeolocationRoute, buildGetItemGeolocationRoute, buildGetItemsInMapRoute, - buildPostItemWithGeolocationRoute, buildPutItemGeolocationRoute, } from './routes'; @@ -32,25 +31,6 @@ export const putItemGeolocation = async ( .then(({ data }) => data), ); -export const postItemWithGeolocation = async ( - payload: { - lat: number; - lng: number; - parentItemId?: UUID; - } & Partial, - { API_HOST, axios }: PartialQueryConfigForApi, -) => - verifyAuthentication(() => - axios - .post( - `${API_HOST}/${buildPostItemWithGeolocationRoute( - payload.parentItemId, - )}`, - payload, - ) - .then(({ data }) => data), - ); - export const getItemsInMap = async ( payload: { lat1: number; lat2: number; lng1: number; lng2: number }, { API_HOST, axios }: PartialQueryConfigForApi, diff --git a/src/api/routes.ts b/src/api/routes.ts index 077f91ec..b5765cd5 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -445,8 +445,6 @@ export const buildGetItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; export const buildPutItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; -export const buildPostItemWithGeolocationRoute = (itemId?: UUID) => - `${ITEMS_ROUTE}/map${itemId ? `?id=${itemId}` : ''}`; export const buildDeleteItemGeolocationRoute = (itemId: UUID) => `${ITEMS_ROUTE}/${itemId}/geolocation`; export const buildGetItemsInMapRoute = ({ diff --git a/src/config/keys.ts b/src/config/keys.ts index 08ad2bb8..62820a58 100644 --- a/src/config/keys.ts +++ b/src/config/keys.ts @@ -280,7 +280,7 @@ export const CURRENT_MEMBER_STORAGE_KEY = [ export const OWN_PUBLIC_PROFILE_KEY = ['own-profile']; export const buildPublicProfileKey = (memberId: UUID) => ['profile', memberId]; -export const buildItemsInMapKeys = { +export const itemsWithGeolocationKeys = { all: [ITEMS_CONTEXT, 'map'], single: ({ lat1, @@ -292,7 +292,7 @@ export const buildItemsInMapKeys = { lat2: number; lng1: number; lng2: number; - }) => [...buildItemsInMapKeys.all, { lat1, lat2, lng1, lng2 }], + }) => [...itemsWithGeolocationKeys.all, { lat1, lat2, lng1, lng2 }], }; export const buildItemGeolocationKey = (itemId?: UUID) => [ @@ -342,6 +342,6 @@ export const DATA_KEYS = { buildSearchPublishedItemsKey, OWN_PUBLIC_PROFILE_KEY, buildPublicProfileKey, - buildItemsInMapKeys, + itemsWithGeolocationKeys, buildItemGeolocationKey, }; diff --git a/src/hooks/itemGeolocation.ts b/src/hooks/itemGeolocation.ts index f358386f..d491cf28 100644 --- a/src/hooks/itemGeolocation.ts +++ b/src/hooks/itemGeolocation.ts @@ -4,7 +4,10 @@ import { useQuery } from 'react-query'; import * as Api from '../api'; import { UndefinedArgument } from '../config/errors'; -import { buildItemGeolocationKey, buildItemsInMapKeys } from '../config/keys'; +import { + buildItemGeolocationKey, + itemsWithGeolocationKeys, +} from '../config/keys'; import { getItemGeolocationRoutine } from '../routines/itemGeolocation'; import { QueryClientConfig } from '../types'; @@ -42,7 +45,7 @@ export default (queryConfig: QueryClientConfig) => { lng2: number; }) => useQuery({ - queryKey: buildItemsInMapKeys.single({ lat1, lat2, lng1, lng2 }), + queryKey: itemsWithGeolocationKeys.single({ lat1, lat2, lng1, lng2 }), queryFn: () => { if (!lat1 || !lat2 || !lng1 || !lng2) { throw new UndefinedArgument({ lat1, lat2, lng1, lng2 }); diff --git a/src/mutations/item.ts b/src/mutations/item.ts index 47b5dcd8..1824970e 100644 --- a/src/mutations/item.ts +++ b/src/mutations/item.ts @@ -23,6 +23,7 @@ import { buildItemKey, buildItemThumbnailKey, getKeyForParentId, + itemsWithGeolocationKeys, } from '../config/keys'; import { copyItemsRoutine, @@ -113,9 +114,14 @@ export default (queryConfig: QueryClientConfig) => { onError: (error: Error) => { notifier?.({ type: createItemRoutine.FAILURE, payload: { error } }); }, - onSettled: (_data, _error, { parentId }) => { + onSettled: (_data, _error, { lat, lng, parentId }) => { const key = getKeyForParentId(parentId); queryClient.invalidateQueries(key); + + // if item has geoloc, invalidate map related keys + if (lat || lng) { + queryClient.invalidateQueries(itemsWithGeolocationKeys.all); + } }, }, ); diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index 9029788c..f8f1c506 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -1,16 +1,11 @@ -import { DiscriminatedItem, ItemGeolocation, UUID } from '@graasp/sdk'; +import { ItemGeolocation, UUID } from '@graasp/sdk'; import { useMutation, useQueryClient } from 'react-query'; -import { - deleteItemGeolocation, - postItemWithGeolocation, - putItemGeolocation, -} from '../api'; -import { buildItemGeolocationKey, buildItemsInMapKeys } from '../config/keys'; +import { deleteItemGeolocation, putItemGeolocation } from '../api'; +import { buildItemGeolocationKey } from '../config/keys'; import { deleteItemGeolocationRoutine, - postItemWithGeolocationRoutine, putItemGeolocationRoutine, } from '../routines'; import { QueryClientConfig } from '../types'; @@ -39,35 +34,6 @@ export default (queryConfig: QueryClientConfig) => { }, ); }; - // eslint-disable-next-line arrow-body-style - const usePostItemWithGeolocation = () => { - const queryClient = useQueryClient(); - return useMutation( - ( - payload: { - lat: number; - lng: number; - parentItemId?: UUID; - } & Partial, - ) => postItemWithGeolocation(payload, queryConfig), - { - onSuccess: () => { - queryConfig.notifier?.({ - type: postItemWithGeolocationRoutine.SUCCESS, - }); - }, - onError: (error: Error) => { - queryConfig.notifier?.({ - type: postItemWithGeolocationRoutine.FAILURE, - payload: { error }, - }); - }, - onSettled: (_data, _error) => { - queryClient.invalidateQueries(buildItemsInMapKeys.all); - }, - }, - ); - }; const useDeleteItemGeolocation = () => { const queryClient = useQueryClient(); @@ -111,7 +77,6 @@ export default (queryConfig: QueryClientConfig) => { }; return { - usePostItemWithGeolocation, usePutItemGeolocation, useDeleteItemGeolocation, }; diff --git a/src/routines/itemGeolocation.ts b/src/routines/itemGeolocation.ts index b2c649f8..2f3c9eae 100644 --- a/src/routines/itemGeolocation.ts +++ b/src/routines/itemGeolocation.ts @@ -5,7 +5,4 @@ export const deleteItemGeolocationRoutine = createRoutine( 'DELETE_ITEM_GEOLOCATION', ); export const putItemGeolocationRoutine = createRoutine('PUT_ITEM_GEOLOCATION'); -export const postItemWithGeolocationRoutine = createRoutine( - 'POST_ITEM_WITH_GEOLOCATION', -); export const getItemsInMapRoutine = createRoutine('GET_ITEMS_IN_MAP'); From 0fdce43c0eca77c577a1cee655f72e473c0344ae Mon Sep 17 00:00:00 2001 From: kim Date: Wed, 24 Jan 2024 16:48:15 +0100 Subject: [PATCH 12/14] test: add tests --- src/api/item.ts | 5 +- src/api/itemGeolocation.ts | 11 +- src/config/keys.ts | 6 +- src/hooks/itemGeolocation.test.ts | 194 ++++++++++++++++++++++++++++++ src/hooks/itemGeolocation.ts | 30 +++-- src/mutations/item.ts | 2 +- src/mutations/itemGeolocation.ts | 35 ++---- test/constants.ts | 15 ++- yarn.lock | 4 +- 9 files changed, 253 insertions(+), 49 deletions(-) create mode 100644 src/hooks/itemGeolocation.test.ts diff --git a/src/api/item.ts b/src/api/item.ts index 68e1966d..e3aa442e 100644 --- a/src/api/item.ts +++ b/src/api/item.ts @@ -79,10 +79,7 @@ type AllOrNothing = T | Partial>; export type PostItemPayloadType = Partial & Pick & { parentId?: UUID; - } & AllOrNothing<{ - lat: ItemGeolocation['lat']; - lng: ItemGeolocation['lng']; - }>; + } & AllOrNothing>; // payload = {name, type, description, extra, lat, lng} // querystring = {parentId} diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index c5d071c0..753c9904 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { ItemGeolocation, UUID } from '@graasp/sdk'; +import { Item, ItemGeolocation, UUID } from '@graasp/sdk'; import { PartialQueryConfigForApi } from '../types'; import { verifyAuthentication } from './axios'; @@ -19,7 +19,7 @@ export const getItemGeolocation = async ( .then(({ data }) => data); export const putItemGeolocation = async ( - payload: { itemId: UUID; lat: number; lng: number }, + payload: { itemId: Item['id'] } & Pick, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => @@ -32,7 +32,12 @@ export const putItemGeolocation = async ( ); export const getItemsInMap = async ( - payload: { lat1: number; lat2: number; lng1: number; lng2: number }, + payload: { + lat1: ItemGeolocation['lat']; + lat2: ItemGeolocation['lat']; + lng1: ItemGeolocation['lng']; + lng2: ItemGeolocation['lng']; + }, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => diff --git a/src/config/keys.ts b/src/config/keys.ts index 62820a58..ef0c1a87 100644 --- a/src/config/keys.ts +++ b/src/config/keys.ts @@ -281,8 +281,8 @@ export const OWN_PUBLIC_PROFILE_KEY = ['own-profile']; export const buildPublicProfileKey = (memberId: UUID) => ['profile', memberId]; export const itemsWithGeolocationKeys = { - all: [ITEMS_CONTEXT, 'map'], - single: ({ + allBounds: [ITEMS_CONTEXT, 'map'], + inBounds: ({ lat1, lat2, lng1, @@ -292,7 +292,7 @@ export const itemsWithGeolocationKeys = { lat2: number; lng1: number; lng2: number; - }) => [...itemsWithGeolocationKeys.all, { lat1, lat2, lng1, lng2 }], + }) => [...itemsWithGeolocationKeys.allBounds, { lat1, lat2, lng1, lng2 }], }; export const buildItemGeolocationKey = (itemId?: UUID) => [ diff --git a/src/hooks/itemGeolocation.test.ts b/src/hooks/itemGeolocation.test.ts new file mode 100644 index 00000000..a1212b98 --- /dev/null +++ b/src/hooks/itemGeolocation.test.ts @@ -0,0 +1,194 @@ +import { StatusCodes } from 'http-status-codes'; + +import { ITEM_GEOLOCATION, UNAUTHORIZED_RESPONSE } from '../../test/constants'; +import { mockHook, setUpTest } from '../../test/utils'; +import { ITEMS_ROUTE } from '../api/routes'; +import { + buildItemGeolocationKey, + itemsWithGeolocationKeys, +} from '../config/keys'; + +const { hooks, wrapper, queryClient } = setUpTest(); + +describe('useItemGeolocation', () => { + const response = ITEM_GEOLOCATION; + const itemId = response.item.id; + const route = `/${ITEMS_ROUTE}/${itemId}/geolocation`; + const hook = () => hooks.useItemGeolocation(response.item.id); + const key = buildItemGeolocationKey(itemId); + const endpoints = [{ route, response }]; + + it(`Retrieve geolocation of item`, async () => { + const { data, isSuccess } = await mockHook({ endpoints, hook, wrapper }); + + expect(isSuccess).toBeTruthy(); + expect(data).toEqual(response); + + // verify cache keys + expect(queryClient.getQueryData(key)).toMatchObject(response); + }); + + it(`Undefined id does not fetch`, async () => { + const { data, isFetched } = await mockHook({ + endpoints, + hook, + wrapper, + enabled: false, + }); + + expect(isFetched).toBeFalsy(); + expect(data).toBeFalsy(); + + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); + + it(`Unauthorized`, async () => { + const { data, isError } = await mockHook({ + hook, + wrapper, + endpoints: [ + { + route, + response: UNAUTHORIZED_RESPONSE, + statusCode: StatusCodes.UNAUTHORIZED, + }, + ], + }); + + expect(data).toBeFalsy(); + expect(isError).toBeTruthy(); + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); +}); + +describe('useItemsInMap', () => { + const response = [ITEM_GEOLOCATION]; + const values = { lat1: 1, lat2: 1, lng1: 1, lng2: 1 }; + const route = `/${ITEMS_ROUTE}/geolocation?lat1=1&lat2=1&lng1=1&lng2=1`; + const hook = () => hooks.useItemsInMap(values); + const key = itemsWithGeolocationKeys.inBounds(values); + const endpoints = [{ route, response }]; + + it(`Retrieve geolocation of item`, async () => { + const { data, isSuccess } = await mockHook({ endpoints, hook, wrapper }); + + expect(isSuccess).toBeTruthy(); + expect(data).toEqual(response); + + // verify cache keys + expect(queryClient.getQueryData(key)).toMatchObject(response); + }); + + it(`Retrieve geolocation for lat1=0`, async () => { + const valuesAndLat1IsZero = { ...values, lat1: 0 }; + const { data, isSuccess } = await mockHook({ + endpoints: [ + { + route: `/${ITEMS_ROUTE}/geolocation?lat1=0&lat2=1&lng1=1&lng2=1`, + response, + }, + ], + hook: () => hooks.useItemsInMap(valuesAndLat1IsZero), + wrapper, + }); + + expect(isSuccess).toBeTruthy(); + expect(data).toEqual(response); + + // verify cache keys + expect( + queryClient.getQueryData( + itemsWithGeolocationKeys.inBounds(valuesAndLat1IsZero), + ), + ).toMatchObject(response); + }); + + it(`Undefined lat1 does not fetch`, async () => { + const { data, isFetched } = await mockHook({ + endpoints: [ + { route: `/${ITEMS_ROUTE}/geolocation?lat2=1&lng1=1&lng2=1`, response }, + ], + hook: () => hooks.useItemsInMap({ ...values, lat1: undefined! }), + wrapper, + enabled: false, + }); + + expect(isFetched).toBeFalsy(); + expect(data).toBeFalsy(); + + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); + + it(`Undefined lat2 does not fetch`, async () => { + const { data, isFetched } = await mockHook({ + endpoints: [ + { route: `/${ITEMS_ROUTE}/geolocation?lat1=1&lng1=1&lng2=1`, response }, + ], + hook: () => hooks.useItemsInMap({ ...values, lat2: undefined! }), + wrapper, + enabled: false, + }); + + expect(isFetched).toBeFalsy(); + expect(data).toBeFalsy(); + + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); + + it(`Undefined lng1 does not fetch`, async () => { + const { data, isFetched } = await mockHook({ + endpoints: [ + { route: `/${ITEMS_ROUTE}/geolocation?lat1=1&lat2=1&lng2=1`, response }, + ], + hook: () => hooks.useItemsInMap({ ...values, lng1: undefined! }), + wrapper, + enabled: false, + }); + + expect(isFetched).toBeFalsy(); + expect(data).toBeFalsy(); + + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); + + it(`Undefined lng2 does not fetch`, async () => { + const { data, isFetched } = await mockHook({ + endpoints: [ + { route: `/${ITEMS_ROUTE}/geolocation?lat1=1&lat2=1&lng1=1`, response }, + ], + hook: () => hooks.useItemsInMap({ ...values, lng2: undefined! }), + wrapper, + enabled: false, + }); + + expect(isFetched).toBeFalsy(); + expect(data).toBeFalsy(); + + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); + + it(`Unauthorized`, async () => { + const { data, isError } = await mockHook({ + hook, + wrapper, + endpoints: [ + { + route, + response: UNAUTHORIZED_RESPONSE, + statusCode: StatusCodes.UNAUTHORIZED, + }, + ], + }); + + expect(data).toBeFalsy(); + expect(isError).toBeTruthy(); + // verify cache keys + expect(queryClient.getQueryData(key)).toBeFalsy(); + }); +}); diff --git a/src/hooks/itemGeolocation.ts b/src/hooks/itemGeolocation.ts index d491cf28..7ca36f25 100644 --- a/src/hooks/itemGeolocation.ts +++ b/src/hooks/itemGeolocation.ts @@ -1,4 +1,4 @@ -import { UUID } from '@graasp/sdk'; +import { Item, ItemGeolocation } from '@graasp/sdk'; import { useQuery } from 'react-query'; @@ -14,7 +14,7 @@ import { QueryClientConfig } from '../types'; export default (queryConfig: QueryClientConfig) => { const { notifier, defaultQueryOptions } = queryConfig; - const useItemGeolocation = (id?: UUID) => + const useItemGeolocation = (id?: Item['id']) => useQuery({ queryKey: buildItemGeolocationKey(id), queryFn: () => { @@ -39,24 +39,32 @@ export default (queryConfig: QueryClientConfig) => { lng1, lng2, }: { - lat1: number; - lat2: number; - lng1: number; - lng2: number; - }) => - useQuery({ - queryKey: itemsWithGeolocationKeys.single({ lat1, lat2, lng1, lng2 }), + lat1: ItemGeolocation['lat']; + lat2: ItemGeolocation['lat']; + lng1: ItemGeolocation['lng']; + lng2: ItemGeolocation['lng']; + }) => { + const enabled = Boolean( + (lat1 || lat1 === 0) && + (lat2 || lat2 === 0) && + (lng1 || lng1 === 0) && + (lng2 || lng2 === 0), + ); + + return useQuery({ + queryKey: itemsWithGeolocationKeys.inBounds({ lat1, lat2, lng1, lng2 }), queryFn: () => { - if (!lat1 || !lat2 || !lng1 || !lng2) { + if (!enabled) { throw new UndefinedArgument({ lat1, lat2, lng1, lng2 }); } return Api.getItemsInMap({ lat1, lat2, lng1, lng2 }, queryConfig); }, // question: cat lat or lng be 0? - enabled: Boolean(lat1 && lat2 && lng1 && lng2), + enabled, ...defaultQueryOptions, }); + }; return { useItemGeolocation, useItemsInMap }; }; diff --git a/src/mutations/item.ts b/src/mutations/item.ts index 1824970e..00f6816c 100644 --- a/src/mutations/item.ts +++ b/src/mutations/item.ts @@ -120,7 +120,7 @@ export default (queryConfig: QueryClientConfig) => { // if item has geoloc, invalidate map related keys if (lat || lng) { - queryClient.invalidateQueries(itemsWithGeolocationKeys.all); + queryClient.invalidateQueries(itemsWithGeolocationKeys.allBounds); } }, }, diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index f8f1c506..0413bb2d 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -1,9 +1,12 @@ -import { ItemGeolocation, UUID } from '@graasp/sdk'; +import { Item, ItemGeolocation } from '@graasp/sdk'; import { useMutation, useQueryClient } from 'react-query'; import { deleteItemGeolocation, putItemGeolocation } from '../api'; -import { buildItemGeolocationKey } from '../config/keys'; +import { + buildItemGeolocationKey, + itemsWithGeolocationKeys, +} from '../config/keys'; import { deleteItemGeolocationRoutine, putItemGeolocationRoutine, @@ -14,8 +17,9 @@ export default (queryConfig: QueryClientConfig) => { const usePutItemGeolocation = () => { const queryClient = useQueryClient(); return useMutation( - (payload: { itemId: UUID; lat: number; lng: number }) => - putItemGeolocation(payload, queryConfig), + ( + payload: { itemId: Item['id'] } & Pick, + ) => putItemGeolocation(payload, queryConfig), { onSuccess: () => { queryConfig.notifier?.({ @@ -30,6 +34,7 @@ export default (queryConfig: QueryClientConfig) => { }, onSettled: (_data, _error, { itemId }) => { queryClient.invalidateQueries(buildItemGeolocationKey(itemId)); + queryClient.invalidateQueries(itemsWithGeolocationKeys.allBounds); }, }, ); @@ -38,32 +43,15 @@ export default (queryConfig: QueryClientConfig) => { const useDeleteItemGeolocation = () => { const queryClient = useQueryClient(); return useMutation( - (payload: { itemId: UUID }) => + (payload: { itemId: Item['id'] }) => deleteItemGeolocation(payload, queryConfig), { - onMutate: async ({ id, itemId }: { id: UUID; itemId: UUID }) => { - const key = buildItemGeolocationKey(itemId); - - const prevValue = queryClient.getQueryData(key); - - // remove ItemGeolocation from list - if (prevValue) { - queryClient.setQueryData( - key, - prevValue.filter(({ id: iId }) => iId !== id), - ); - return { prevValue }; - } - return {}; - }, onSuccess: () => { queryConfig.notifier?.({ type: deleteItemGeolocationRoutine.SUCCESS, }); }, - onError: (error: Error, { itemId }, context) => { - const key = buildItemGeolocationKey(itemId); - queryClient.setQueryData(key, context?.prevValue); + onError: (error: Error) => { queryConfig.notifier?.({ type: deleteItemGeolocationRoutine.FAILURE, payload: { error }, @@ -71,6 +59,7 @@ export default (queryConfig: QueryClientConfig) => { }, onSettled: (_data, _error, { itemId }) => { queryClient.invalidateQueries(buildItemGeolocationKey(itemId)); + queryClient.invalidateQueries(itemsWithGeolocationKeys.allBounds); }, }, ); diff --git a/test/constants.ts b/test/constants.ts index d77b8718..31aefcb7 100644 --- a/test/constants.ts +++ b/test/constants.ts @@ -14,9 +14,10 @@ import { FolderItemType, HttpMethod, Invitation, + ItemBookmark, ItemCategory, - ItemFavorite, ItemFlag, + ItemGeolocation, ItemLike, ItemLoginSchema, ItemLoginSchemaType, @@ -215,7 +216,7 @@ export const RECYCLED_ITEM_DATA: RecycledItemData[] = [ }, ]; -export const FAVORITE_ITEM: ItemFavorite[] = [ +export const FAVORITE_ITEM: ItemBookmark[] = [ { id: `favorite-item-id`, item: ITEM_1, @@ -661,3 +662,13 @@ export const ITEM_PUBLISHED_DATA: ItemPublished = { createdAt: '2023-09-06T11:50:32.894Z', totalViews: 1, }; + +export const ITEM_GEOLOCATION: ItemGeolocation = { + id: 'item-published-id', + item: ITEM_1, + lat: 1, + lng: 1, + country: 'DE', + createdAt: '2023-09-06T11:50:32.894Z', + updatedAt: '2023-09-06T11:50:32.894Z', +}; diff --git a/yarn.lock b/yarn.lock index 93073f04..b2a19764 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1845,13 +1845,13 @@ __metadata: "@graasp/sdk@github:graasp/graasp-sdk#item-geolocation": version: 3.5.0 - resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=7f9b1c7e88c8f22fa9beeb871d3a577c110cc0eb" + resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=c2a2b310ec7989f2b7358521d04c563a155a8c91" dependencies: date-fns: "npm:3.3.1" js-cookie: "npm:3.0.5" uuid: "npm:9.0.1" validator: "npm:13.11.0" - checksum: 95a6309e11f70364c0f5ccb46317e16a3ebc8bf4766aaf0cac092e59b6f74b935253a9d1b62ed254879f62e64cb093b081935b553f71e61c9ab9823ca9ddb01c + checksum: c865daa5256c9e0946fcc0b096cb0bd9d2e0c69374f3e9ef4a18de5d45b717028d12e83694a5254c42febe052ff8467427aa1b890187cc01ffaf8d102627fa3d languageName: node linkType: hard From 0fc1baaedce1a147e63e760c3c8c98d6d2710d2a Mon Sep 17 00:00:00 2001 From: kim Date: Mon, 29 Jan 2024 11:54:27 +0100 Subject: [PATCH 13/14] refactor: add tests --- package.json | 5 +- src/mutations/item.test.ts | 51 +++++++ src/mutations/item.ts | 1 + src/mutations/itemGeolocation.test.ts | 195 ++++++++++++++++++++++++++ src/mutations/itemGeolocation.ts | 12 +- yarn.lock | 24 ++-- 6 files changed, 272 insertions(+), 16 deletions(-) create mode 100644 src/mutations/itemGeolocation.test.ts diff --git a/package.json b/package.json index ab6ddd57..cc8e12ba 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,11 @@ "Alexandre Chau" ], "dependencies": { - "@graasp/sdk": "github:graasp/graasp-sdk#item-geolocation", - "@graasp/translations": "1.22.1", + "@graasp/sdk": "3.6.0", + "@graasp/translations": "github:graasp/graasp-translations#geolocation", "axios": "0.27.2", "crypto-js": "4.2.0", + "date-fns": "3.3.1", "http-status-codes": "2.3.0", "qs": "6.11.2", "react-query": "3.39.3", diff --git a/src/mutations/item.test.ts b/src/mutations/item.test.ts index 3fbee9c3..84d338c6 100644 --- a/src/mutations/item.test.ts +++ b/src/mutations/item.test.ts @@ -15,6 +15,7 @@ import nock from 'nock'; import { ITEMS, + ITEM_GEOLOCATION, OK_RESPONSE, RECYCLED_ITEM_DATA, THUMBNAIL_BLOB_RESPONSE, @@ -44,6 +45,7 @@ import { buildItemKey, buildItemThumbnailKey, getKeyForParentId, + itemsWithGeolocationKeys, } from '../config/keys'; import { uploadFileRoutine, uploadItemThumbnailRoutine } from '../routines'; import { @@ -137,6 +139,55 @@ describe('Items Mutations', () => { ).toBeTruthy(); }); + it('Post item with geoloc', async () => { + const parentItem = ITEMS[1]!; + const singleKey = itemsWithGeolocationKeys.inBounds({ + lat1: 1, + lat2: 2, + lng1: 1, + lng2: 2, + }); + const response = { + ...newItem, + id: 'someid', + path: buildPath({ prefix: parentItem.path, ids: ['someid'] }), + }; + + // set default data + queryClient.setQueryData(getKeyForParentId(parentItem.id), [ITEMS[2]]); + queryClient.setQueryData(singleKey, [ITEM_GEOLOCATION]); + + const endpoints = [ + { + response, + method: HttpMethod.POST, + route: `/${buildPostItemRoute(parentItem.id)}`, + }, + ]; + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ + ...newItem, + parentId: parentItem.id, + lat: 1, + lng: 1, + }); + await waitForMutation(); + }); + + expect( + queryClient.getQueryState(getKeyForParentId(parentItem.id)) + ?.isInvalidated, + ).toBeTruthy(); + expect(queryClient.getQueryState(singleKey)?.isInvalidated).toBeTruthy(); + }); + it('Unauthorized', async () => { const route = `/${buildPostItemRoute()}`; queryClient.setQueryData(accessibleItemsKeys.all, [ITEMS[1]]); diff --git a/src/mutations/item.ts b/src/mutations/item.ts index 00f6816c..a7e13c26 100644 --- a/src/mutations/item.ts +++ b/src/mutations/item.ts @@ -115,6 +115,7 @@ export default (queryConfig: QueryClientConfig) => { notifier?.({ type: createItemRoutine.FAILURE, payload: { error } }); }, onSettled: (_data, _error, { lat, lng, parentId }) => { + console.log(lat, lng); const key = getKeyForParentId(parentId); queryClient.invalidateQueries(key); diff --git a/src/mutations/itemGeolocation.test.ts b/src/mutations/itemGeolocation.test.ts new file mode 100644 index 00000000..c4820a91 --- /dev/null +++ b/src/mutations/itemGeolocation.test.ts @@ -0,0 +1,195 @@ +import { HttpMethod } from '@graasp/sdk'; +import { SUCCESS_MESSAGES } from '@graasp/translations'; + +import { StatusCodes } from 'http-status-codes'; +import nock from 'nock'; +import { act } from 'react-test-renderer'; +import { v4 } from 'uuid'; + +import { ITEM_GEOLOCATION, UNAUTHORIZED_RESPONSE } from '../../test/constants'; +import { mockMutation, setUpTest, waitForMutation } from '../../test/utils'; +import { buildPutItemGeolocationRoute } from '../api/routes'; +import { + buildItemGeolocationKey, + itemsWithGeolocationKeys, +} from '../config/keys'; +import { + deleteItemGeolocationRoutine, + putItemGeolocationRoutine, +} from '../routines'; + +const mockedNotifier = jest.fn(); +const { wrapper, queryClient, mutations } = setUpTest({ + notifier: mockedNotifier, +}); + +describe('Item Flag Mutations', () => { + afterEach(() => { + queryClient.clear(); + nock.cleanAll(); + }); + + describe('usePutItemGeolocation', () => { + const itemId = v4(); + const key = buildItemGeolocationKey(itemId); + const route = `/${buildPutItemGeolocationRoute(itemId)}`; + const mutation = mutations.usePutItemGeolocation; + const singleKey = itemsWithGeolocationKeys.inBounds({ + lat1: 0, + lng1: 0, + lat2: 1, + lng2: 1, + }); + + it('Put item geolocation', async () => { + // set some starting data + queryClient.setQueryData(key, ITEM_GEOLOCATION); + queryClient.setQueryData(singleKey, [ITEM_GEOLOCATION]); + + const response = {}; + + const endpoints = [ + { + response, + method: HttpMethod.PUT, + route, + }, + ]; + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ lat: 1, lng: 1, itemId }); + await waitForMutation(); + }); + + expect(queryClient.getQueryState(key)?.isInvalidated).toBeTruthy(); + expect(queryClient.getQueryState(singleKey)?.isInvalidated).toBeTruthy(); + expect(mockedNotifier).toHaveBeenCalledWith({ + type: putItemGeolocationRoutine.SUCCESS, + payload: { message: SUCCESS_MESSAGES.PUT_ITEM_GEOLOCATION }, + }); + }); + + it('Unauthorized to put item geolocation', async () => { + // set some starting data + queryClient.setQueryData(key, ITEM_GEOLOCATION); + queryClient.setQueryData(singleKey, [ITEM_GEOLOCATION]); + + const endpoints = [ + { + response: UNAUTHORIZED_RESPONSE, + statusCode: StatusCodes.UNAUTHORIZED, + method: HttpMethod.PUT, + route, + }, + ]; + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ lat: 1, lng: 1, itemId }); + await waitForMutation(); + }); + + expect(queryClient.getQueryState(key)?.isInvalidated).toBeTruthy(); + expect(queryClient.getQueryState(singleKey)?.isInvalidated).toBeTruthy(); + expect(mockedNotifier).toHaveBeenCalledWith( + expect.objectContaining({ + type: putItemGeolocationRoutine.FAILURE, + payload: { error: expect.anything() }, + }), + ); + }); + }); + + describe('useDeleteItemGeolocation', () => { + const itemId = v4(); + const key = buildItemGeolocationKey(itemId); + const route = `/${buildPutItemGeolocationRoute(itemId)}`; + const mutation = mutations.useDeleteItemGeolocation; + const singleKey = itemsWithGeolocationKeys.inBounds({ + lat1: 0, + lng1: 0, + lat2: 1, + lng2: 1, + }); + + it('Delete item geolocation', async () => { + // set some starting data + queryClient.setQueryData(key, ITEM_GEOLOCATION); + queryClient.setQueryData(singleKey, [ITEM_GEOLOCATION]); + + const response = {}; + + const endpoints = [ + { + response, + method: HttpMethod.DELETE, + route, + }, + ]; + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ itemId }); + await waitForMutation(); + }); + + expect(queryClient.getQueryState(key)?.isInvalidated).toBeTruthy(); + expect(queryClient.getQueryState(singleKey)?.isInvalidated).toBeTruthy(); + expect(mockedNotifier).toHaveBeenCalledWith({ + type: deleteItemGeolocationRoutine.SUCCESS, + payload: { message: SUCCESS_MESSAGES.DELETE_ITEM_GEOLOCATION }, + }); + }); + + it('Unauthorized to delete item geolocation', async () => { + // set some starting data + queryClient.setQueryData(key, ITEM_GEOLOCATION); + queryClient.setQueryData(singleKey, [ITEM_GEOLOCATION]); + + const endpoints = [ + { + response: UNAUTHORIZED_RESPONSE, + statusCode: StatusCodes.UNAUTHORIZED, + method: HttpMethod.DELETE, + route, + }, + ]; + + const mockedMutation = await mockMutation({ + endpoints, + mutation, + wrapper, + }); + + await act(async () => { + await mockedMutation.mutate({ itemId }); + await waitForMutation(); + }); + + expect(queryClient.getQueryState(key)?.isInvalidated).toBeTruthy(); + expect(queryClient.getQueryState(singleKey)?.isInvalidated).toBeTruthy(); + expect(mockedNotifier).toHaveBeenCalledWith( + expect.objectContaining({ + type: deleteItemGeolocationRoutine.FAILURE, + payload: { error: expect.anything() }, + }), + ); + }); + }); +}); diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index 0413bb2d..5c75f67a 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -1,4 +1,5 @@ -import { Item, ItemGeolocation } from '@graasp/sdk'; +import { DiscriminatedItem, ItemGeolocation } from '@graasp/sdk'; +import { SUCCESS_MESSAGES } from '@graasp/translations'; import { useMutation, useQueryClient } from 'react-query'; @@ -18,12 +19,16 @@ export default (queryConfig: QueryClientConfig) => { const queryClient = useQueryClient(); return useMutation( ( - payload: { itemId: Item['id'] } & Pick, + payload: { itemId: DiscriminatedItem['id'] } & Pick< + ItemGeolocation, + 'lat' | 'lng' + >, ) => putItemGeolocation(payload, queryConfig), { onSuccess: () => { queryConfig.notifier?.({ type: putItemGeolocationRoutine.SUCCESS, + payload: { message: SUCCESS_MESSAGES.PUT_ITEM_GEOLOCATION }, }); }, onError: (error: Error) => { @@ -43,12 +48,13 @@ export default (queryConfig: QueryClientConfig) => { const useDeleteItemGeolocation = () => { const queryClient = useQueryClient(); return useMutation( - (payload: { itemId: Item['id'] }) => + (payload: { itemId: DiscriminatedItem['id'] }) => deleteItemGeolocation(payload, queryConfig), { onSuccess: () => { queryConfig.notifier?.({ type: deleteItemGeolocationRoutine.SUCCESS, + payload: { message: SUCCESS_MESSAGES.DELETE_ITEM_GEOLOCATION }, }); }, onError: (error: Error) => { diff --git a/yarn.lock b/yarn.lock index b2a19764..6c1bc144 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1791,8 +1791,8 @@ __metadata: "@babel/preset-typescript": "npm:7.23.3" "@commitlint/cli": "npm:18.6.0" "@commitlint/config-conventional": "npm:18.6.0" - "@graasp/sdk": "github:graasp/graasp-sdk#item-geolocation" - "@graasp/translations": "npm:1.22.1" + "@graasp/sdk": "npm:3.6.0" + "@graasp/translations": "github:graasp/graasp-translations#geolocation" "@testing-library/dom": "npm:9.3.4" "@testing-library/jest-dom": "npm:6.3.0" "@testing-library/react": "npm:14.1.2" @@ -1812,6 +1812,7 @@ __metadata: axios: "npm:0.27.2" babel-jest: "npm:29.7.0" crypto-js: "npm:4.2.0" + date-fns: "npm:3.3.1" env-cmd: "npm:10.1.0" eslint: "npm:8.56.0" eslint-config-airbnb: "npm:19.0.4" @@ -1843,24 +1844,25 @@ __metadata: languageName: unknown linkType: soft -"@graasp/sdk@github:graasp/graasp-sdk#item-geolocation": - version: 3.5.0 - resolution: "@graasp/sdk@https://github.com/graasp/graasp-sdk.git#commit=c2a2b310ec7989f2b7358521d04c563a155a8c91" +"@graasp/sdk@npm:3.6.0": + version: 3.6.0 + resolution: "@graasp/sdk@npm:3.6.0" dependencies: - date-fns: "npm:3.3.1" js-cookie: "npm:3.0.5" - uuid: "npm:9.0.1" validator: "npm:13.11.0" - checksum: c865daa5256c9e0946fcc0b096cb0bd9d2e0c69374f3e9ef4a18de5d45b717028d12e83694a5254c42febe052ff8467427aa1b890187cc01ffaf8d102627fa3d + peerDependencies: + date-fns: ^3 + uuid: ^9 + checksum: df52e11764c727e17ce69d247805fd8a2d49ba291b84e00ca390f853f0b0eb7fc4a2153e7003044cb8f84edf9c2c9cae91b79a55d1e71e71c69317e40145ad12 languageName: node linkType: hard -"@graasp/translations@npm:1.22.1": +"@graasp/translations@github:graasp/graasp-translations#geolocation": version: 1.22.1 - resolution: "@graasp/translations@npm:1.22.1" + resolution: "@graasp/translations@https://github.com/graasp/graasp-translations.git#commit=38e9980b5d0450a1fe80b942aae798b20b01ab89" dependencies: i18next: "npm:23.7.16" - checksum: 29043007a9926ff54236101c41ceb92eead09d8ce99edf46943790171f3275fb2cc1acd552bfa605247c42b4866bbf1f83dd889da3f571d388eca5f2dd598e17 + checksum: d4499181f921aa08bbaf8c7f191d03d5b463c3d55ebb377ccd17edb6603643b239eb5d33af2e8476bca906f1ed5e3bc47271bbfedac1ba471fd393c0ecddb426 languageName: node linkType: hard From da1a0bad6f44ad01be98ce4e02e8d2c746df450c Mon Sep 17 00:00:00 2001 From: kim Date: Mon, 29 Jan 2024 14:15:36 +0100 Subject: [PATCH 14/14] refactor: fix PR requested reviews --- src/api/item.ts | 19 +++++++++++++------ src/api/itemGeolocation.ts | 7 +++++-- src/hooks/itemGeolocation.ts | 1 - src/mutations/item.test.ts | 3 +-- src/mutations/item.ts | 5 ++--- src/mutations/itemGeolocation.test.ts | 10 ++++++++-- src/mutations/itemGeolocation.ts | 10 ++++------ 7 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/api/item.ts b/src/api/item.ts index e3aa442e..27ca2bdd 100644 --- a/src/api/item.ts +++ b/src/api/item.ts @@ -75,16 +75,24 @@ export const getAccessibleItems = async ( .then(({ data }) => data), ); -type AllOrNothing = T | Partial>; export type PostItemPayloadType = Partial & Pick & { parentId?: UUID; - } & AllOrNothing>; + } & { + geolocation?: Pick; + }; -// payload = {name, type, description, extra, lat, lng} +// payload = {name, type, description, extra, geolocation} // querystring = {parentId} export const postItem = async ( - { name, type, description, extra, parentId, lat, lng }: PostItemPayloadType, + { + name, + type, + description, + extra, + parentId, + geolocation, + }: PostItemPayloadType, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => @@ -94,8 +102,7 @@ export const postItem = async ( type, description, extra, - lat, - lng, + geolocation, }) .then(({ data }) => data), ); diff --git a/src/api/itemGeolocation.ts b/src/api/itemGeolocation.ts index 753c9904..5b8ed059 100644 --- a/src/api/itemGeolocation.ts +++ b/src/api/itemGeolocation.ts @@ -19,14 +19,17 @@ export const getItemGeolocation = async ( .then(({ data }) => data); export const putItemGeolocation = async ( - payload: { itemId: Item['id'] } & Pick, + payload: { + itemId: Item['id']; + geolocation: Pick; + }, { API_HOST, axios }: PartialQueryConfigForApi, ) => verifyAuthentication(() => axios .put( `${API_HOST}/${buildPutItemGeolocationRoute(payload.itemId)}`, - { lat: payload.lat, lng: payload.lng }, + payload, ) .then(({ data }) => data), ); diff --git a/src/hooks/itemGeolocation.ts b/src/hooks/itemGeolocation.ts index 7ca36f25..7f4a8f94 100644 --- a/src/hooks/itemGeolocation.ts +++ b/src/hooks/itemGeolocation.ts @@ -60,7 +60,6 @@ export default (queryConfig: QueryClientConfig) => { return Api.getItemsInMap({ lat1, lat2, lng1, lng2 }, queryConfig); }, - // question: cat lat or lng be 0? enabled, ...defaultQueryOptions, }); diff --git a/src/mutations/item.test.ts b/src/mutations/item.test.ts index 84d338c6..f35468c4 100644 --- a/src/mutations/item.test.ts +++ b/src/mutations/item.test.ts @@ -175,8 +175,7 @@ describe('Items Mutations', () => { await mockedMutation.mutate({ ...newItem, parentId: parentItem.id, - lat: 1, - lng: 1, + geolocation: { lat: 1, lng: 1 }, }); await waitForMutation(); }); diff --git a/src/mutations/item.ts b/src/mutations/item.ts index a7e13c26..7c3f1e7e 100644 --- a/src/mutations/item.ts +++ b/src/mutations/item.ts @@ -114,13 +114,12 @@ export default (queryConfig: QueryClientConfig) => { onError: (error: Error) => { notifier?.({ type: createItemRoutine.FAILURE, payload: { error } }); }, - onSettled: (_data, _error, { lat, lng, parentId }) => { - console.log(lat, lng); + onSettled: (_data, _error, { geolocation, parentId }) => { const key = getKeyForParentId(parentId); queryClient.invalidateQueries(key); // if item has geoloc, invalidate map related keys - if (lat || lng) { + if (geolocation) { queryClient.invalidateQueries(itemsWithGeolocationKeys.allBounds); } }, diff --git a/src/mutations/itemGeolocation.test.ts b/src/mutations/itemGeolocation.test.ts index c4820a91..c2c89494 100644 --- a/src/mutations/itemGeolocation.test.ts +++ b/src/mutations/itemGeolocation.test.ts @@ -63,7 +63,10 @@ describe('Item Flag Mutations', () => { }); await act(async () => { - await mockedMutation.mutate({ lat: 1, lng: 1, itemId }); + await mockedMutation.mutate({ + geolocation: { lat: 1, lng: 1 }, + itemId, + }); await waitForMutation(); }); @@ -96,7 +99,10 @@ describe('Item Flag Mutations', () => { }); await act(async () => { - await mockedMutation.mutate({ lat: 1, lng: 1, itemId }); + await mockedMutation.mutate({ + geolocation: { lat: 1, lng: 1 }, + itemId, + }); await waitForMutation(); }); diff --git a/src/mutations/itemGeolocation.ts b/src/mutations/itemGeolocation.ts index 5c75f67a..f2ae0eed 100644 --- a/src/mutations/itemGeolocation.ts +++ b/src/mutations/itemGeolocation.ts @@ -18,12 +18,10 @@ export default (queryConfig: QueryClientConfig) => { const usePutItemGeolocation = () => { const queryClient = useQueryClient(); return useMutation( - ( - payload: { itemId: DiscriminatedItem['id'] } & Pick< - ItemGeolocation, - 'lat' | 'lng' - >, - ) => putItemGeolocation(payload, queryConfig), + (payload: { + itemId: DiscriminatedItem['id']; + geolocation: Pick; + }) => putItemGeolocation(payload, queryConfig), { onSuccess: () => { queryConfig.notifier?.({