diff --git a/packages/core/email/admin/src/translations/ja.json b/packages/core/email/admin/src/translations/ja.json index 0967ef424b..e159c8de47 100644 --- a/packages/core/email/admin/src/translations/ja.json +++ b/packages/core/email/admin/src/translations/ja.json @@ -1 +1,22 @@ -{} +{ + "Settings.email.plugin.button.test-email": "メール送信のテスト", + "Settings.email.plugin.label.defaultFrom": "デフォルトの送信アドレス", + "Settings.email.plugin.label.defaultReplyTo": "デフォルトの返信アドレス", + "Settings.email.plugin.label.provider": "Eメール プロバイダ", + "Settings.email.plugin.label.testAddress": "メール送信のテストをする", + "Settings.email.plugin.notification.config.error": "Eメールの設定の取得に失敗しました", + "Settings.email.plugin.notification.data.loaded": "Eメールの設定データはすでに取得済みです", + "Settings.email.plugin.notification.test.error": "{to}へのテストメースの送信に失敗しました", + "Settings.email.plugin.notification.test.success": "テストメースの送信に成功しました、{to}のメールボックスを確認してください", + "Settings.email.plugin.placeholder.defaultFrom": "例: Strapi No-Reply ", + "Settings.email.plugin.placeholder.defaultReplyTo": "例: Strapi ", + "Settings.email.plugin.placeholder.testAddress": "例: developer@example.com", + "Settings.email.plugin.subTitle": "Eメールプラグインの設定のテスト", + "Settings.email.plugin.text.configuration": "このプラグインは{file}ファイルを用いて構成されました、このリンク {link} のドキュメントを確認してください。", + "Settings.email.plugin.title": "Eメールの設定", + "Settings.email.plugin.title.config": "構成", + "Settings.email.plugin.title.test": "テストメールの送信", + "SettingsNav.link.settings": "設定", + "SettingsNav.section-label": "Eメール プラグイン", + "components.Input.error.validation.email": "これは無効なEメールアドレスです" +} diff --git a/packages/core/strapi/lib/core-api/__tests__/service.test.js b/packages/core/strapi/lib/core-api/service/__tests__/index.test.js similarity index 90% rename from packages/core/strapi/lib/core-api/__tests__/service.test.js rename to packages/core/strapi/lib/core-api/service/__tests__/index.test.js index 8b32973a3f..fb0b176e80 100644 --- a/packages/core/strapi/lib/core-api/__tests__/service.test.js +++ b/packages/core/strapi/lib/core-api/service/__tests__/index.test.js @@ -1,25 +1,6 @@ 'use strict'; -const _ = require('lodash'); -const { createService } = require('../service'); - -const maxLimit = 50; -const defaultLimit = 20; - -// init global strapi -global.strapi = { - config: { - get(path, defaultValue) { - return _.get(this, path, defaultValue); - }, - api: { - rest: { - defaultLimit, - maxLimit, - }, - }, - }, -}; +const { createService } = require('../index'); describe('Default Service', () => { describe('Collection Type', () => { diff --git a/packages/core/strapi/lib/core-api/service/__tests__/pagination.test.js b/packages/core/strapi/lib/core-api/service/__tests__/pagination.test.js new file mode 100644 index 0000000000..3390a39d6f --- /dev/null +++ b/packages/core/strapi/lib/core-api/service/__tests__/pagination.test.js @@ -0,0 +1,275 @@ +'use strict'; + +const _ = require('lodash'); +const { getPaginationInfo } = require('../pagination'); + +const maxLimit = 50; +const defaultLimit = 20; + +describe('Pagination service', () => { + describe('With maxLimit set globally', () => { + beforeAll(() => { + global.strapi = { + config: { + get(path, defaultValue) { + return _.get(this, path, defaultValue); + }, + api: { + rest: { + defaultLimit, + maxLimit, + }, + }, + }, + }; + }); + + test('Uses default limit', () => { + const pagination = {}; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: defaultLimit, + }); + }); + + describe('Paged pagination', () => { + test('Uses specified pageSize', () => { + const pagination = { pageSize: 5 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: pagination.pageSize, + }); + }); + + test('Uses maxLimit as pageSize', () => { + const pagination = { pageSize: 999 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: maxLimit, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: 0 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -1 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -2 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: 1, + }); + }); + }); + + describe('Offset pagination', () => { + test('Uses specified limit', () => { + const pagination = { limit: 5 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: pagination.limit, + }); + }); + + test('Uses maxLimit as limit', () => { + const pagination = { limit: 999 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: maxLimit, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: 0 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses maxLimit as limit', () => { + const pagination = { limit: -1 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: maxLimit, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: -2 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: 1, + }); + }); + }); + }); + + // Setting global strapi api conf + + describe('With maxLimit undefined', () => { + beforeAll(() => { + global.strapi = { + config: { + get(path, defaultValue) { + return _.get(this, path, defaultValue); + }, + api: { + rest: { + defaultLimit, + maxLimit: undefined, + }, + }, + }, + }; + }); + + test('Uses default limit', () => { + const pagination = {}; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: defaultLimit, + }); + }); + + describe('Paged pagination', () => { + test('Uses specified pageSize', () => { + const pagination = { pageSize: 5 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: pagination.pageSize, + }); + }); + + test('Uses specified pageSize', () => { + const pagination = { pageSize: 999 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: pagination.pageSize, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: 0 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -1 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -2 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + page: 1, + pageSize: 1, + }); + }); + }); + + describe('Offset pagination', () => { + test('Uses specified limit', () => { + const pagination = { limit: 5 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: pagination.limit, + }); + }); + + test('Uses specified limit', () => { + const pagination = { limit: 999 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: pagination.limit, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: 0 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses -1 as limit', () => { + const pagination = { limit: -1 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: -1, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: -2 }; + const paginationInfo = getPaginationInfo({ pagination }); + + expect(paginationInfo).toEqual({ + start: 0, + limit: 1, + }); + }); + }); + }); +}); diff --git a/packages/core/strapi/lib/core-api/service/pagination.js b/packages/core/strapi/lib/core-api/service/pagination.js index a02e328f63..872f4b3596 100644 --- a/packages/core/strapi/lib/core-api/service/pagination.js +++ b/packages/core/strapi/lib/core-api/service/pagination.js @@ -13,18 +13,13 @@ const getLimitConfigDefaults = () => ({ }); /** - * if there is max limit set and limit exceeds this number, return configured max limit + * Should maxLimit be used as the limit or not * @param {number} limit - limit you want to cap * @param {number?} maxLimit - maxlimit used has capping - * @returns {number} + * @returns {boolean} */ -const applyMaxLimit = (limit, maxLimit) => { - if (maxLimit && (limit === -1 || limit > maxLimit)) { - return maxLimit; - } - - return limit; -}; +const shouldApplyMaxLimit = (limit, maxLimit = null, { isPagedPagination = false } = {}) => + (!isPagedPagination && limit === -1) || (maxLimit && limit > maxLimit); const shouldCount = params => { if (has('pagination.withCount', params)) { @@ -81,17 +76,17 @@ const getPaginationInfo = params => { return { page: Math.max(1, toNumber(pagination.page || 1)), - pageSize: applyMaxLimit(pageSize, maxLimit), + pageSize: shouldApplyMaxLimit(pageSize, maxLimit, { isPagedPagination: true }) + ? maxLimit + : Math.max(1, pageSize), }; } - const limit = isUndefined(pagination.limit) - ? defaultLimit - : Math.max(1, toNumber(pagination.limit)); + const limit = isUndefined(pagination.limit) ? defaultLimit : toNumber(pagination.limit); return { start: Math.max(0, toNumber(pagination.start || 0)), - limit: applyMaxLimit(limit, maxLimit), + limit: shouldApplyMaxLimit(limit, maxLimit) ? maxLimit || -1 : Math.max(1, limit), }; }; diff --git a/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js b/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js index 5318d43827..d5ff644a2f 100644 --- a/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js +++ b/packages/core/strapi/lib/core/loaders/plugins/get-enabled-plugins.js @@ -5,7 +5,7 @@ const { statSync, existsSync } = require('fs'); const _ = require('lodash'); const { get, has, pick, pickBy, defaultsDeep, map, prop, pipe } = require('lodash/fp'); const { isKebabCase } = require('@strapi/utils'); -const loadConfigFile = require('../../app-configuration/load-config-file'); +const getUserPluginsConfig = require('./get-user-plugins-config'); const isStrapiPlugin = info => get('strapi.kind', info) === 'plugin'; const INTERNAL_PLUGINS = [ @@ -60,7 +60,12 @@ const getEnabledPlugins = async strapi => { const installedPlugins = {}; for (const dep in strapi.config.get('info.dependencies', {})) { const packagePath = join(dep, 'package.json'); - const packageInfo = require(packagePath); + let packageInfo; + try { + packageInfo = require(packagePath); + } catch { + continue; + } if (isStrapiPlugin(packageInfo)) { validatePluginName(packageInfo.strapi.name); @@ -72,10 +77,7 @@ const getEnabledPlugins = async strapi => { } const declaredPlugins = {}; - const userPluginConfigPath = join(strapi.dirs.config, 'plugins.js'); - const userPluginsConfig = existsSync(userPluginConfigPath) - ? loadConfigFile(userPluginConfigPath) - : {}; + const userPluginsConfig = await getUserPluginsConfig(); _.forEach(userPluginsConfig, (declaration, pluginName) => { validatePluginName(pluginName); diff --git a/packages/core/strapi/lib/core/loaders/plugins/get-user-plugins-config.js b/packages/core/strapi/lib/core/loaders/plugins/get-user-plugins-config.js new file mode 100644 index 0000000000..10a0231089 --- /dev/null +++ b/packages/core/strapi/lib/core/loaders/plugins/get-user-plugins-config.js @@ -0,0 +1,37 @@ +'use strict'; + +const { join } = require('path'); +const fse = require('fs-extra'); +const { merge } = require('lodash/fp'); +const loadConfigFile = require('../../app-configuration/load-config-file'); + +/** + * Return user defined plugins' config + * first load config from `config/plugins.js` + * and then merge config from `config/env/{env}/plugins.js` + * @return {Promise<{}>} + */ +const getUserPluginsConfig = async () => { + const globalUserConfigPath = join(strapi.dirs.config, 'plugins.js'); + const currentEnvUserConfigPath = join( + strapi.dirs.config, + 'env', + process.env.NODE_ENV, + 'plugins.js' + ); + let config = {}; + + // assign global user config if exists + if (await fse.pathExists(globalUserConfigPath)) { + config = loadConfigFile(globalUserConfigPath); + } + + // and merge user config by environment if exists + if (await fse.pathExists(currentEnvUserConfigPath)) { + config = merge(config, loadConfigFile(currentEnvUserConfigPath)); + } + + return config; +}; + +module.exports = getUserPluginsConfig; diff --git a/packages/core/strapi/lib/core/loaders/plugins/index.js b/packages/core/strapi/lib/core/loaders/plugins/index.js index 9154d595dd..982805a1ae 100644 --- a/packages/core/strapi/lib/core/loaders/plugins/index.js +++ b/packages/core/strapi/lib/core/loaders/plugins/index.js @@ -7,6 +7,7 @@ const { env } = require('@strapi/utils'); const loadConfigFile = require('../../app-configuration/load-config-file'); const loadFiles = require('../../../load/load-files'); const getEnabledPlugins = require('./get-enabled-plugins'); +const getUserPluginsConfig = require('./get-user-plugins-config'); const defaultPlugin = { bootstrap() {}, @@ -66,10 +67,7 @@ const formatContentTypes = plugins => { }; const applyUserConfig = async plugins => { - const userPluginConfigPath = join(strapi.dirs.config, 'plugins.js'); - const userPluginsConfig = (await fse.pathExists(userPluginConfigPath)) - ? loadConfigFile(userPluginConfigPath) - : {}; + const userPluginsConfig = await getUserPluginsConfig(); for (const pluginName in plugins) { const plugin = plugins[pluginName]; diff --git a/packages/core/upload/admin/src/components/AssetCard/AssetCard.js b/packages/core/upload/admin/src/components/AssetCard/AssetCard.js index 41ce38ace9..c83b85313c 100644 --- a/packages/core/upload/admin/src/components/AssetCard/AssetCard.js +++ b/packages/core/upload/admin/src/components/AssetCard/AssetCard.js @@ -27,7 +27,7 @@ export const AssetCard = ({ allowedTypes, asset, isSelected, onSelect, onEdit, s key={asset.id} name={asset.name} extension={getFileExtension(asset.ext)} - url={local ? asset.url : createAssetUrl(asset)} + url={local ? asset.url : createAssetUrl(asset, true)} mime={asset.mime} onEdit={onEdit ? () => onEdit(asset) : undefined} onSelect={handleSelect} diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.js b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.js index 551a2a08f5..85963c0903 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.js +++ b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.js @@ -41,7 +41,8 @@ export const PreviewBox = ({ }) => { const { trackUsage } = useTracking(); const previewRef = useRef(null); - const [assetUrl, setAssetUrl] = useState(createAssetUrl(asset)); + const [assetUrl, setAssetUrl] = useState(createAssetUrl(asset, false)); + const [thumbnailUrl, setThumbnailUrl] = useState(createAssetUrl(asset, true)); const { formatMessage } = useIntl(); const [showConfirmDialog, setShowConfirmDialog] = useState(false); const { @@ -73,6 +74,7 @@ export const PreviewBox = ({ asset.url = fileLocalUrl; } setAssetUrl(fileLocalUrl); + setThumbnailUrl(fileLocalUrl); } }, [replacementFile, asset]); @@ -83,16 +85,19 @@ export const PreviewBox = ({ // Making sure that when persisting the new asset, the URL changes with width and height // So that the browser makes a request and handle the image caching correctly at the good size let optimizedCachingImage; + let optimizedCachingThumbnailImage; if (asset.isLocal) { optimizedCachingImage = URL.createObjectURL(file); + optimizedCachingThumbnailImage = optimizedCachingImage; asset.url = optimizedCachingImage; asset.rawFile = file; trackUsage('didCropFile', { duplicatedFile: null, location: trackedLocation }); } else { const updatedAsset = await editAsset(nextAsset, file); - optimizedCachingImage = createAssetUrl(updatedAsset); + optimizedCachingImage = createAssetUrl(updatedAsset, false); + optimizedCachingThumbnailImage = createAssetUrl(updatedAsset, true); trackUsage('didCropFile', { duplicatedFile: false, location: trackedLocation }); } @@ -100,6 +105,7 @@ export const PreviewBox = ({ stopCropping(); onCropCancel(); setAssetUrl(optimizedCachingImage); + setThumbnailUrl(optimizedCachingThumbnailImage); }; const isInCroppingMode = isCropping && !isLoading; @@ -192,7 +198,7 @@ export const PreviewBox = ({ )} - + ', () => { fireEvent.click(screen.getByLabelText('Download')); expect(downloadFile).toHaveBeenCalledWith( - 'http://localhost:1337/uploads/thumbnail_Screenshot_2_5d4a574d61.png?updated_at=2021-10-04T09:42:31.670Z', + 'http://localhost:1337/uploads/Screenshot_2_5d4a574d61.png?updated_at=2021-10-04T09:42:31.670Z', 'Screenshot 2.png' ); }); diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.js b/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.js index daf1fc036f..76c9c02b59 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.js +++ b/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.js @@ -156,7 +156,7 @@ describe('', () => { fireEvent.click(screen.getByLabelText('Download')); expect(downloadFile).toHaveBeenCalledWith( - 'http://localhost:1337/uploads/thumbnail_Screenshot_2_5d4a574d61.png?updated_at=2021-10-04T09:42:31.670Z', + 'http://localhost:1337/uploads/Screenshot_2_5d4a574d61.png?updated_at=2021-10-04T09:42:31.670Z', 'Screenshot 2.png' ); }); diff --git a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js index 43d62e9f9b..b11b0dfc7b 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js +++ b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js @@ -25,7 +25,7 @@ export const CarouselAsset = ({ asset }) => { return ( @@ -39,7 +39,7 @@ export const CarouselAsset = ({ asset }) => { as="img" maxHeight="100%" maxWidth="100%" - src={createAssetUrl(asset)} + src={createAssetUrl(asset, true)} alt={asset.alternativeText || asset.name} /> ); diff --git a/packages/core/upload/admin/src/utils/createAssetUrl.js b/packages/core/upload/admin/src/utils/createAssetUrl.js index 5c878601c4..8560b80f11 100644 --- a/packages/core/upload/admin/src/utils/createAssetUrl.js +++ b/packages/core/upload/admin/src/utils/createAssetUrl.js @@ -1,11 +1,18 @@ import { prefixFileUrlWithBackendUrl } from '@strapi/helper-plugin'; -export const createAssetUrl = asset => { +/** + * Create image URL for asset + * @param {Object} asset + * @param {Boolean} forThumbnail - if true, return URL for thumbnail + * if there's no thumbnail, return the URL of the original image. + * @return {String} URL + */ +export const createAssetUrl = (asset, forThumbnail = true) => { if (asset.isLocal) { return asset.url; } - const assetUrl = asset?.formats?.thumbnail?.url || asset.url; + const assetUrl = forThumbnail ? asset?.formats?.thumbnail?.url || asset.url : asset.url; const backendUrl = prefixFileUrlWithBackendUrl(assetUrl); return `${backendUrl}?updated_at=${asset.updatedAt}`; diff --git a/packages/core/utils/lib/__tests__/pagination.test.js b/packages/core/utils/lib/__tests__/pagination.test.js new file mode 100644 index 0000000000..709ffff821 --- /dev/null +++ b/packages/core/utils/lib/__tests__/pagination.test.js @@ -0,0 +1,245 @@ +'use strict'; + +const { withDefaultPagination } = require('../pagination'); + +const defaultLimit = 20; +const defaults = { + offset: { limit: defaultLimit }, + page: { pageSize: defaultLimit }, +}; + +describe('Pagination util', () => { + describe('With maxLimit set', () => { + const maxLimit = 50; + + test('Uses default limit', () => { + const pagination = {}; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: defaultLimit, + }); + }); + + describe('Paged pagination', () => { + test('Uses specified pageSize', () => { + const pagination = { pageSize: 5 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: pagination.pageSize, + }); + }); + + test('Uses maxLimit as pageSize', () => { + const pagination = { pageSize: 999 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: maxLimit, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: 0 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -1 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -2 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + }); + + describe('Offset pagination', () => { + test('Uses specified limit', () => { + const pagination = { limit: 5 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: pagination.limit, + }); + }); + + test('Uses maxLimit as limit', () => { + const pagination = { limit: 999 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: maxLimit, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: 0 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses maxLimit as limit', () => { + const pagination = { limit: -1 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: maxLimit, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: -2 }; + const defaultPagination = withDefaultPagination(pagination, { defaults, maxLimit }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + }); + }); + + describe('With maxLimit undefined', () => { + test('Uses default limit', () => { + const pagination = {}; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: defaultLimit, + }); + }); + + describe('Paged pagination', () => { + test('Uses specified pageSize', () => { + const pagination = { pageSize: 5 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: pagination.pageSize, + }); + }); + + test('Uses specified pageSize', () => { + const pagination = { pageSize: 999 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: pagination.pageSize, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: 0 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -1 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses 1 as pageSize', () => { + const pagination = { pageSize: -2 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + }); + + describe('Offset pagination', () => { + test('Uses specified limit', () => { + const pagination = { limit: 5 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: pagination.limit, + }); + }); + + test('Uses apecified limit', () => { + const pagination = { limit: 999 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 999, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: 0 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + + test('Uses -1 as limit', () => { + const pagination = { limit: -1 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: -1, + }); + }); + + test('Uses 1 as limit', () => { + const pagination = { limit: -2 }; + const defaultPagination = withDefaultPagination(pagination, { defaults }); + + expect(defaultPagination).toEqual({ + start: 0, + limit: 1, + }); + }); + }); + }); +}); diff --git a/packages/core/utils/lib/convert-query-params.js b/packages/core/utils/lib/convert-query-params.js index 93da3c1626..6cf24f6121 100644 --- a/packages/core/utils/lib/convert-query-params.js +++ b/packages/core/utils/lib/convert-query-params.js @@ -110,6 +110,8 @@ const convertLimitQueryParams = limitQuery => { throw new Error(`convertLimitQueryParams expected a positive integer got ${limitAsANumber}`); } + if (limitAsANumber === -1) return null; + return limitAsANumber; }; diff --git a/packages/core/utils/lib/pagination.js b/packages/core/utils/lib/pagination.js index d8fde21494..52283e9022 100644 --- a/packages/core/utils/lib/pagination.js +++ b/packages/core/utils/lib/pagination.js @@ -27,7 +27,7 @@ const withMaxLimit = (limit, maxLimit = -1) => { // Ensure minimum page & pageSize values (page >= 1, pageSize >= 0, start >= 0, limit >= 0) const ensureMinValues = ({ start, limit }) => ({ start: Math.max(start, 0), - limit: Math.max(limit, 1), + limit: limit === -1 ? limit : Math.max(limit, 1), }); const ensureMaxValues = (maxLimit = -1) => ({ start, limit }) => ({ @@ -35,6 +35,12 @@ const ensureMaxValues = (maxLimit = -1) => ({ start, limit }) => ({ limit: withMaxLimit(limit, maxLimit), }); +// Apply maxLimit as the limit when limit is -1 +const withNoLimit = (pagination, maxLimit = -1) => ({ + ...pagination, + limit: pagination.limit === -1 ? maxLimit : pagination.limit, +}); + const withDefaultPagination = (args, { defaults = {}, maxLimit = -1 } = {}) => { const defaultValues = merge(STRAPI_DEFAULTS, defaults); @@ -67,7 +73,10 @@ const withDefaultPagination = (args, { defaults = {}, maxLimit = -1 } = {}) => { // Page / PageSize if (usePagePagination) { - const { page, pageSize } = merge(defaultValues.page, args); + const { page, pageSize } = merge(defaultValues.page, { + ...args, + pageSize: Math.max(1, args.pageSize), + }); Object.assign(pagination, { start: (page - 1) * pageSize, @@ -75,6 +84,9 @@ const withDefaultPagination = (args, { defaults = {}, maxLimit = -1 } = {}) => { }); } + // Handle -1 limit + Object.assign(pagination, withNoLimit(pagination, maxLimit)); + const replacePaginationAttributes = pipe( // Remove pagination attributes omit(paginationAttributes), diff --git a/packages/plugins/graphql/server/services/internals/types/response-collection-meta.js b/packages/plugins/graphql/server/services/internals/types/response-collection-meta.js index 828008aa2e..2a8beaec20 100644 --- a/packages/plugins/graphql/server/services/internals/types/response-collection-meta.js +++ b/packages/plugins/graphql/server/services/internals/types/response-collection-meta.js @@ -23,11 +23,12 @@ module.exports = ({ strapi }) => { async resolve(parent) { const { args, resourceUID } = parent; const { start, limit } = args; + const safeLimit = Math.max(limit, 1); const total = await strapi.entityService.count(resourceUID, args); - const pageSize = limit; - const pageCount = limit === 0 ? 0 : Math.ceil(total / limit); - const page = limit === 0 ? 1 : Math.floor(start / limit) + 1; + const pageSize = limit === -1 ? total - start : safeLimit; + const pageCount = limit === -1 ? safeLimit : Math.ceil(total / safeLimit); + const page = limit === -1 ? safeLimit : Math.floor(start / safeLimit) + 1; return { total, page, pageSize, pageCount }; },