Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add short links feature (graasp/graasp#664) #516

Merged
merged 9 commits into from
Nov 28, 2023
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"Alexandre Chau"
],
"dependencies": {
"@graasp/sdk": "3.1.0",
"@graasp/translations": "1.20.1",
"@graasp/sdk": "3.2.0",
"@graasp/translations": "1.21.0",
"axios": "0.27.2",
"crypto-js": "4.2.0",
"http-status-codes": "2.3.0",
Expand Down
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from './membership';
export * from './mentions';
export * from './search';
export * from './subscription';
export * from './shortLink';
17 changes: 17 additions & 0 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const CATEGORIES_ROUTE = `${ITEMS_ROUTE}/categories`;
export const ETHERPAD_ROUTE = `${ITEMS_ROUTE}/etherpad`;
export const COLLECTIONS_ROUTE = `collections`;
export const buildAppListRoute = `${APPS_ROUTE}/list`;
export const SHORT_LINKS_ROUTE = `${ITEMS_ROUTE}/short-links`;
export const SHORT_LINKS_LIST_ROUTE = `${SHORT_LINKS_ROUTE}/list`;

export const buildPostItemRoute = (parentId?: UUID) => {
let url = ITEMS_ROUTE;
Expand Down Expand Up @@ -396,6 +398,16 @@ export const buildGetEtherpadRoute = (itemId: UUID) =>

export const SEARCH_PUBLISHED_ITEMS_ROUTE = `${ITEMS_ROUTE}/${COLLECTIONS_ROUTE}/search`;

export const buildGetShortLinkAvailableRoute = (alias: string) =>
`${SHORT_LINKS_ROUTE}/available/${alias}`;
export const buildGetShortLinksItemRoute = (itemId: string) =>
`${SHORT_LINKS_LIST_ROUTE}/${itemId}`;
export const buildDeleteShortLinkRoute = (alias: string) =>
`${SHORT_LINKS_ROUTE}/${alias}`;
export const buildPostShortLinkRoute = () => `${SHORT_LINKS_ROUTE}`;
export const buildPatchShortLinkRoute = (alias: string) =>
`${SHORT_LINKS_ROUTE}/${alias}`;

export const API_ROUTES = {
APPS_ROUTE,
buildAppListRoute,
Expand All @@ -412,6 +424,7 @@ export const API_ROUTES = {
buildDeleteItemsRoute,
buildDeleteItemTagRoute,
buildDeleteMemberRoute,
buildDeleteShortLinkRoute,
buildDownloadAvatarRoute,
buildDownloadFilesRoute,
buildDownloadItemThumbnailRoute,
Expand Down Expand Up @@ -449,6 +462,8 @@ export const API_ROUTES = {
buildGetMostRecentPublishedItemsRoute,
buildGetPlanRoute,
buildGetPublishedItemsForMemberRoute,
buildGetShortLinkAvailableRoute,
buildGetShortLinksItemRoute,
buildImportH5PRoute,
buildImportZipRoute,
buildItemPublishRoute,
Expand All @@ -459,6 +474,7 @@ export const API_ROUTES = {
buildPatchInvitationRoute,
buildPatchItemChatMessageRoute,
buildPatchMember,
buildPatchShortLinkRoute,
buildPostEtherpadRoute,
buildPostInvitationsRoute,
buildPostItemAction,
Expand All @@ -472,6 +488,7 @@ export const API_ROUTES = {
buildPostItemTagRoute,
buildPostItemValidationRoute,
buildPostManyItemMembershipsRoute,
buildPostShortLinkRoute,
buildPutItemLoginSchemaRoute,
buildRecycleItemRoute,
buildRecycleItemsRoute,
Expand Down
73 changes: 73 additions & 0 deletions src/api/shortLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
ShortLink,
ShortLinkAvailable,
ShortLinkPatchPayload,
ShortLinkPostPayload,
} from '@graasp/sdk';

import { PartialQueryConfigForApi } from '../types';
import { verifyAuthentication } from './axios';
import {
buildDeleteShortLinkRoute,
buildGetShortLinkAvailableRoute,
buildGetShortLinksItemRoute,
buildPatchShortLinkRoute,
buildPostShortLinkRoute,
} from './routes';

export const getShortLinkAvailable = (
alias: string,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
verifyAuthentication(() =>
axios
.get<ShortLinkAvailable>(
`${API_HOST}/${buildGetShortLinkAvailableRoute(alias)}`,
)
.then(({ data }) => data),
);

export const getShortLinksItem = (
itemId: string,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
verifyAuthentication(() =>
axios
.get<ShortLink[]>(`${API_HOST}/${buildGetShortLinksItemRoute(itemId)}`)
.then(({ data }) => data),
);

export const deleteShortLink = (
alias: string,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
verifyAuthentication(() =>
axios
.delete<ShortLink>(`${API_HOST}/${buildDeleteShortLinkRoute(alias)}`)
.then(({ data }) => data),
);

export const postShortLink = async (
shortLink: ShortLinkPostPayload,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
verifyAuthentication(() =>
axios
.post<ShortLink>(`${API_HOST}/${buildPostShortLinkRoute()}`, {
...shortLink,
})
.then(({ data }) => data),
);

export const patchShortLink = (
alias: string,
updatedPayload: ShortLinkPatchPayload,
{ API_HOST, axios }: PartialQueryConfigForApi,
) =>
verifyAuthentication(() =>
axios
.patch<ShortLink>(`${API_HOST}/${buildPatchShortLinkRoute(alias)}`, {
...updatedPayload,
})
.then(({ data }) => data),
);
10 changes: 10 additions & 0 deletions src/config/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ import { DEFAULT_THUMBNAIL_SIZE } from './constants';

export const APPS_KEY = 'apps';
export const ITEMS_KEY = 'items';
export const SHORT_LINKS_KEY = 'shortLinks';
export const OWN_ITEMS_KEY = [ITEMS_KEY, 'own'];
export const ETHERPADS_KEY = 'etherpads';
export const SUBSCRIPTION_KEY = 'subscriptions';

export const buildShortLinkKey = (alias: string | undefined) => [
SHORT_LINKS_KEY,
alias,
];
export const buildShortLinksItemKey = (id: UUID) => [
SHORT_LINKS_KEY,
ITEMS_KEY,
id,
];
export const buildItemKey = (id?: UUID) => [ITEMS_KEY, id];
export const buildItemsKey = (ids: UUID[]) => [ITEMS_KEY, hashItemsIds(ids)];
export const buildItemChildrenKey = (id?: UUID) => [ITEMS_KEY, 'children', id];
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import configureMembershipHooks from './membership';
import configureMentionsHooks from './mention';
import configurePlanHooks from './plan';
import configureKeywordSearchHooks from './search';
import configureShortLinkHooks from './shortLink';

export default (
queryConfig: QueryClientConfig,
Expand Down Expand Up @@ -51,5 +52,6 @@ export default (
...configureInvitationHooks(queryConfig),
...memberHooks,
...configurePlanHooks(queryConfig),
...configureShortLinkHooks(queryConfig),
};
};
33 changes: 33 additions & 0 deletions src/hooks/shortLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useQuery } from 'react-query';

import * as Api from '../api';
import { UndefinedArgument } from '../config/errors';
import { buildShortLinkKey, buildShortLinksItemKey } from '../config/keys';
import { QueryClientConfig } from '../types';

export default (queryConfig: QueryClientConfig) => {
const { defaultQueryOptions } = queryConfig;

return {
useShortLinkAvailable: (alias: string | undefined) =>
useQuery({
queryKey: buildShortLinkKey(alias),
queryFn: () => {
if (!alias) {
throw new UndefinedArgument();
}
return Api.getShortLinkAvailable(alias, queryConfig);
},
enabled: Boolean(alias),
...defaultQueryOptions,
}),

useShortLinksItem: (itemId: string) =>
useQuery({
queryKey: buildShortLinksItemKey(itemId),
queryFn: () => Api.getShortLinksItem(itemId, queryConfig),
enabled: true,
...defaultQueryOptions,
}),
};
};
2 changes: 2 additions & 0 deletions src/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import memberMutations from './member';
import itemMembershipMutations from './membership';
import mentionMutations from './mention';
import subscriptionMutations from './plan';
import shortLinksMutations from './shortLink';

const configureMutations = (queryConfig: QueryClientConfig) => ({
...itemMutations(queryConfig),
Expand All @@ -37,6 +38,7 @@ const configureMutations = (queryConfig: QueryClientConfig) => ({
...authenticationMutations(queryConfig),
...subscriptionMutations(queryConfig),
...itemPublishMutations(queryConfig),
...shortLinksMutations(queryConfig),
});

export default configureMutations;
101 changes: 101 additions & 0 deletions src/mutations/shortLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ShortLinkPatchPayload, ShortLinkPostPayload } from '@graasp/sdk';
import { SUCCESS_MESSAGES } from '@graasp/translations';

import { useMutation, useQueryClient } from 'react-query';

import * as Api from '../api';
import { buildShortLinkKey, buildShortLinksItemKey } from '../config/keys';
import {
createShortLinkRoutine,
deleteShortLinkRoutine,
patchShortLinkRoutine,
} from '../routines';
import type { QueryClientConfig } from '../types';

export default (queryConfig: QueryClientConfig) => {
const { notifier } = queryConfig;

const usePostShortLink = () => {
const queryClient = useQueryClient();
return useMutation(
async (shortLink: ShortLinkPostPayload) =>
Api.postShortLink(shortLink, queryConfig),
{
onSuccess: (_data, variables) => {
notifier?.({
type: createShortLinkRoutine.SUCCESS,
payload: { message: SUCCESS_MESSAGES.CREATE_SHORT_LINK },
});
queryClient.invalidateQueries(
buildShortLinksItemKey(variables.itemId),
);
queryClient.invalidateQueries(buildShortLinkKey(variables.alias));
},
onError: (error: Error) => {
notifier?.({
type: createShortLinkRoutine.FAILURE,
payload: { error },
});
},
},
);
};

const usePatchShortLink = () => {
const queryClient = useQueryClient();
return useMutation(
async ({
alias,
shortLink,
}: {
alias: string;
shortLink: ShortLinkPatchPayload;
}) => Api.patchShortLink(alias, shortLink, queryConfig),
{
onSuccess: (data) => {
notifier?.({
type: patchShortLinkRoutine.SUCCESS,
payload: { message: SUCCESS_MESSAGES.EDIT_SHORT_LINK },
});
queryClient.invalidateQueries(buildShortLinksItemKey(data.item.id));
queryClient.invalidateQueries(buildShortLinkKey(data.alias));
},
onError: (error: Error) => {
notifier?.({
type: patchShortLinkRoutine.FAILURE,
payload: { error },
});
},
},
);
};

const useDeleteShortLink = () => {
const queryClient = useQueryClient();
return useMutation(
async (alias: string) => Api.deleteShortLink(alias, queryConfig),
{
onSuccess: (data) => {
notifier?.({
type: deleteShortLinkRoutine.SUCCESS,
payload: { message: SUCCESS_MESSAGES.DELETE_SHORT_LINK },
});
queryClient.invalidateQueries(buildShortLinksItemKey(data.item.id));
queryClient.invalidateQueries(buildShortLinkKey(data.alias));
},
onError: (error: Error) => {
notifier?.({
type: deleteShortLinkRoutine.FAILURE,
payload: { error },
});
},
},
);
};

return {
usePostShortLink,
usePatchShortLink,
useDeleteShortLink,
};
};
3 changes: 3 additions & 0 deletions src/routines/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ export const uploadItemThumbnailRoutine = createRoutine(
export const importZipRoutine = createRoutine('IMPORT_ZIP');
export const importH5PRoutine = createRoutine('IMPORT_H5P');
export const createEtherpadRoutine = createRoutine('CREATE_ETHERPAD');
export const createShortLinkRoutine = createRoutine('CREATE_SHORT_LINK');
export const patchShortLinkRoutine = createRoutine('PATCH_SHORT_LINK');
export const deleteShortLinkRoutine = createRoutine('DELETE_SHORT_LINK');
Loading