Merge branch 'main' into enhancement/axios-migration-users-permissions

This commit is contained in:
Simone 2023-01-25 14:48:45 +01:00 committed by GitHub
commit 0d6e7936c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1686 additions and 1293 deletions

View File

@ -95,12 +95,12 @@ Strapi only supports maintenance and LTS versions of Node.js. Please refer to th
**Database:**
| Database | Minimum | Recommended |
| ---------- | ------- | ----------- |
| MySQL | 5.7.8 | 8.0 |
| MariaDB | 10.3 | 10.6 |
| PostgreSQL | 11.0 | 14.0 |
| SQLite | 3 | 3 |
| Database | Recommended | Minimum |
| ---------- | ----------- | ------- |
| MySQL | 8.0 | 5.7.8 |
| MariaDB | 10.6 | 10.3 |
| PostgreSQL | 14.0 | 11.0 |
| SQLite | 3 | 3 |
**We recommend always using the latest version of Strapi stable to start your new projects**.

View File

@ -4,7 +4,7 @@
"description": "Generate a new Strapi application.",
"dependencies": {
"@strapi/generate-new": "4.5.6",
"commander": "8.2.0",
"commander": "8.3.0",
"inquirer": "8.2.4"
},
"keywords": [

View File

@ -41,7 +41,7 @@
"@strapi/generate-new": "4.5.6",
"chalk": "4.1.1",
"ci-info": "3.5.0",
"commander": "8.2.0",
"commander": "8.3.0",
"execa": "5.1.1",
"fs-extra": "10.0.0",
"inquirer": "8.2.4",

View File

@ -1,15 +1,11 @@
import React, { useEffect } from 'react';
import React from 'react';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Flex } from '@strapi/design-system/Flex';
import { Box } from '@strapi/design-system/Box';
import { FocusTrap } from '@strapi/design-system/FocusTrap';
import { Portal } from '@strapi/design-system/Portal';
import { BaseButton } from '@strapi/design-system/BaseButton';
import { Typography } from '@strapi/design-system/Typography';
import { pxToRem } from '@strapi/helper-plugin';
import Collapse from '@strapi/icons/Collapse';
import { useIntl } from 'react-intl';
import { BaseButton, Box, Flex, FocusTrap, Portal, Typography } from '@strapi/design-system';
import { Collapse } from '@strapi/icons';
import { pxToRem, useLockScroll } from '@strapi/helper-plugin';
import PreviewWysiwyg from '../PreviewWysiwyg';
const setOpacity = (hex, alpha) =>
@ -32,6 +28,7 @@ export const ExpandButton = styled(BaseButton)`
svg {
margin-left: ${({ theme }) => `${theme.spaces[2]}`};
path {
fill: ${({ theme }) => theme.colors.neutral700};
width: ${12 / 16}rem;
@ -42,19 +39,8 @@ export const ExpandButton = styled(BaseButton)`
export const EditorLayout = ({ children, isExpandMode, error, previewContent, onCollapse }) => {
const { formatMessage } = useIntl();
useEffect(() => {
const body = document.body;
if (isExpandMode) {
body.classList.add('lock-body-scroll');
}
return () => {
if (isExpandMode) {
body.classList.remove('lock-body-scroll');
}
};
}, [isExpandMode]);
useLockScroll(isExpandMode);
if (isExpandMode) {
return (
@ -71,7 +57,6 @@ export const EditorLayout = ({ children, isExpandMode, error, previewContent, on
onClick={onCollapse}
>
<Box
id="wysiwyg-expand"
background="neutral0"
hasRadius
shadow="popupShadow"
@ -84,9 +69,15 @@ export const EditorLayout = ({ children, isExpandMode, error, previewContent, on
<BoxWithBorder flex="1" height="100%">
{children}
</BoxWithBorder>
<Box flex="1" height="100%">
<Flex height={pxToRem(48)} background="neutral100" justifyContent="flex-end">
<ExpandButton id="collapse" onClick={onCollapse}>
<Flex alignItems="start" direction="column" flex={1} height="100%" width="100%">
<Flex
height={pxToRem(48)}
background="neutral100"
justifyContent="flex-end"
shrink={0}
width="100%"
>
<ExpandButton onClick={onCollapse}>
<Typography>
{formatMessage({
id: 'components.Wysiwyg.collapse',
@ -97,10 +88,10 @@ export const EditorLayout = ({ children, isExpandMode, error, previewContent, on
</ExpandButton>
</Flex>
<Box position="relative" height="100%">
<Box position="relative" height="100%" width="100%">
<PreviewWysiwyg data={previewContent} />
</Box>
</Box>
</Flex>
</Flex>
</Box>
</ExpandWrapper>

View File

@ -1,5 +1,4 @@
import { createStore, applyMiddleware, compose } from 'redux';
import createReducer from './createReducer';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
const configureStore = (appMiddlewares, appReducers) => {
let composeEnhancers = compose;
@ -19,11 +18,30 @@ const configureStore = (appMiddlewares, appReducers) => {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
}
return createStore(
createReducer(appReducers),
const store = createStore(
createReducer(appReducers, {}),
{},
composeEnhancers(applyMiddleware(...middlewares))
);
// Add a dictionary to keep track of the registered async reducers
store.asyncReducers = {};
// Create an inject reducer function
// This function adds the async reducer, and creates a new combined reducer
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer;
store.replaceReducer(createReducer(appReducers, store.asyncReducers));
};
return store;
};
const createReducer = (appReducers, asyncReducers) => {
return combineReducers({
...appReducers,
...asyncReducers,
});
};
export default configureStore;

View File

@ -1,5 +0,0 @@
import { combineReducers } from 'redux';
const createReducer = (reducers) => combineReducers(reducers);
export default createReducer;

View File

@ -0,0 +1,69 @@
import configureStore from '../configureStore';
function middlewareFixture(callback) {
return () =>
({ getState }) =>
(next) =>
(action) => {
callback(getState());
return next(action);
};
}
function reducer(state = {}, action) {
switch (action.type) {
case 'something': {
return {
...state,
...action.payload,
};
}
default:
return state;
}
}
function asyncReducer(state = {}, action) {
switch (action.type) {
case 'async': {
return {
...state,
async: action.payload,
};
}
default:
return state;
}
}
describe('configureStore', () => {
test('applies middlewares and reducers', () => {
const spy = jest.fn();
const store = configureStore([middlewareFixture(spy)], [reducer]);
store.dispatch({ type: 'something', payload: { redux: true } });
store.dispatch({ type: 'something', payload: { redux: 1 } });
store.dispatch({ type: 'something', payload: { redux: 2 } });
expect(spy).toBeCalledTimes(3);
expect(spy).toHaveBeenNthCalledWith(1, { 0: {} });
expect(spy).toHaveBeenNthCalledWith(2, { 0: { redux: true } });
expect(spy).toHaveBeenNthCalledWith(3, { 0: { redux: 1 } });
});
test('adds injectReducer() method', () => {
const store = configureStore([middlewareFixture(() => {})], [reducer]);
expect(store.injectReducer).toBeDefined();
store.injectReducer('asyncReducer', asyncReducer);
store.dispatch({ type: 'async', payload: true });
expect(store.getState()).toStrictEqual({ 0: {}, asyncReducer: { async: true } });
});
});

View File

@ -0,0 +1,308 @@
'use strict';
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
let strapi;
describe('transactions', () => {
let original;
beforeAll(async () => {
strapi = await createStrapiInstance();
original = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
});
afterAll(async () => {
await strapi.destroy();
});
afterEach(async () => {
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: original[0].key,
})
.where({ id: 1 })
.execute();
});
describe('using a transaction method', () => {
test('commits successfully', async () => {
await strapi.db.transaction(async () => {
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'wrong key',
})
.where({ id: 1 })
.execute();
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'new key',
})
.where({ id: 1 })
.execute();
});
const end = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(end[0].key).toEqual('new key');
});
test('rollback successfully', async () => {
try {
await strapi.db.transaction(async () => {
// this is valid
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'wrong key',
})
.where({ id: 1 })
.execute();
// this throws
await strapi.db
.queryBuilder('invalid_uid')
.update({
key: 'bad key',
invalid_key: 'error',
})
.where({ id: 1 })
.execute();
});
expect('this should not be reached').toBe(false);
} catch (e) {
// do nothing
}
const end = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(end[0].key).toEqual(original[0].key);
});
test('nested rollback -> rollback works', async () => {
try {
await strapi.db.transaction(async () => {
// this is valid
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'changed key',
})
.where({ id: 1 })
.execute();
// here we'll make a nested transaction that throws and then confirm we still have "changed key" from above
try {
await strapi.db.transaction(async () => {
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'changed key - nested',
})
.where({ id: 1 })
.execute();
// this should throw and roll back
await strapi.db
.queryBuilder('invalid_uid')
.update({
invalid_key: 'error',
})
.where({ id: 1 })
.execute();
});
} catch (e) {
// do nothing
}
// should equal the result from above
const result = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(result[0].key).toEqual('changed key');
// this throws
await strapi.db
.queryBuilder('invalid_uid')
.update({
key: original[0].key,
invalid_key: 'error',
})
.where({ id: 1 })
.execute();
});
expect('this should not be reached').toBe(false);
} catch (e) {
// do nothing
}
const end = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(end[0].key).toEqual(original[0].key);
});
test('nested commit -> rollback works', async () => {
try {
await strapi.db.transaction(async () => {
// this is valid
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'changed key',
})
.where({ id: 1 })
.execute();
// here we'll make a nested transaction that works, and then later we'll rollback the outer transaction
try {
await strapi.db.transaction(async () => {
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'changed key - nested',
})
.where({ id: 1 })
.execute();
});
} catch (e) {
// do nothing
}
// should equal the result from above
const result = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(result[0].key).toEqual('changed key - nested');
// this throws
await strapi.db
.queryBuilder('invalid_uid')
.update({
key: original[0].key,
invalid_key: 'error',
})
.where({ id: 1 })
.execute();
});
expect('this should not be reached').toBe(false);
} catch (e) {
// do nothing
}
const end = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(end[0].key).toEqual(original[0].key);
});
});
describe('using a transaction object', () => {
test('commits successfully', async () => {
const trx = await strapi.db.transaction();
try {
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'wrong key',
})
.where({ id: 1 })
.transacting(trx.get())
.execute();
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: original[0].key,
})
.where({ id: 1 })
.transacting(trx.get())
.execute();
await trx.commit();
} catch (e) {
await trx.rollback();
console.log(e.message);
expect('this should not be reached').toBe(false);
}
const end = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(end[0].key).toEqual('strapi_content_types_schema');
});
test('rollback successfully', async () => {
const trx = await strapi.db.transaction();
try {
await strapi.db
.queryBuilder('strapi::core-store')
.update({
key: 'wrong key',
})
.where({ id: 1 })
.transacting(trx.get())
.execute();
// this query should throw because it has errors
await strapi.db
.queryBuilder('invalid_uid')
.update({
key: 123,
key_not_here: 'this should error',
})
.where({ id: 'this should error' })
.transacting(trx.get())
.execute();
await trx.commit();
expect('this should not be reached').toBe(false);
} catch (e) {
await trx.rollback();
}
const end = await strapi.db
.queryBuilder('strapi::core-store')
.select(['*'])
.where({ id: 1 })
.execute();
expect(end[0].key).toEqual('strapi_content_types_schema');
});
});
});

View File

@ -95,12 +95,12 @@ Strapi only supports maintenance and LTS versions of Node.js. Please refer to th
**Database:**
| Database | Minimum | Recommended |
| ---------- | ------- | ----------- |
| MySQL | 5.7.8 | 8.0 |
| MariaDB | 10.3 | 10.6 |
| PostgreSQL | 11.0 | 14.0 |
| SQLite | 3 | 3 |
| Database | Recommended | Minimum |
| ---------- | ----------- | ------- |
| MySQL | 8.0 | 5.7.8 |
| MariaDB | 10.6 | 10.3 |
| PostgreSQL | 14.0 | 11.0 |
| SQLite | 3 | 3 |
**We recommend always using the latest version of Strapi stable to start your new projects**.

View File

@ -43,9 +43,10 @@ const createComponents = async (uid, data) => {
throw new Error('Expected an array to create repeatable component');
}
const components = await Promise.all(
componentValue.map((value) => createComponent(componentUID, value))
);
const components = [];
for (const value of componentValue) {
components.push(await createComponent(componentUID, value));
}
componentBody[attributeName] = components.map(({ id }) => {
return {
@ -77,18 +78,19 @@ const createComponents = async (uid, data) => {
throw new Error('Expected an array to create repeatable component');
}
componentBody[attributeName] = await Promise.all(
dynamiczoneValues.map(async (value) => {
const { id } = await createComponent(value.__component, value);
return {
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
};
})
);
const dynamicZoneData = [];
for (const value of dynamiczoneValues) {
const { id } = await createComponent(value.__component, value);
dynamicZoneData.push({
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
});
}
componentBody[attributeName] = dynamicZoneData;
continue;
}
@ -137,9 +139,10 @@ const updateComponents = async (uid, entityToUpdate, data) => {
throw new Error('Expected an array to create repeatable component');
}
const components = await Promise.all(
componentValue.map((value) => updateOrCreateComponent(componentUID, value))
);
const components = [];
for (const value of componentValue) {
components.push(await updateOrCreateComponent(componentUID, value));
}
componentBody[attributeName] = components.filter(_.negate(_.isNil)).map(({ id }) => {
return {
@ -173,19 +176,19 @@ const updateComponents = async (uid, entityToUpdate, data) => {
throw new Error('Expected an array to create repeatable component');
}
componentBody[attributeName] = await Promise.all(
dynamiczoneValues.map(async (value) => {
const { id } = await updateOrCreateComponent(value.__component, value);
const dynamicZoneData = [];
for (const value of dynamiczoneValues) {
const { id } = await updateOrCreateComponent(value.__component, value);
dynamicZoneData.push({
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
});
}
return {
id,
__component: value.__component,
__pivot: {
field: attributeName,
},
};
})
);
componentBody[attributeName] = dynamicZoneData;
continue;
}
@ -287,14 +290,14 @@ const deleteComponents = async (uid, entityToDelete, { loadComponents = true } =
if (attribute.type === 'component') {
const { component: componentUID } = attribute;
await Promise.all(
_.castArray(value).map((subValue) => deleteComponent(componentUID, subValue))
);
for (const subValue of _.castArray(value)) {
await deleteComponent(componentUID, subValue);
}
} else {
// delete dynamic zone components
await Promise.all(
_.castArray(value).map((subValue) => deleteComponent(subValue.__component, subValue))
);
for (const subValue of _.castArray(value)) {
await deleteComponent(subValue.__component, subValue);
}
}
continue;

View File

@ -98,7 +98,7 @@
"chokidar": "3.5.2",
"ci-info": "3.5.0",
"cli-table3": "0.6.2",
"commander": "8.2.0",
"commander": "8.3.0",
"configstore": "5.0.1",
"debug": "4.3.4",
"delegates": "1.0.0",

View File

@ -228,6 +228,7 @@ module.exports = ({ strapi }) => ({
const formats = await generateResponsiveFormats(fileData);
if (Array.isArray(formats) && formats.length > 0) {
for (const format of formats) {
// eslint-disable-next-line no-continue
if (!format) continue;
uploadPromises.push(uploadResponsiveFormat(format));
}

View File

@ -32,7 +32,9 @@ const syncLocalizations = async (entry, { model }) => {
return strapi.query(model.uid).update({ where: { id }, data: { localizations } });
};
await Promise.all(entry.localizations.map(({ id }) => updateLocalization(id)));
for (const localization of entry.localizations) {
await updateLocalization(localization.id);
}
}
};
@ -56,7 +58,9 @@ const syncNonLocalizedAttributes = async (entry, { model }) => {
return strapi.entityService.update(model.uid, id, { data: nonLocalizedAttributes });
};
await Promise.all(entry.localizations.map(({ id }) => updateLocalization(id)));
for (const localization of entry.localizations) {
await updateLocalization(localization.id);
}
}
};

View File

@ -8997,10 +8997,10 @@ comma-separated-tokens@^1.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
commander@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8"
integrity sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==
commander@8.3.0, commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commander@^2.19.0, commander@^2.20.0, commander@^2.20.3:
version "2.20.3"
@ -9022,11 +9022,6 @@ commander@^7.0.0, commander@^7.2.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commander@^9.1.0, commander@^9.3.0:
version "9.4.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"