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

fix: document service find many pagination #20178

Merged
merged 14 commits into from May 6, 2024
@@ -1,6 +1,6 @@
import { omit, pipe } from 'lodash/fp';

import { contentTypes, sanitize, errors } from '@strapi/utils';
import { contentTypes, sanitize, errors, pagination } from '@strapi/utils';
import type { Core, Modules, UID } from '@strapi/types';

import { buildDeepPopulate, getDeepPopulate, getDeepPopulateDraftCount } from './utils/populate';
Expand Down Expand Up @@ -82,23 +82,18 @@ const documentManager = ({ strapi }: { strapi: Core.Strapi }) => {
},

async findPage(opts: DocServiceParams<'findMany'>, uid: UID.CollectionType) {
// Pagination
const page = Number(opts?.page) || 1;
const pageSize = Number(opts?.pageSize) || 10;
const params = pagination.withDefaultPagination(opts || {}, {
maxLimit: 1000,
});

const [documents, total = 0] = await Promise.all([
strapi.documents(uid).findMany(opts),
strapi.documents(uid).count(opts),
strapi.documents(uid).findMany(params),
strapi.documents(uid).count(params),
]);

return {
results: documents,
pagination: {
page,
pageSize,
pageCount: Math.ceil(total! / pageSize),
total,
},
pagination: pagination.transformPagedPaginationInfo(params, total),
};
},

Expand Down
Expand Up @@ -27,8 +27,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: defaultLimit,
start: 0,
limit: defaultLimit,
});
});

Expand All @@ -38,8 +38,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: pagination.pageSize,
start: 0,
limit: pagination.pageSize,
});
});

Expand All @@ -48,8 +48,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: maxLimit,
start: 0,
limit: maxLimit,
});
});

Expand All @@ -58,8 +58,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: 1,
start: 0,
limit: 1,
});
});

Expand All @@ -68,8 +68,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: 1,
start: 0,
limit: 1,
});
});

Expand All @@ -78,8 +78,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: 1,
start: 0,
limit: 1,
});
});
});
Expand Down Expand Up @@ -161,8 +161,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: defaultLimit,
start: 0,
limit: defaultLimit,
});
});

Expand All @@ -172,8 +172,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: pagination.pageSize,
start: 0,
limit: pagination.pageSize,
});
});

Expand All @@ -182,8 +182,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: pagination.pageSize,
start: 0,
limit: pagination.pageSize,
});
});

Expand All @@ -192,8 +192,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: 1,
start: 0,
limit: 1,
});
});

Expand All @@ -202,8 +202,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: 1,
start: 0,
limit: 1,
});
});

Expand All @@ -212,8 +212,8 @@ describe('Pagination service', () => {
const paginationInfo = getPaginationInfo({ pagination });

expect(paginationInfo).toEqual({
page: 1,
pageSize: 1,
start: 0,
limit: 1,
});
});
});
Expand Down
10 changes: 8 additions & 2 deletions packages/core/core/src/core-api/service/collection-type.ts
@@ -1,6 +1,11 @@
import type { Core, Struct, Modules } from '@strapi/types';

import { getPaginationInfo, shouldCount, transformPaginationResponse } from './pagination';
import {
getPaginationInfo,
shouldCount,
isPagedPagination,
transformPaginationResponse,
} from './pagination';

import { CoreService } from './core-service';

Expand All @@ -22,6 +27,7 @@ export class CollectionTypeService
const fetchParams = this.getFetchParams(params);

const paginationInfo = getPaginationInfo(fetchParams);
const isPaged = isPagedPagination(fetchParams.pagination);

const results = await strapi.documents(uid).findMany({
...fetchParams,
Expand All @@ -37,7 +43,7 @@ export class CollectionTypeService

return {
results,
pagination: transformPaginationResponse(paginationInfo, count),
pagination: transformPaginationResponse(paginationInfo, count, isPaged),
};
}

Expand Down
85 changes: 19 additions & 66 deletions packages/core/core/src/core-api/service/pagination.ts
@@ -1,5 +1,6 @@
import { has, toNumber, isUndefined } from 'lodash/fp';
import { errors } from '@strapi/utils';
import { has, toNumber } from 'lodash/fp';

import { errors, pagination } from '@strapi/utils';

interface BasePaginationParams {
withCount?: boolean | 't' | '1' | 'true' | 'f' | '0' | 'false' | 0 | 1;
Expand Down Expand Up @@ -35,14 +36,8 @@ const getLimitConfigDefaults = () => ({
maxLimit: toNumber(strapi.config.get('api.rest.maxLimit')) || null,
});

/**
* Should maxLimit be used as the limit or not
*/
const shouldApplyMaxLimit = (
limit: number,
maxLimit: number | null,
{ isPagedPagination = false } = {}
) => (!isPagedPagination && limit === -1) || (maxLimit !== null && limit > maxLimit);
const isPagedPagination = (pagination?: PaginationParams): pagination is PagedPagination =>
has('page', pagination) || has('pageSize', pagination);

const shouldCount = (params: { pagination?: PaginationParams }) => {
if (has('pagination.withCount', params)) {
Expand Down Expand Up @@ -72,69 +67,27 @@ const shouldCount = (params: { pagination?: PaginationParams }) => {
return Boolean(strapi.config.get('api.rest.withCount', true));
};

const isOffsetPagination = (pagination?: PaginationParams): pagination is OffsetPagination =>
has('start', pagination) || has('limit', pagination);

const isPagedPagination = (pagination?: PaginationParams): pagination is PagedPagination =>
has('page', pagination) || has('pageSize', pagination);

const getPaginationInfo = (params: { pagination?: PaginationParams }): PaginationInfo => {
const { defaultLimit, maxLimit } = getLimitConfigDefaults();

const { pagination } = params;

const isPaged = isPagedPagination(pagination);
const isOffset = isOffsetPagination(pagination);

if (isOffset && isPaged) {
throw new errors.ValidationError(
'Invalid pagination parameters. Expected either start/limit or page/pageSize'
);
}

if (!isOffset && !isPaged) {
return {
page: 1,
pageSize: defaultLimit,
};
}
const { start, limit } = pagination.withDefaultPagination(params.pagination || {}, {
defaults: { offset: { limit: defaultLimit }, page: { pageSize: defaultLimit } },
maxLimit: maxLimit || -1,
});

if (isPagedPagination(pagination)) {
const pageSize = isUndefined(pagination.pageSize)
? defaultLimit
: Math.max(1, toNumber(pagination.pageSize));

return {
page: Math.max(1, toNumber(pagination.page || 1)),
pageSize:
typeof maxLimit === 'number' &&
shouldApplyMaxLimit(pageSize, maxLimit, { isPagedPagination: true })
? maxLimit
: Math.max(1, pageSize),
};
}

const limit = isUndefined(pagination.limit) ? defaultLimit : toNumber(pagination.limit);

return {
start: Math.max(0, toNumber(pagination.start || 0)),
limit: shouldApplyMaxLimit(limit, maxLimit) ? maxLimit || -1 : Math.max(1, limit),
};
return { start, limit };
};

const transformPaginationResponse = (paginationInfo: PaginationInfo, count: number) => {
if ('page' in paginationInfo) {
return {
...paginationInfo,
pageCount: Math.ceil(count / paginationInfo.pageSize),
total: count,
};
const transformPaginationResponse = (
paginationInfo: PaginationInfo,
count: number,
isPaged: boolean
) => {
if (isPaged) {
return pagination.transformPagedPaginationInfo(paginationInfo, count);
}

return {
...paginationInfo,
total: count,
};
return pagination.transformOffsetPaginationInfo(paginationInfo, count);
};

export { getPaginationInfo, transformPaginationResponse, shouldCount };
export { isPagedPagination, shouldCount, getPaginationInfo, transformPaginationResponse };