Skip to content

Commit

Permalink
Merge pull request #149 from graasp/146/publicDownloadZip
Browse files Browse the repository at this point in the history
refactor: add public export item
  • Loading branch information
pyphilia authored Mar 30, 2022
2 parents 0ef8be0 + b4dac11 commit 1a7a124
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 88 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@
"hooks:uninstall": "husky uninstall",
"hooks:install": "husky install",
"predeploy": "cd example && yarn install && yarn run build",
"test:watch": "jest --watchAll",
"test": "jest src/ --silent",
"test": "jest --silent",
"test:watch": "yarn test --watchAll",
"test:ci": "yarn && cd example && yarn && cd .. && yarn test",
"deploy": "gh-pages -d example/build",
"release": "standard-version",
Expand Down
25 changes: 19 additions & 6 deletions src/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const fallbackForArray = async (
return data;
};

export type FallbackToPublicOptions = {
public?: boolean;
fallbackData?: unknown;
};
/**
* Automatically send request depending on whether member is authenticated
* The function fallback to public depending on status code or authentication
Expand All @@ -62,26 +66,35 @@ const fallbackForArray = async (
export const fallbackToPublic = (
request: () => Promise<AxiosResponse>,
publicRequest: () => Promise<AxiosResponse>,
fallbackData?: unknown,
options?: FallbackToPublicOptions,
) => {
const isAuthenticated = isUserAuthenticated();
let isAuthenticated;

// if the call should be public, override isAuthenticated
if (options?.public) {
isAuthenticated = false;
} else {
isAuthenticated = isUserAuthenticated();
}

if (!isAuthenticated) {
return publicRequest()
.then(({ data }) => data)
.catch((e) => returnFallbackDataOrThrow(e, fallbackData));
.catch((e) => returnFallbackDataOrThrow(e, options?.fallbackData));
}

return request()
.then(({ data }) => fallbackForArray(data, publicRequest))
.catch((error) => {
if (FALLBACK_TO_PUBLIC_FOR_STATUS_CODES.includes(error.response.status)) {
if (
FALLBACK_TO_PUBLIC_FOR_STATUS_CODES.includes(error.response?.status)
) {
return publicRequest()
.then(({ data }) => data)
.catch((e) => returnFallbackDataOrThrow(e, fallbackData));
.catch((e) => returnFallbackDataOrThrow(e, options?.fallbackData));
}

return returnFallbackDataOrThrow(error, fallbackData);
return returnFallbackDataOrThrow(error, options?.fallbackData);
});
};

Expand Down
2 changes: 1 addition & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export * from './itemFlag';
export * from './chat';
export * from './category';
export * from './search';
export * from './itemDownload';
export * from './itemExport';
export * from './itemLike';
export * from './itemValidation';
15 changes: 0 additions & 15 deletions src/api/itemDownload.ts

This file was deleted.

27 changes: 27 additions & 0 deletions src/api/itemExport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { QueryClientConfig, UUID } from '../types';
import configureAxios, { fallbackToPublic } from './axios';
import { buildExportItemRoute, buildExportPublicItemRoute } from './routes';

const axios = configureAxios();

/* eslint-disable import/prefer-default-export */
export const exportItem = async (
id: UUID,
{ API_HOST }: QueryClientConfig,
options?: { public: boolean },
) =>
fallbackToPublic(
() =>
axios({
url: `${API_HOST}/${buildExportItemRoute(id)}`,
method: 'GET',
responseType: 'blob',
}),
() =>
axios({
url: `${API_HOST}/${buildExportPublicItemRoute(id)}`,
method: 'GET',
responseType: 'blob',
}),
options,
);
7 changes: 5 additions & 2 deletions src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ export const buildCopyPublicItemRoute = (id: UUID) =>
export const buildCopyItemsRoute = (ids: UUID[]) =>
`${ITEMS_ROUTE}/copy?${qs.stringify({ id: ids }, { arrayFormat: 'repeat' })}`;
export const buildEditItemRoute = (id: UUID) => `${ITEMS_ROUTE}/${id}`;
export const buildDownloadItemRoute = (id: UUID) =>
export const buildExportItemRoute = (id: UUID) =>
`${ITEMS_ROUTE}/zip-export/${id}`;
export const buildExportPublicItemRoute = (id: UUID) =>
`${PUBLIC_PREFIX}/${buildExportItemRoute(id)}`;
export const buildShareItemWithRoute = (id: UUID) =>
`item-memberships?itemId=${id}`;
export const buildGetItemMembershipsForItemsRoute = (ids: UUID[]) =>
Expand Down Expand Up @@ -282,7 +284,8 @@ export const API_ROUTES = {
buildCopyItemRoute,
buildCopyPublicItemRoute,
buildCopyItemsRoute,
buildDownloadItemRoute,
buildExportItemRoute,
buildExportPublicItemRoute,
buildPatchMember,
buildPostItemFlagRoute,
buildEditItemMembershipRoute,
Expand Down
4 changes: 2 additions & 2 deletions src/mutations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import itemMembershipMutations from './membership';
import chatMutations from './chat';
import itemCategoryMutations from './itemCategory';
import { QueryClientConfig } from '../types';
import itemDownloadMutations from './itemDownload';
import itemExportMutations from './itemExport';
import itemLikeMutations from './itemLike';
import itemValidationMutations from './itemValidation';

Expand All @@ -24,7 +24,7 @@ const configureMutations = (
itemLoginMutations(queryClient, queryConfig);
chatMutations(queryClient, queryConfig);
itemCategoryMutations(queryClient, queryConfig);
itemDownloadMutations(queryClient, queryConfig);
itemExportMutations(queryClient, queryConfig);
itemLikeMutations(queryClient, queryConfig);
itemValidationMutations(queryClient, queryConfig);
};
Expand Down
54 changes: 0 additions & 54 deletions src/mutations/itemDownload.test.ts

This file was deleted.

114 changes: 114 additions & 0 deletions src/mutations/itemExport.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable import/no-extraneous-dependencies */
import nock from 'nock';
import Cookies from 'js-cookie';
import { act } from 'react-test-renderer';
import { StatusCodes } from 'http-status-codes';
import { mockMutation, setUpTest, waitForMutation } from '../../test/utils';
import { REQUEST_METHODS } from '../api/utils';
import { MUTATION_KEYS } from '../config/keys';
import {
buildExportItemRoute,
buildExportPublicItemRoute,
} from '../api/routes';
import { exportItemRoutine } from '../routines';
import { UNAUTHORIZED_RESPONSE } from '../../test/constants';

const mockedNotifier = jest.fn();
const { wrapper, queryClient, useMutation } = setUpTest({
notifier: mockedNotifier,
});

jest.spyOn(Cookies, 'get').mockReturnValue({ session: 'somesession' });

describe('Export Zip', () => {
afterEach(() => {
queryClient.clear();
nock.cleanAll();
});

describe(MUTATION_KEYS.EXPORT_ZIP, () => {
const itemId = 'item-id';
const route = `/${buildExportItemRoute(itemId)}`;
const mutation = () => useMutation(MUTATION_KEYS.EXPORT_ZIP);

it('Export zip', async () => {
const endpoints = [
{
response: { id: 'id', content: 'content' },
method: REQUEST_METHODS.GET,
route,
},
];

const mockedMutation = await mockMutation({
endpoints,
mutation,
wrapper,
});

await act(async () => {
await mockedMutation.mutate({ id: itemId });
await waitForMutation();
});

expect(mockedNotifier).toHaveBeenCalledWith({
type: exportItemRoutine.SUCCESS,
});
});

it(`Fallback to public`, async () => {
const endpoints = [
{
response: UNAUTHORIZED_RESPONSE,
statusCode: StatusCodes.UNAUTHORIZED,
route,
},
{
response: { id: 'id', content: 'content' },
method: REQUEST_METHODS.GET,
route: `/${buildExportPublicItemRoute(itemId)}`,
},
];

const mockedMutation = await mockMutation({
endpoints,
mutation,
wrapper,
});

await act(async () => {
await mockedMutation.mutate({ id: itemId });
await waitForMutation();
});

expect(mockedNotifier).toHaveBeenCalledWith({
type: exportItemRoutine.SUCCESS,
});
});

it(`Fallback to public automatically`, async () => {
const endpoints = [
{
response: { id: 'id', content: 'public content' },
method: REQUEST_METHODS.GET,
route: `/${buildExportPublicItemRoute(itemId)}`,
},
];

const mockedMutation = await mockMutation({
endpoints,
mutation,
wrapper,
});

await act(async () => {
await mockedMutation.mutate({ id: itemId, options: { public: true } });
await waitForMutation();
});

expect(mockedNotifier).toHaveBeenCalledWith({
type: exportItemRoutine.SUCCESS,
});
});
});
});
11 changes: 7 additions & 4 deletions src/mutations/itemDownload.ts → src/mutations/itemExport.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { QueryClient } from 'react-query';
import * as Api from '../api';
import { MUTATION_KEYS } from '../config/keys';
import { downloadItemRoutine } from '../routines';
import { exportItemRoutine } from '../routines';
import { QueryClientConfig } from '../types';

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

/**
* @param options.public {boolean} force fallback to public endpoint
*/
queryClient.setMutationDefaults(MUTATION_KEYS.EXPORT_ZIP, {
mutationFn: (id) => Api.downloadItem(id, queryConfig).then((data) => data),
mutationFn: ({ id, options }) => Api.exportItem(id, queryConfig, options),
onSuccess: () => {
notifier?.({ type: downloadItemRoutine.SUCCESS });
notifier?.({ type: exportItemRoutine.SUCCESS });
},
onError: (error) => {
notifier?.({ type: downloadItemRoutine.FAILURE, payload: { error } });
notifier?.({ type: exportItemRoutine.FAILURE, payload: { error } });
},
});
};
2 changes: 1 addition & 1 deletion src/routines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export * from './itemLogin';
export * from './itemFlag';
export * from './chat';
export * from './itemCategory';
export * from './itemDownload';
export * from './itemExport';
export * from './itemLike';
export * from './itemValidation';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import createRoutine from './utils';

/* eslint-disable import/prefer-default-export */
export const downloadItemRoutine = createRoutine('EXPORT_ZIP');
export const exportItemRoutine = createRoutine('EXPORT_ZIP');

0 comments on commit 1a7a124

Please sign in to comment.