feat: handle history version pagination params (#19594)

* feat: handle history version pagination params

* fix: ts build error

* chore: validate pagination params

* chore: pr feedback and test
This commit is contained in:
Rémi de Juvigny 2024-03-07 14:43:37 +01:00 committed by GitHub
parent 741baba5b5
commit 6408670a35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 100 additions and 8 deletions

View File

@ -22,6 +22,7 @@ jest.mock('../../../utils', () => ({
cannot: {
read: jest.fn(() => false),
},
sanitizeQuery: jest.fn((query) => query),
})),
};
}
@ -29,6 +30,10 @@ jest.mock('../../../utils', () => ({
}));
describe('History version controller', () => {
beforeEach(() => {
mockFindVersionsPage.mockClear();
});
describe('findMany', () => {
it('should require contentType and documentId for collection types', () => {
const ctx = {
@ -135,4 +140,60 @@ describe('History version controller', () => {
expect(response.data.length).toBe(1);
expect(response.meta.pagination).toBeDefined();
});
it('applies pagination params', async () => {
const ctx = {
state: {
userAbility: {},
},
query: {
contentType: 'api::test.test',
},
};
const historyVersionController = createHistoryVersionController({
// @ts-expect-error - we're not mocking the entire strapi object
strapi: { getModel: jest.fn(() => ({ kind: 'singleType' })) },
});
/**
* Applies default pagination params
*/
mockFindVersionsPage.mockResolvedValueOnce({
results: [],
pagination: {
page: 1,
pageSize: 10,
},
});
// @ts-expect-error partial context
const mockResponse = await historyVersionController.findMany(ctx);
expect(mockFindVersionsPage).toHaveBeenCalledWith(
expect.objectContaining({
page: 1,
pageSize: 10,
})
);
expect(mockResponse.meta.pagination.page).toBe(1);
expect(mockResponse.meta.pagination.pageSize).toBe(10);
/**
* Prevents invalid pagination params
*/
mockFindVersionsPage.mockResolvedValueOnce({
results: [],
pagination: {},
});
// @ts-expect-error partial context
await historyVersionController.findMany({
...ctx,
query: { ...ctx.query, page: '-1', pageSize: '1000' },
});
expect(mockFindVersionsPage).toHaveBeenCalledWith(
expect.objectContaining({
page: 1,
pageSize: 10,
})
);
});
});

View File

@ -4,6 +4,33 @@ import { getService as getContentManagerService } from '../../utils';
import { getService } from '../utils';
import { HistoryVersions } from '../../../../shared/contracts';
/**
* Parses pagination params and makes sure they're within valid ranges
*/
const getValidPagination = ({ page, pageSize }: { page: any; pageSize: any }) => {
let pageNumber = 1;
let pageSizeNumber = 10;
if (page) {
const parsedPage = parseInt(page, 10);
pageNumber = parseInt(page, 10);
if (!Number.isNaN(parsedPage) && parsedPage >= 1) {
pageNumber = parsedPage;
}
}
if (pageSize) {
const parsedPageSize = parseInt(pageSize, 10);
if (!Number.isNaN(parsedPageSize) && parsedPageSize >= 1 && parsedPageSize <= 100) {
pageSizeNumber = parsedPageSize;
}
}
return { page: pageNumber, pageSize: pageSizeNumber };
};
const createHistoryVersionController = ({ strapi }: { strapi: Strapi }) => {
return {
async findMany(ctx) {
@ -18,22 +45,26 @@ const createHistoryVersionController = ({ strapi }: { strapi: Strapi }) => {
throw new errors.ForbiddenError('contentType and documentId are required');
}
const params = ctx.query as HistoryVersions.GetHistoryVersions.Request['query'];
/**
* There are no permissions specifically for history versions,
* but we need to check that the user can read the content type
*/
const permissionChecker = getContentManagerService('permission-checker').create({
userAbility: ctx.state.userAbility,
model: params.contentType,
model: ctx.query.contentType,
});
if (permissionChecker.cannot.read()) {
return ctx.forbidden();
}
const { results, pagination } = await getService(strapi, 'history').findVersionsPage(params);
const params: HistoryVersions.GetHistoryVersions.Request['query'] =
await permissionChecker.sanitizeQuery(ctx.query);
const { results, pagination } = await getService(strapi, 'history').findVersionsPage({
...params,
...getValidPagination({ page: params.page, pageSize: params.pageSize }),
});
return { data: results, meta: { pagination } };
},

View File

@ -151,8 +151,7 @@ const createHistoryService = ({ strapi }: { strapi: LoadedStrapi }) => {
async findVersionsPage(params: HistoryVersions.GetHistoryVersions.Request['query']) {
const [{ results, pagination }, localeDictionary] = await Promise.all([
query.findPage({
page: 1,
pageSize: 10,
...params,
where: {
$and: [
{ contentType: params.contentType },

View File

@ -33,7 +33,8 @@ export interface HistoryVersionDataResponse extends Omit<CreateHistoryVersion, '
locale: Locale | null;
}
interface Pagination {
// Export to prevent the TS "cannot be named" error in the history service
export interface Pagination {
page: number;
pageSize: number;
pageCount: number;
@ -52,7 +53,7 @@ export declare namespace GetHistoryVersions {
contentType: UID.ContentType;
documentId?: Entity.ID;
locale?: string;
};
} & Partial<Pick<Pagination, 'page' | 'pageSize'>>;
}
export interface Response {