mirror of
https://github.com/strapi/strapi.git
synced 2025-11-20 12:08:25 +00:00
Merge branch 'master' into features/typescript
This commit is contained in:
commit
66e3aa5dcb
4
.github/actions/check-pr-status/package.json
vendored
4
.github/actions/check-pr-status/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "check-pr-status",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@ -9,7 +9,7 @@
|
||||
"watch": "NODE_ENV=production ncc build index.js -w -o dist --minify"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/core": "1.8.0",
|
||||
"@actions/core": "1.8.1",
|
||||
"@actions/github": "5.0.0",
|
||||
"@vercel/ncc": "0.33.3"
|
||||
}
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As of January 2022 (and until this document is updated), only the v3.0.0 and v4.0.0 _stable_ releases of Strapi are supported for updates. Any previous versions are currently not supported and users are advised to use them "at their own risk".
|
||||
As of May 2022 (and until this document is updated), only the v4.x.x _stable_ releases of Strapi are supported for updates and bug fixes. Any previous versions are currently not supported and users are advised to use them "at their own risk".
|
||||
|
||||
- v3 support is limited to Critical/High priority security updates only until September 2022
|
||||
- v4 is considered LTS until further notice
|
||||
- v3.x.x support is limited to Critical/High severity security updates only until December 2022
|
||||
- v4.x.x is considered LTS until further notice
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "getstarted",
|
||||
"private": true,
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "A Strapi application.",
|
||||
"scripts": {
|
||||
"develop": "strapi develop",
|
||||
@ -12,17 +12,17 @@
|
||||
"strapi": "strapi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/admin": "4.1.9",
|
||||
"@strapi/plugin-documentation": "4.1.9",
|
||||
"@strapi/plugin-graphql": "4.1.9",
|
||||
"@strapi/plugin-i18n": "4.1.9",
|
||||
"@strapi/plugin-sentry": "4.1.9",
|
||||
"@strapi/plugin-users-permissions": "4.1.9",
|
||||
"@strapi/provider-email-mailgun": "4.1.9",
|
||||
"@strapi/provider-upload-aws-s3": "4.1.9",
|
||||
"@strapi/provider-upload-cloudinary": "4.1.9",
|
||||
"@strapi/strapi": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/admin": "4.1.11",
|
||||
"@strapi/plugin-documentation": "4.1.11",
|
||||
"@strapi/plugin-graphql": "4.1.11",
|
||||
"@strapi/plugin-i18n": "4.1.11",
|
||||
"@strapi/plugin-sentry": "4.1.11",
|
||||
"@strapi/plugin-users-permissions": "4.1.11",
|
||||
"@strapi/provider-email-mailgun": "4.1.11",
|
||||
"@strapi/provider-upload-aws-s3": "4.1.11",
|
||||
"@strapi/provider-upload-cloudinary": "4.1.11",
|
||||
"@strapi/strapi": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"@vscode/sqlite3": "5.0.8",
|
||||
"better-sqlite3": "7.4.6",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kitchensink",
|
||||
"private": true,
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "A Strapi application.",
|
||||
"scripts": {
|
||||
"develop": "strapi develop",
|
||||
@ -12,12 +12,12 @@
|
||||
"strapi": "strapi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/admin": "4.1.9",
|
||||
"@strapi/provider-email-mailgun": "4.1.9",
|
||||
"@strapi/provider-upload-aws-s3": "4.1.9",
|
||||
"@strapi/provider-upload-cloudinary": "4.1.9",
|
||||
"@strapi/strapi": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/admin": "4.1.11",
|
||||
"@strapi/provider-email-mailgun": "4.1.11",
|
||||
"@strapi/provider-upload-aws-s3": "4.1.11",
|
||||
"@strapi/provider-upload-cloudinary": "4.1.11",
|
||||
"@strapi/strapi": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"lodash": "4.17.21",
|
||||
"mysql": "2.18.1",
|
||||
"passport-google-oauth2": "0.2.0",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"examples/*"
|
||||
|
||||
@ -95,7 +95,7 @@
|
||||
"execa": "1.0.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"get-port": "5.1.1",
|
||||
"glob": "7.2.0",
|
||||
"glob": "7.2.3",
|
||||
"husky": "3.1.0",
|
||||
"istanbul": "~0.4.2",
|
||||
"jest": "26.6.3",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/admin-test-utils",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"private": true,
|
||||
"description": "Test utilities for the Strapi administration panel",
|
||||
"license": "MIT",
|
||||
|
||||
@ -81,8 +81,7 @@ async function initProject(projectName, program) {
|
||||
return generateApp(projectName, program);
|
||||
}
|
||||
|
||||
const prompt = await promptUser(projectName, program);
|
||||
|
||||
const prompt = await promptUser(projectName, program, hasDatabaseOptions);
|
||||
const directory = prompt.directory || projectName;
|
||||
await checkInstallPath(resolve(directory));
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-strapi-app",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Generate a new Strapi application.",
|
||||
"keywords": [
|
||||
"create-strapi-app",
|
||||
@ -38,7 +38,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/generate-new": "4.1.9",
|
||||
"@strapi/generate-new": "4.1.11",
|
||||
"commander": "6.1.0",
|
||||
"inquirer": "8.2.0"
|
||||
},
|
||||
|
||||
@ -7,8 +7,8 @@ const inquirer = require('inquirer');
|
||||
* @param {string|null} template - The Github repo of the template
|
||||
* @returns Object containting prompt answers
|
||||
*/
|
||||
module.exports = async function promptUser(projectName, program) {
|
||||
const questions = await getPromptQuestions(projectName, program);
|
||||
module.exports = async function promptUser(projectName, program, hasDatabaseOptions) {
|
||||
const questions = await getPromptQuestions(projectName, program, hasDatabaseOptions);
|
||||
return inquirer.prompt(questions);
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ module.exports = async function promptUser(projectName, program) {
|
||||
* @param {string|null} template - The template the project should use
|
||||
* @returns Array of prompt question objects
|
||||
*/
|
||||
async function getPromptQuestions(projectName, program) {
|
||||
async function getPromptQuestions(projectName, program, hasDatabaseOptions) {
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
@ -30,7 +30,7 @@ async function getPromptQuestions(projectName, program) {
|
||||
type: 'list',
|
||||
name: 'quick',
|
||||
message: 'Choose your installation type',
|
||||
when: !program.quickstart,
|
||||
when: !program.quickstart && !hasDatabaseOptions,
|
||||
choices: [
|
||||
{
|
||||
name: 'Quickstart (recommended)',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-strapi-starter",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Generate a new Strapi application.",
|
||||
"keywords": [
|
||||
"create-strapi-starter",
|
||||
@ -38,7 +38,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/generate-new": "4.1.9",
|
||||
"@strapi/generate-new": "4.1.11",
|
||||
"chalk": "4.1.1",
|
||||
"ci-info": "3.1.1",
|
||||
"commander": "7.1.0",
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const DynamicComponentCard = ({ children, componentUid, friendlyName, icon, onClick }) => {
|
||||
return (
|
||||
<Wrapper
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
onClick(componentUid);
|
||||
}}
|
||||
>
|
||||
<button className="component-icon" type="button">
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
</button>
|
||||
|
||||
<div className="component-uid">
|
||||
<span>{friendlyName}</span>
|
||||
</div>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
DynamicComponentCard.defaultProps = {
|
||||
children: null,
|
||||
friendlyName: '',
|
||||
onClick: () => {},
|
||||
icon: 'smile',
|
||||
};
|
||||
|
||||
DynamicComponentCard.propTypes = {
|
||||
children: PropTypes.node,
|
||||
componentUid: PropTypes.string.isRequired,
|
||||
friendlyName: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default DynamicComponentCard;
|
||||
@ -74,7 +74,7 @@ function ComponentCard({ componentUid, intlLabel, icon, onClick }) {
|
||||
}
|
||||
|
||||
ComponentCard.defaultProps = {
|
||||
icon: 'smile',
|
||||
icon: 'dice-d6',
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ const getSelectStyles = theme => {
|
||||
};
|
||||
},
|
||||
indicatorContainer: base => ({ ...base, padding: 0, paddingRight: theme.spaces[3] }),
|
||||
input: base => ({ ...base, margin: 0, padding: 0 }),
|
||||
input: base => ({ ...base, margin: 0, padding: 0, color: theme.colors.neutral800 }),
|
||||
menu: base => {
|
||||
return {
|
||||
...base,
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { ContentBox, useTracking } from '@strapi/helper-plugin';
|
||||
import { Stack } from '@strapi/design-system/Stack';
|
||||
import InformationSquare from '@strapi/icons/InformationSquare';
|
||||
import CodeSquare from '@strapi/icons/CodeSquare';
|
||||
import PlaySquare from '@strapi/icons/PlaySquare';
|
||||
import FeatherSquare from '@strapi/icons/FeatherSquare';
|
||||
import { ContentBox } from '@strapi/helper-plugin';
|
||||
|
||||
const BlockLink = styled.a`
|
||||
text-decoration: none;
|
||||
@ -14,6 +14,11 @@ const BlockLink = styled.a`
|
||||
|
||||
const ContentBlocks = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { trackUsage } = useTracking();
|
||||
|
||||
const handleClick = eventName => {
|
||||
trackUsage(eventName);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack spacing={5}>
|
||||
@ -21,6 +26,7 @@ const ContentBlocks = () => {
|
||||
href="https://strapi.io/resource-center"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
onClick={() => handleClick('didClickonReadTheDocumentationSection')}
|
||||
>
|
||||
<ContentBox
|
||||
title={formatMessage({
|
||||
@ -39,6 +45,7 @@ const ContentBlocks = () => {
|
||||
href="https://strapi.io/starters"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
onClick={() => handleClick('didClickonCodeExampleSection')}
|
||||
>
|
||||
<ContentBox
|
||||
title={formatMessage({
|
||||
@ -57,6 +64,7 @@ const ContentBlocks = () => {
|
||||
href="https://strapi.io/blog/categories/tutorials"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
onClick={() => handleClick('didClickonTutorialSection')}
|
||||
>
|
||||
<ContentBox
|
||||
title={formatMessage({
|
||||
@ -71,7 +79,12 @@ const ContentBlocks = () => {
|
||||
iconBackground="secondary100"
|
||||
/>
|
||||
</BlockLink>
|
||||
<BlockLink href="https://strapi.io/blog" target="_blank" rel="noopener noreferrer nofollow">
|
||||
<BlockLink
|
||||
href="https://strapi.io/blog"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
onClick={() => handleClick('didClickonBlogSection')}
|
||||
>
|
||||
<ContentBox
|
||||
title={formatMessage({
|
||||
id: 'app.components.BlockLink.blog',
|
||||
|
||||
@ -29,6 +29,10 @@ module.exports = {
|
||||
const createdUser = await getService('user').create(attributes);
|
||||
const userInfo = getService('user').sanitizeUser(createdUser);
|
||||
|
||||
// Note: We need to assign manually the registrationToken to the
|
||||
// final user payload so that it's not removed in the sanitation process.
|
||||
Object.assign(userInfo, { registrationToken: createdUser.registrationToken });
|
||||
|
||||
ctx.created({ data: userInfo });
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/admin",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Strapi Admin",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -50,12 +50,12 @@
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@strapi/babel-plugin-switch-ee-ce": "4.1.9",
|
||||
"@strapi/babel-plugin-switch-ee-ce": "4.1.11",
|
||||
"@strapi/design-system": "1.1.0",
|
||||
"@strapi/helper-plugin": "4.1.9",
|
||||
"@strapi/helper-plugin": "4.1.11",
|
||||
"@strapi/icons": "1.1.0",
|
||||
"@strapi/typescript-utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/typescript-utils": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"axios": "0.24.0",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-styled-components": "2.0.2",
|
||||
|
||||
@ -36,6 +36,10 @@ module.exports = {
|
||||
|
||||
const userInfo = getService('user').sanitizeUser(createdUser);
|
||||
|
||||
// Note: We need to assign manually the registrationToken to the
|
||||
// final user payload so that it's not removed in the sanitation process.
|
||||
Object.assign(userInfo, { registrationToken: createdUser.registrationToken });
|
||||
|
||||
// Send 201 created
|
||||
ctx.created({ data: userInfo });
|
||||
},
|
||||
|
||||
@ -105,13 +105,22 @@ describe('Auth', () => {
|
||||
});
|
||||
|
||||
describe('validatePassword', () => {
|
||||
test('Compares password with hash', async () => {
|
||||
test('Compares password with hash (matching passwords)', async () => {
|
||||
const password = 'pcw123';
|
||||
const hash = await hashPassword(password);
|
||||
|
||||
const isValid = await validatePassword(password, hash);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
test('Compares password with hash (not matching passwords)', async () => {
|
||||
const password = 'pcw123';
|
||||
const password2 = 'pcs1234';
|
||||
const hash = await hashPassword(password2);
|
||||
|
||||
const isValid = await validatePassword(password, hash);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forgotPassword', () => {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { omit } = require('lodash/fp');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
const { createUtils } = require('../../../../../test/helpers/utils');
|
||||
|
||||
const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';
|
||||
|
||||
const omitTimestamps = obj => _.omit(obj, ['updatedAt', 'createdAt']);
|
||||
const omitTimestamps = omit(['updatedAt', 'createdAt']);
|
||||
const omitRegistrationToken = omit(['registrationToken']);
|
||||
|
||||
/**
|
||||
* == Test Suite Overview ==
|
||||
@ -128,9 +129,10 @@ describe('Admin User CRUD (e2e)', () => {
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).not.toBeNull();
|
||||
expect(res.body.data).toHaveProperty('registrationToken');
|
||||
|
||||
// Using the created user as an example for the rest of the tests
|
||||
testData.user = res.body.data;
|
||||
testData.user = omitRegistrationToken(res.body.data);
|
||||
});
|
||||
|
||||
test('3. Creates users with superAdmin role (success)', async () => {
|
||||
@ -153,7 +155,7 @@ describe('Admin User CRUD (e2e)', () => {
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).not.toBeNull();
|
||||
|
||||
testData.otherSuperAdminUsers.push(res.body.data);
|
||||
testData.otherSuperAdminUsers.push(omitRegistrationToken(res.body.data));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-content-manager",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "A powerful UI to easily manage your data.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -24,7 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -106,7 +106,7 @@ function ComponentCard({ component, dzName, index, isActive, isInDevelopmentMode
|
||||
paddingRight={4}
|
||||
>
|
||||
<StackCentered spacing={1}>
|
||||
<StyledFontAwesomeIcon icon={icon} />
|
||||
<StyledFontAwesomeIcon icon={icon || 'dice-d6'} />
|
||||
<Box maxWidth={`calc(${pxToRem(140)} - 32px)`}>
|
||||
<Typography variant="pi" fontWeight="bold" ellipsis>
|
||||
{displayName}
|
||||
|
||||
@ -16,23 +16,14 @@ import Cell from './Cell';
|
||||
|
||||
const CELL_WIDTH = 44;
|
||||
|
||||
const ComponentIconPicker = ({ error, isCreating, intlLabel, name, onChange, value }) => {
|
||||
const { allIcons, allComponentsIconAlreadyTaken } = useDataManager();
|
||||
const ComponentIconPicker = ({ error, intlLabel, name, onChange, value }) => {
|
||||
const { allIcons } = useDataManager();
|
||||
const { formatMessage } = useIntl();
|
||||
const [originalIcon] = useState(value);
|
||||
const initialIcons = allIcons.filter(ico => {
|
||||
if (isCreating) {
|
||||
return !allComponentsIconAlreadyTaken.includes(ico);
|
||||
}
|
||||
|
||||
// Edition
|
||||
return !allComponentsIconAlreadyTaken.filter(icon => icon !== originalIcon).includes(ico);
|
||||
});
|
||||
|
||||
const searchWrapperRef = useRef();
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [icons, setIcons] = useState(initialIcons);
|
||||
const [icons, setIcons] = useState(allIcons);
|
||||
const toggleSearch = () => setShowSearch(prev => !prev);
|
||||
|
||||
useEffect(() => {
|
||||
@ -43,7 +34,7 @@ const ComponentIconPicker = ({ error, isCreating, intlLabel, name, onChange, val
|
||||
|
||||
const handleChangeSearch = ({ target: { value } }) => {
|
||||
setSearch(value);
|
||||
setIcons(() => initialIcons.filter(icon => icon.includes(value)));
|
||||
setIcons(() => allIcons.filter(icon => icon.includes(value)));
|
||||
};
|
||||
|
||||
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
|
||||
@ -91,7 +82,7 @@ const ComponentIconPicker = ({ error, isCreating, intlLabel, name, onChange, val
|
||||
}}
|
||||
onClear={() => {
|
||||
setSearch('');
|
||||
setIcons(initialIcons);
|
||||
setIcons(allIcons);
|
||||
toggleSearch();
|
||||
}}
|
||||
value={search}
|
||||
@ -175,7 +166,6 @@ ComponentIconPicker.defaultProps = {
|
||||
|
||||
ComponentIconPicker.propTypes = {
|
||||
error: PropTypes.string,
|
||||
isCreating: PropTypes.bool.isRequired,
|
||||
intlLabel: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
|
||||
@ -554,10 +554,6 @@ const DataManagerProvider = ({
|
||||
addAttribute,
|
||||
addCreatedComponentToDynamicZone,
|
||||
allComponentsCategories: retrieveSpecificInfoFromComponents(components, ['category']),
|
||||
allComponentsIconAlreadyTaken: retrieveSpecificInfoFromComponents(components, [
|
||||
'schema',
|
||||
'icon',
|
||||
]),
|
||||
allIcons,
|
||||
changeDynamicZoneComponents,
|
||||
components,
|
||||
|
||||
@ -251,7 +251,7 @@ const advancedForm = {
|
||||
id: 'global.settings',
|
||||
defaultMessage: 'Settings',
|
||||
},
|
||||
items: [options.private, options.required],
|
||||
items: [options.required, options.private],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -39,7 +39,7 @@ const createComponentSchema = (usedComponentNames, reservedNames, category) => {
|
||||
.matches(CATEGORY_NAME_REGEX, errorsTrads.regex)
|
||||
.required(errorsTrads.required),
|
||||
|
||||
icon: yup.string().required(errorsTrads.required),
|
||||
icon: yup.string(),
|
||||
};
|
||||
|
||||
return yup.object(shape);
|
||||
|
||||
@ -67,11 +67,10 @@ const formsAPI = {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
formType[field].validators.push(validator);
|
||||
formType[field].form.advanced.push(advanced);
|
||||
formType[field].form.base.push(base);
|
||||
}
|
||||
formType[field].validators.push(validator);
|
||||
formType[field].form.advanced.push(advanced);
|
||||
formType[field].form.base.push(base);
|
||||
});
|
||||
},
|
||||
getAdvancedForm(target, props = null) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-content-type-builder",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Strapi plugin to create content type",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -28,9 +28,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
"@strapi/generators": "4.1.9",
|
||||
"@strapi/helper-plugin": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/generators": "4.1.11",
|
||||
"@strapi/helper-plugin": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"fs-extra": "10.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"pluralize": "^8.0.0",
|
||||
|
||||
@ -22,8 +22,7 @@ const componentSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
|
||||
icon: yup
|
||||
.string()
|
||||
.nullable()
|
||||
.test(isValidIcon)
|
||||
.required('icon.required'),
|
||||
.test(isValidIcon),
|
||||
category: yup
|
||||
.string()
|
||||
.nullable()
|
||||
|
||||
@ -47,11 +47,6 @@ describe('Content Type Builder - Components', () => {
|
||||
name: 'ValidationError',
|
||||
path: ['component', 'displayName'],
|
||||
},
|
||||
{
|
||||
message: 'icon.required',
|
||||
name: 'ValidationError',
|
||||
path: ['component', 'icon'],
|
||||
},
|
||||
{
|
||||
message: 'category.required',
|
||||
name: 'ValidationError',
|
||||
@ -59,7 +54,7 @@ describe('Content Type Builder - Components', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
message: '4 errors occurred',
|
||||
message: '3 errors occurred',
|
||||
name: 'ValidationError',
|
||||
},
|
||||
});
|
||||
@ -242,11 +237,6 @@ describe('Content Type Builder - Components', () => {
|
||||
name: 'ValidationError',
|
||||
path: ['component', 'displayName'],
|
||||
},
|
||||
{
|
||||
message: 'icon.required',
|
||||
name: 'ValidationError',
|
||||
path: ['component', 'icon'],
|
||||
},
|
||||
{
|
||||
message: 'category.required',
|
||||
name: 'ValidationError',
|
||||
@ -254,7 +244,7 @@ describe('Content Type Builder - Components', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
message: '3 errors occurred',
|
||||
message: '2 errors occurred',
|
||||
name: 'ValidationError',
|
||||
},
|
||||
});
|
||||
|
||||
55
packages/core/database/lib/__tests__/lifecycles.test.js
Normal file
55
packages/core/database/lib/__tests__/lifecycles.test.js
Normal file
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const { createLifecyclesProvider } = require('../lifecycles');
|
||||
|
||||
describe('LifecycleProvider', () => {
|
||||
describe('run', () => {
|
||||
/** @type {import("../lifecycles").LifecycleProvider} */
|
||||
let provider;
|
||||
let dbMetadataGetStub = jest.fn(uid => ({ uid, name: 'TestModel' }));
|
||||
|
||||
beforeEach(() => {
|
||||
const db = {
|
||||
metadata: {
|
||||
get: dbMetadataGetStub,
|
||||
},
|
||||
};
|
||||
provider = createLifecyclesProvider(db);
|
||||
provider.clear();
|
||||
});
|
||||
|
||||
it('store state', async () => {
|
||||
const expectedState = new Date().toISOString();
|
||||
|
||||
const subscriber = {
|
||||
async beforeEvent(event) {
|
||||
event.state = expectedState;
|
||||
},
|
||||
};
|
||||
provider.subscribe(subscriber);
|
||||
|
||||
const stateBefore = await provider.run('beforeEvent', 'test-model', { id: 'instance-id' });
|
||||
|
||||
expect(stateBefore.get(subscriber)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('use shared state', async () => {
|
||||
const expectedState = { value: new Date().toISOString() };
|
||||
let receivedState;
|
||||
|
||||
provider.subscribe({
|
||||
async beforeEvent(event) {
|
||||
event.state.value = expectedState.value;
|
||||
},
|
||||
async afterEvent(event) {
|
||||
receivedState = event.state;
|
||||
},
|
||||
});
|
||||
|
||||
const stateBefore = await provider.run('beforeEvent', 'test-model', { id: 'instance-id' });
|
||||
await provider.run('afterEvent', 'test-model', { id: 'instance-id' }, stateBefore);
|
||||
|
||||
expect(receivedState).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -113,33 +113,33 @@ const createEntityManager = db => {
|
||||
|
||||
return {
|
||||
async findOne(uid, params) {
|
||||
await db.lifecycles.run('beforeFindOne', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeFindOne', uid, { params });
|
||||
|
||||
const result = await this.createQueryBuilder(uid)
|
||||
.init(params)
|
||||
.first()
|
||||
.execute();
|
||||
|
||||
await db.lifecycles.run('afterFindOne', uid, { params, result });
|
||||
await db.lifecycles.run('afterFindOne', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// should we name it findOne because people are used to it ?
|
||||
async findMany(uid, params) {
|
||||
await db.lifecycles.run('beforeFindMany', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeFindMany', uid, { params });
|
||||
|
||||
const result = await this.createQueryBuilder(uid)
|
||||
.init(params)
|
||||
.execute();
|
||||
|
||||
await db.lifecycles.run('afterFindMany', uid, { params, result });
|
||||
await db.lifecycles.run('afterFindMany', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async count(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeCount', uid, { params });
|
||||
async count(uid, params) {
|
||||
const states = await db.lifecycles.run('beforeCount', uid, { params });
|
||||
|
||||
const res = await this.createQueryBuilder(uid)
|
||||
.init(_.pick(['_q', 'where', 'filters'], params))
|
||||
@ -149,13 +149,13 @@ const createEntityManager = db => {
|
||||
|
||||
const result = Number(res.count);
|
||||
|
||||
await db.lifecycles.run('afterCount', uid, { params, result });
|
||||
await db.lifecycles.run('afterCount', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async create(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeCreate', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeCreate', uid, { params });
|
||||
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { data } = params;
|
||||
@ -182,14 +182,14 @@ const createEntityManager = db => {
|
||||
populate: params.populate,
|
||||
});
|
||||
|
||||
await db.lifecycles.run('afterCreate', uid, { params, result });
|
||||
await db.lifecycles.run('afterCreate', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// TODO: where do we handle relation processing for many queries ?
|
||||
async createMany(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeCreateMany', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeCreateMany', uid, { params });
|
||||
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { data } = params;
|
||||
@ -210,13 +210,13 @@ const createEntityManager = db => {
|
||||
|
||||
const result = { count: data.length };
|
||||
|
||||
await db.lifecycles.run('afterCreateMany', uid, { params, result });
|
||||
await db.lifecycles.run('afterCreateMany', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async update(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeUpdate', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeUpdate', uid, { params });
|
||||
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { where, data } = params;
|
||||
@ -259,14 +259,14 @@ const createEntityManager = db => {
|
||||
populate: params.populate,
|
||||
});
|
||||
|
||||
await db.lifecycles.run('afterUpdate', uid, { params, result });
|
||||
await db.lifecycles.run('afterUpdate', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// TODO: where do we handle relation processing for many queries ?
|
||||
async updateMany(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeUpdateMany', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeUpdateMany', uid, { params });
|
||||
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { where, data } = params;
|
||||
@ -284,13 +284,13 @@ const createEntityManager = db => {
|
||||
|
||||
const result = { count: updatedRows };
|
||||
|
||||
await db.lifecycles.run('afterUpdateMany', uid, { params, result });
|
||||
await db.lifecycles.run('afterUpdateMany', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async delete(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeDelete', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeDelete', uid, { params });
|
||||
|
||||
const { where, select, populate } = params;
|
||||
|
||||
@ -318,14 +318,14 @@ const createEntityManager = db => {
|
||||
|
||||
await this.deleteRelations(uid, id);
|
||||
|
||||
await db.lifecycles.run('afterDelete', uid, { params, result: entity });
|
||||
await db.lifecycles.run('afterDelete', uid, { params, result: entity }, states);
|
||||
|
||||
return entity;
|
||||
},
|
||||
|
||||
// TODO: where do we handle relation processing for many queries ?
|
||||
async deleteMany(uid, params = {}) {
|
||||
await db.lifecycles.run('beforeDeleteMany', uid, { params });
|
||||
const states = await db.lifecycles.run('beforeDeleteMany', uid, { params });
|
||||
|
||||
const { where } = params;
|
||||
|
||||
@ -336,7 +336,7 @@ const createEntityManager = db => {
|
||||
|
||||
const result = { count: deletedRows };
|
||||
|
||||
await db.lifecycles.run('afterDelete', uid, { params, result });
|
||||
await db.lifecycles.run('afterDeleteMany', uid, { params, result }, states);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
@ -43,7 +43,8 @@ export interface Event {
|
||||
export interface LifecycleProvider {
|
||||
subscribe(subscriber: Subscriber): () => void;
|
||||
clear(): void;
|
||||
run(action: Action, uid: string, properties: any): Promise<void>;
|
||||
run(action: Action, uid: string, properties: any): Promise<Map<any, any>>;
|
||||
run(action: Action, uid: string, properties: any, states: Map<any, any>): Promise<Map<any, any>>;
|
||||
createEvent(action: Action, uid: string, properties: any): Event;
|
||||
}
|
||||
|
||||
|
||||
@ -30,21 +30,39 @@ const createLifecyclesProvider = db => {
|
||||
subscribers = [];
|
||||
},
|
||||
|
||||
createEvent(action, uid, properties) {
|
||||
/**
|
||||
* @param {string} action
|
||||
* @param {string} uid
|
||||
* @param {{ params?: any, result?: any }} properties
|
||||
* @param {Map<any, any>} state
|
||||
*/
|
||||
createEvent(action, uid, properties, state) {
|
||||
const model = db.metadata.get(uid);
|
||||
|
||||
return {
|
||||
action,
|
||||
model,
|
||||
state,
|
||||
...properties,
|
||||
};
|
||||
},
|
||||
|
||||
async run(action, uid, properties) {
|
||||
for (const subscriber of subscribers) {
|
||||
/**
|
||||
* @param {string} action
|
||||
* @param {string} uid
|
||||
* @param {{ params?: any, result?: any }} properties
|
||||
* @param {Map<any, any>} states
|
||||
*/
|
||||
async run(action, uid, properties, states = new Map()) {
|
||||
for (let i = 0; i < subscribers.length; i++) {
|
||||
const subscriber = subscribers[i];
|
||||
if (typeof subscriber === 'function') {
|
||||
const event = this.createEvent(action, uid, properties);
|
||||
const state = states.get(subscriber) || {};
|
||||
const event = this.createEvent(action, uid, properties, state);
|
||||
await subscriber(event);
|
||||
if (event.state) {
|
||||
states.set(subscriber, event.state || state);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -52,11 +70,17 @@ const createLifecyclesProvider = db => {
|
||||
const hasModel = !subscriber.models || subscriber.models.includes(uid);
|
||||
|
||||
if (hasAction && hasModel) {
|
||||
const event = this.createEvent(action, uid, properties);
|
||||
const state = states.get(subscriber) || {};
|
||||
const event = this.createEvent(action, uid, properties, state);
|
||||
|
||||
await subscriber[action](event);
|
||||
if (event.state) {
|
||||
states.set(subscriber, event.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return states;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,24 +2,35 @@
|
||||
|
||||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
const Umzug = require('umzug');
|
||||
const { Umzug } = require('umzug');
|
||||
|
||||
const createStorage = require('./storage');
|
||||
|
||||
const wrapTransaction = db => fn => () =>
|
||||
db.getConnection().transaction(trx => Promise.resolve(fn(trx)));
|
||||
|
||||
// TODO: check multiple commands in one sql statement
|
||||
const migrationResolver = path => {
|
||||
const migrationResolver = ({ name, path, context }) => {
|
||||
const { db } = context;
|
||||
|
||||
// if sql file run with knex raw
|
||||
if (path.match(/\.sql$/)) {
|
||||
const sql = fse.readFileSync(path, 'utf8');
|
||||
|
||||
return {
|
||||
up: knex => knex.raw(sql),
|
||||
name,
|
||||
up: wrapTransaction(db)(knex => knex.raw(sql)),
|
||||
down() {},
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE: we can add some ts register if we want to handle ts migration files at some point
|
||||
return require(path);
|
||||
const migration = require(path);
|
||||
return {
|
||||
name,
|
||||
up: wrapTransaction(db)(migration.up),
|
||||
down: wrapTransaction(db)(migration.down),
|
||||
};
|
||||
};
|
||||
|
||||
const createUmzugProvider = db => {
|
||||
@ -27,17 +38,12 @@ const createUmzugProvider = db => {
|
||||
|
||||
fse.ensureDirSync(migrationDir);
|
||||
|
||||
const wrapFn = fn => db => db.getConnection().transaction(trx => Promise.resolve(fn(trx)));
|
||||
const storage = createStorage({ db, tableName: 'strapi_migrations' });
|
||||
|
||||
return new Umzug({
|
||||
storage,
|
||||
storage: createStorage({ db, tableName: 'strapi_migrations' }),
|
||||
context: { db },
|
||||
migrations: {
|
||||
path: migrationDir,
|
||||
pattern: /\.(js|sql)$/,
|
||||
params: [db],
|
||||
wrap: wrapFn,
|
||||
customResolver: migrationResolver,
|
||||
glob: ['*.{js,sql}', { cwd: migrationDir }],
|
||||
resolve: migrationResolver,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -14,21 +14,21 @@ const createStorage = (opts = {}) => {
|
||||
};
|
||||
|
||||
return {
|
||||
async logMigration(migrationName) {
|
||||
async logMigration({ name }) {
|
||||
await db
|
||||
.getConnection()
|
||||
.insert({
|
||||
name: migrationName,
|
||||
name,
|
||||
time: new Date(),
|
||||
})
|
||||
.into(tableName);
|
||||
},
|
||||
|
||||
async unlogMigration(migrationName) {
|
||||
async unlogMigration({ name }) {
|
||||
await db
|
||||
.getConnection(tableName)
|
||||
.del()
|
||||
.where({ name: migrationName });
|
||||
.where({ name });
|
||||
},
|
||||
|
||||
async executed() {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/database",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Strapi's database layer",
|
||||
"homepage": "https://strapi.io",
|
||||
"bugs": {
|
||||
@ -36,7 +36,7 @@
|
||||
"fs-extra": "10.0.0",
|
||||
"knex": "1.0.4",
|
||||
"lodash": "4.17.21",
|
||||
"umzug": "2.3.0"
|
||||
"umzug": "3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-email",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Easily configure your Strapi application to send emails.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -26,12 +26,12 @@
|
||||
"test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/provider-email-sendmail": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/provider-email-sendmail": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@strapi/helper-plugin": "4.1.9"
|
||||
"@strapi/helper-plugin": "4.1.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/helper-plugin",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Helper for Strapi plugins development",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@ -7,31 +7,35 @@ const fse = require('fs-extra');
|
||||
const { isKebabCase } = require('@strapi/utils');
|
||||
const { importDefault } = require('../../utils');
|
||||
|
||||
// to handle names with numbers in it we first check if it is already in kebabCase
|
||||
const normalizeName = name => (isKebabCase(name) ? name : _.kebabCase(name));
|
||||
|
||||
const DEFAULT_CONTENT_TYPE = {
|
||||
schema: {},
|
||||
actions: {},
|
||||
lifecycles: {},
|
||||
};
|
||||
|
||||
// to handle names with numbers in it we first check if it is already in kebabCase
|
||||
const normalizeName = name => (isKebabCase(name) ? name : _.kebabCase(name));
|
||||
|
||||
const isDirectory = fd => fd.isDirectory();
|
||||
const isDotFile = fd => fd.name.startsWith('.');
|
||||
|
||||
module.exports = async strapi => {
|
||||
if (!existsSync(strapi.dirs.dist.api)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apisFDs = await fse.readdir(strapi.dirs.dist.api, { withFileTypes: true });
|
||||
const apisFDs = await (await fse.readdir(strapi.dirs.dist.api, { withFileTypes: true }))
|
||||
.filter(isDirectory)
|
||||
.filter(_.negate(isDotFile));
|
||||
|
||||
const apis = {};
|
||||
|
||||
// only load folders
|
||||
for (const apiFD of apisFDs) {
|
||||
if (apiFD.isDirectory()) {
|
||||
const apiName = normalizeName(apiFD.name);
|
||||
const api = await loadAPI(join(strapi.dirs.dist.api, apiFD.name));
|
||||
const apiName = normalizeName(apiFD.name);
|
||||
const api = await loadAPI(join(strapi.dirs.dist.api, apiFD.name));
|
||||
|
||||
apis[apiName] = api;
|
||||
}
|
||||
apis[apiName] = api;
|
||||
}
|
||||
|
||||
validateContentTypesUnicity(apis);
|
||||
|
||||
@ -200,4 +200,19 @@ describe('BigInteger validator', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('min', () => {
|
||||
test('it does not validate the min constraint if the attribute min is not a number', async () => {
|
||||
const validator = strapiUtils.validateYupSchema(
|
||||
validators.biginteger(
|
||||
{
|
||||
attr: { type: 'biginteger', minLength: '123' },
|
||||
},
|
||||
{ isDraft: false }
|
||||
)
|
||||
);
|
||||
|
||||
expect(await validator(1)).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -12,7 +12,7 @@ describe('Email validator', () => {
|
||||
const validator = strapiUtils.validateYupSchema(
|
||||
validators.email(
|
||||
{
|
||||
attr: { type: 'string' },
|
||||
attr: { type: 'email' },
|
||||
},
|
||||
{ isDraft: false }
|
||||
)
|
||||
@ -29,7 +29,7 @@ describe('Email validator', () => {
|
||||
const validator = strapiUtils.validateYupSchema(
|
||||
validators.email(
|
||||
{
|
||||
attr: { type: 'string' },
|
||||
attr: { type: 'email' },
|
||||
},
|
||||
{ isDraft: false }
|
||||
)
|
||||
@ -37,5 +37,18 @@ describe('Email validator', () => {
|
||||
|
||||
expect(await validator('valid@email.com')).toBe('valid@email.com');
|
||||
});
|
||||
|
||||
test('it validates non-empty email required field', async () => {
|
||||
const validator = strapiUtils.validateYupSchema(
|
||||
validators.email({ attr: { type: 'email' } }, { isDraft: false })
|
||||
);
|
||||
|
||||
try {
|
||||
await validator('');
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(YupValidationError);
|
||||
expect(err.message).toBe('this cannot be empty');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -166,7 +166,9 @@ const stringValidator = composeValidators(
|
||||
addUniqueValidator
|
||||
);
|
||||
|
||||
const emailValidator = composeValidators(stringValidator, validator => validator.email());
|
||||
const emailValidator = composeValidators(stringValidator, validator =>
|
||||
validator.email().min(1, '${path} cannot be empty')
|
||||
);
|
||||
|
||||
const uidValidator = composeValidators(stringValidator, validator =>
|
||||
validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/strapi",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
|
||||
"keywords": [
|
||||
"strapi",
|
||||
@ -80,17 +80,17 @@
|
||||
"dependencies": {
|
||||
"@koa/cors": "3.1.0",
|
||||
"@koa/router": "10.1.1",
|
||||
"@strapi/admin": "4.1.9",
|
||||
"@strapi/database": "4.1.9",
|
||||
"@strapi/generate-new": "4.1.9",
|
||||
"@strapi/generators": "4.1.9",
|
||||
"@strapi/logger": "4.1.9",
|
||||
"@strapi/plugin-content-manager": "4.1.9",
|
||||
"@strapi/plugin-content-type-builder": "4.1.9",
|
||||
"@strapi/plugin-email": "4.1.9",
|
||||
"@strapi/plugin-upload": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/typescript-utils": "4.1.9",
|
||||
"@strapi/admin": "4.1.11",
|
||||
"@strapi/database": "4.1.11",
|
||||
"@strapi/generate-new": "4.1.11",
|
||||
"@strapi/generators": "4.1.11",
|
||||
"@strapi/logger": "4.1.11",
|
||||
"@strapi/plugin-content-manager": "4.1.11",
|
||||
"@strapi/plugin-content-type-builder": "4.1.11",
|
||||
"@strapi/plugin-email": "4.1.11",
|
||||
"@strapi/plugin-upload": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"@strapi/typescript-utils": "4.1.11",
|
||||
"bcryptjs": "2.4.3",
|
||||
"boxen": "5.1.2",
|
||||
"chalk": "4.1.2",
|
||||
|
||||
104
packages/core/upload/admin/src/translations/ca.json
Normal file
104
packages/core/upload/admin/src/translations/ca.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"bulk.select.label": "Seleccioneu tots els fitxers",
|
||||
"button.next": "Següent",
|
||||
"checkControl.crop-duplicate": "Duplica i retalla l'imatge",
|
||||
"checkControl.crop-original": "Retalla l'imatge original",
|
||||
"control-card.add": "Afegeix",
|
||||
"control-card.cancel": "Cancel·lar",
|
||||
"control-card.copy-link": "Copia l'enllaç",
|
||||
"control-card.crop": "Retalla",
|
||||
"control-card.download": "Descarregar",
|
||||
"control-card.edit": "Edita",
|
||||
"control-card.replace-media": "Substitueix els fitxers",
|
||||
"control-card.save": "Desa",
|
||||
"control-card.stop-crop": "Deixa de retallar",
|
||||
"filter.add": "Afegeix un filtre",
|
||||
"form.button.replace-media": "Substituïu els fitxers",
|
||||
"form.input.decription.file-alt": "Aquest text es mostrarà si el recurs no es pot mostrar.",
|
||||
"form.input.label.file-alt": "Text alternatiu",
|
||||
"form.input.label.file-caption": "Subtítol",
|
||||
"form.input.label.file-name": "Nom de l'arxiu",
|
||||
"form.upload-url.error.url.invalid": "Un URL no és vàlid",
|
||||
"form.upload-url.error.url.invalids": "{number} URL no són vàlids",
|
||||
"header.actions.add-assets": "Afegeix nous fitxers",
|
||||
"header.actions.upload-assets": "Carregueu fitxers",
|
||||
"header.actions.upload-new-asset": "Penja un fitxer nou",
|
||||
"header.content.assets-empty": "Sense fitxers",
|
||||
"header.content.assets-multiple": "{nombre} fitxers",
|
||||
"header.content.assets-single": "1 fitxer",
|
||||
"input.button.label": "Exploreu fitxers",
|
||||
"input.label": "Arrossega i deixa anar aquí o",
|
||||
"input.label-bold": "Arrossegar i deixar anar",
|
||||
"input.label-normal": "per carregar o",
|
||||
"input.placeholder": "Feu clic per afegir un fitxer o arrossegueu i deixeu anar un fitxer en aquesta àrea",
|
||||
"input.placeholder.icon": "Deixeu el fitxer en aquesta zona",
|
||||
"input.url.description": "Separeu els vostres enllaços URL mitjançant un retorn de carro.",
|
||||
"input.url.label": "URL",
|
||||
"list.asset.at.finished": "Els fitxers s'han acabat de carregar.",
|
||||
"list.asset.load": "S'està carregant la llista de fitxers.",
|
||||
"list.assets-empty.search": "No s'ha trobat cap resultat",
|
||||
"list.assets-empty.subtitle": "Afegiu-ne un a la llista.",
|
||||
"list.assets-empty.title": "Encara no hi ha fitxers",
|
||||
"list.assets-empty.title-withSearch": "No hi ha fitxers amb els filtres aplicats",
|
||||
"list.assets.empty": "Penja els teus primers fitxers...",
|
||||
"list.assets.empty.no-permissions": "La llista de fitxers està buida.",
|
||||
"list.assets.loading-asset": "S'està carregant la previsualització dels mitjans: {path}",
|
||||
"list.assets.not-supported-content": "No hi ha vista prèvia disponible",
|
||||
"list.assets.preview-asset": "Vista prèvia del vídeo al camí {path}",
|
||||
"list.assets.selected": "{number, plural, =0 {Cap fitxer} un {1 fitxer} altres {# fitxers}} a punt per penjar",
|
||||
"list.assets.type-not-allowed": "Aquest tipus de fitxer no està permès.",
|
||||
"mediaLibraryInput.actions.nextSlide": "Següent diapositiva",
|
||||
"mediaLibraryInput.actions.previousSlide": "Diapositiva anterior",
|
||||
"mediaLibraryInput.placeholder": "Feu clic per afegir un fitxer o arrossegueu-ne un en aquesta àrea",
|
||||
"mediaLibraryInput.slideCount": "{n} de {m} diapositives",
|
||||
"modal.file-details.date": "Data",
|
||||
"modal.file-details.dimensions": "Dimensions",
|
||||
"modal.file-details.extension": "Extensió",
|
||||
"modal.file-details.size": "Mida",
|
||||
"modal.header.browse": "Carregueu fitxers",
|
||||
"modal.header.file-detail": "Detalls",
|
||||
"modal.header.pending-assets": "Fitxers pendents",
|
||||
"modal.header.select-files": "Fitxers seleccionats",
|
||||
"modal.nav.browse": "navegar",
|
||||
"modal.nav.computer": "Des de l'ordinador",
|
||||
"modal.nav.selected": "seleccionat",
|
||||
"modal.nav.url": "Des de l'URL",
|
||||
"modal.remove.success-label": "El fitxer s'ha eliminat correctament.",
|
||||
"modal.selected-list.sub-header-subtitle": "Arrossegueu i deixeu anar per reordenar els fitxers al camp",
|
||||
"modal.upload-list.footer.button": "Carregueu {nombre, plural, un {# fitxer} altres {# fitxer}} a la biblioteca",
|
||||
"modal.upload-list.sub-header-subtitle": "Gestioneu els fitxers abans d'afegir-los a la Mediateca",
|
||||
"modal.upload-list.sub-header.button": "Afegeix més actius",
|
||||
"modal.upload.cancelled": "La càrrega s'ha avortat manualment.",
|
||||
"page.title": "Configuració - Mediateca",
|
||||
"permissions.not-allowed.update": "No teniu permís per editar aquest fitxer.",
|
||||
"plugin.description.long": "Gestió de fitxers multimèdia.",
|
||||
"plugin.description.short": "Gestió de fitxers multimèdia.",
|
||||
"plugin.name": "Mediateca",
|
||||
"search.clear.label": "Esborra la cerca",
|
||||
"search.label": "Cerca un actiu",
|
||||
"search.placeholder": "ex.: el primer gos a la lluna",
|
||||
"settings.blockTitle": "Gestió de fitxers",
|
||||
"settings.form.autoOrientation.description": "Si activeu aquesta opció, la imatge girarà automàticament segons l'etiqueta d'orientació EXIF.",
|
||||
"settings.form.autoOrientation.label": "Orientació automàtica",
|
||||
"settings.form.responsiveDimensions.description": "Si activeu aquesta opció, es generaran diversos formats (petit, mitjà i gran) del recurs penjat.",
|
||||
"settings.form.responsiveDimensions.label": "Càrrega amigable responsiva",
|
||||
"settings.form.sizeOptimization.description": "Activar aquesta opció reduirà la mida de la imatge i la qualitat lleugerament.",
|
||||
"settings.form.sizeOptimization.label": "Optimització de la mida",
|
||||
"settings.form.videoPreview.description": "Generarà una previsualització de sis segons del vídeo (GIF)",
|
||||
"settings.form.videoPreview.label": "Vista prèvia",
|
||||
"settings.header.label": "Mediateca",
|
||||
"settings.section.doc.label": "Doc",
|
||||
"settings.section.image.label": "Imatge",
|
||||
"settings.section.video.label": "Vídeo",
|
||||
"settings.sub-header.label": "Configureu la configuració de la mediateca",
|
||||
"sort.created_at_asc": "Càrregues més antigues",
|
||||
"sort.created_at_desc": "Càrregues més recents",
|
||||
"sort.label": "Ordenar per",
|
||||
"sort.name_asc": "Ordre alfabètic (A a Z)",
|
||||
"sort.name_desc": "Ordre alfabètic invers (de la Z a la A)",
|
||||
"sort.updated_at_asc": "Actualitzacions més antigues",
|
||||
"sort.updated_at_desc": "Actualitzacions més recents",
|
||||
"tabs.title": "Com vols pujar els teus actius?",
|
||||
"window.confirm.close-modal.file": "Estàs segur? Els vostres canvis es perdran.",
|
||||
"window.confirm.close-modal.files": "Estàs segur? Teniu alguns fitxers que encara no s'han penjat."
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-upload",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Makes it easy to upload images and files to your Strapi Application.",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"author": {
|
||||
@ -23,9 +23,9 @@
|
||||
"test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/helper-plugin": "4.1.9",
|
||||
"@strapi/provider-upload-local": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/helper-plugin": "4.1.11",
|
||||
"@strapi/provider-upload-local": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"byte-size": "7.0.1",
|
||||
"cropperjs": "1.5.11",
|
||||
"fs-extra": "10.0.0",
|
||||
|
||||
73
packages/core/upload/server/__tests__/register.test.js
Normal file
73
packages/core/upload/server/__tests__/register.test.js
Normal file
@ -0,0 +1,73 @@
|
||||
'use strict';
|
||||
|
||||
const { join } = require('path');
|
||||
const bootstrap = require('../register');
|
||||
|
||||
const exampleMiddlewaresConfig = [
|
||||
{
|
||||
name: 'strapi::security',
|
||||
config: {
|
||||
contentSecurityPolicy: {
|
||||
useDefaults: true,
|
||||
directives: {
|
||||
'connect-src': ["'self'", 'https:'],
|
||||
'img-src': ["'self'", 'data:', 'blob:', 'https://exampledomain.global.strapi.io'],
|
||||
'media-src': ["'self'", 'data:', 'blob:', 'https://exampledomain.global.strapi.io'],
|
||||
upgradeInsecureRequests: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('@strapi/provider-upload-local', () => ({
|
||||
init() {
|
||||
global.strapi.config.set('middlewares', exampleMiddlewaresConfig);
|
||||
|
||||
return {
|
||||
uploadStream: jest.fn(),
|
||||
upload: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Upload plugin register function', () => {
|
||||
test('The upload plugin registers the /upload route', async () => {
|
||||
const registerRoute = jest.fn();
|
||||
|
||||
global.strapi = {
|
||||
dirs: { root: process.cwd(), public: join(process.cwd(), 'public') },
|
||||
plugins: { upload: {} },
|
||||
server: { app: { on: jest.fn() }, routes: registerRoute },
|
||||
admin: { services: { permission: { actionProvider: { registerMany: jest.fn() } } } },
|
||||
config: {
|
||||
get: jest.fn().mockReturnValueOnce({ provider: 'local' }),
|
||||
set: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
await bootstrap({ strapi });
|
||||
|
||||
expect(registerRoute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Strapi config can programatically be extended by providers', async () => {
|
||||
const setConfig = jest.fn();
|
||||
|
||||
global.strapi = {
|
||||
dirs: { root: process.cwd(), public: join(process.cwd(), 'public') },
|
||||
plugins: { upload: {} },
|
||||
server: { app: { on: jest.fn() }, routes: jest.fn() },
|
||||
admin: { services: { permission: { actionProvider: { registerMany: jest.fn() } } } },
|
||||
config: {
|
||||
get: jest.fn().mockReturnValueOnce({ provider: 'local' }),
|
||||
set: setConfig,
|
||||
},
|
||||
};
|
||||
|
||||
await bootstrap({ strapi });
|
||||
|
||||
expect(setConfig).toHaveBeenCalledWith('middlewares', exampleMiddlewaresConfig);
|
||||
});
|
||||
});
|
||||
62
packages/core/upload/server/bootstrap.js
vendored
62
packages/core/upload/server/bootstrap.js
vendored
@ -1,13 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = async ({ strapi }) => {
|
||||
// set plugin store
|
||||
const configurator = strapi.store({ type: 'plugin', name: 'upload', key: 'settings' });
|
||||
|
||||
strapi.plugin('upload').provider = createProvider(strapi.config.get('plugin.upload', {}));
|
||||
|
||||
// if provider config does not exist set one by default
|
||||
const config = await configurator.get();
|
||||
|
||||
@ -24,64 +20,6 @@ module.exports = async ({ strapi }) => {
|
||||
await registerPermissionActions();
|
||||
};
|
||||
|
||||
const createProvider = config => {
|
||||
const { providerOptions, actionOptions = {} } = config;
|
||||
|
||||
const providerName = _.toLower(config.provider);
|
||||
let provider;
|
||||
|
||||
let modulePath;
|
||||
try {
|
||||
modulePath = require.resolve(`@strapi/provider-upload-${providerName}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
modulePath = providerName;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
provider = require(modulePath);
|
||||
} catch (err) {
|
||||
const newError = new Error(`Could not load upload provider "${providerName}".`);
|
||||
newError.stack = err.stack;
|
||||
throw newError;
|
||||
}
|
||||
|
||||
const providerInstance = provider.init(providerOptions);
|
||||
|
||||
if (!providerInstance.delete) {
|
||||
throw new Error(`The upload provider "${providerName}" doesn't implement the delete method.`);
|
||||
}
|
||||
|
||||
if (!providerInstance.upload && !providerInstance.uploadStream) {
|
||||
throw new Error(
|
||||
`The upload provider "${providerName}" doesn't implement the uploadStream nor the upload method.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!providerInstance.uploadStream) {
|
||||
process.emitWarning(
|
||||
`The upload provider "${providerName}" doesn't implement the uploadStream function. Strapi will fallback on the upload method. Some performance issues may occur.`
|
||||
);
|
||||
}
|
||||
|
||||
const wrappedProvider = _.mapValues(providerInstance, (method, methodName) => {
|
||||
return async function(file, options = actionOptions[methodName]) {
|
||||
return providerInstance[methodName](file, options);
|
||||
};
|
||||
});
|
||||
|
||||
return Object.assign(Object.create(baseProvider), wrappedProvider);
|
||||
};
|
||||
|
||||
const baseProvider = {
|
||||
extend(obj) {
|
||||
Object.assign(this, obj);
|
||||
},
|
||||
};
|
||||
|
||||
const registerPermissionActions = async () => {
|
||||
const actions = [
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const registerUploadMiddleware = require('./middlewares/upload');
|
||||
|
||||
/**
|
||||
@ -7,9 +8,69 @@ const registerUploadMiddleware = require('./middlewares/upload');
|
||||
* @param {{ strapi: import('@strapi/strapi').Strapi }}
|
||||
*/
|
||||
module.exports = async ({ strapi }) => {
|
||||
strapi.plugin('upload').provider = createProvider(strapi.config.get('plugin.upload', {}));
|
||||
|
||||
await registerUploadMiddleware({ strapi });
|
||||
|
||||
if (strapi.plugin('graphql')) {
|
||||
require('./graphql')({ strapi });
|
||||
}
|
||||
};
|
||||
|
||||
const createProvider = config => {
|
||||
const { providerOptions, actionOptions = {} } = config;
|
||||
|
||||
const providerName = _.toLower(config.provider);
|
||||
let provider;
|
||||
|
||||
let modulePath;
|
||||
try {
|
||||
modulePath = require.resolve(`@strapi/provider-upload-${providerName}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'MODULE_NOT_FOUND') {
|
||||
modulePath = providerName;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
provider = require(modulePath);
|
||||
} catch (err) {
|
||||
const newError = new Error(`Could not load upload provider "${providerName}".`);
|
||||
newError.stack = err.stack;
|
||||
throw newError;
|
||||
}
|
||||
|
||||
const providerInstance = provider.init(providerOptions);
|
||||
|
||||
if (!providerInstance.delete) {
|
||||
throw new Error(`The upload provider "${providerName}" doesn't implement the delete method.`);
|
||||
}
|
||||
|
||||
if (!providerInstance.upload && !providerInstance.uploadStream) {
|
||||
throw new Error(
|
||||
`The upload provider "${providerName}" doesn't implement the uploadStream nor the upload method.`
|
||||
);
|
||||
}
|
||||
|
||||
if (!providerInstance.uploadStream) {
|
||||
process.emitWarning(
|
||||
`The upload provider "${providerName}" doesn't implement the uploadStream function. Strapi will fallback on the upload method. Some performance issues may occur.`
|
||||
);
|
||||
}
|
||||
|
||||
const wrappedProvider = _.mapValues(providerInstance, (method, methodName) => {
|
||||
return async function(file, options = actionOptions[methodName]) {
|
||||
return providerInstance[methodName](file, options);
|
||||
};
|
||||
});
|
||||
|
||||
return Object.assign(Object.create(baseProvider), wrappedProvider);
|
||||
};
|
||||
|
||||
const baseProvider = {
|
||||
extend(obj) {
|
||||
Object.assign(this, obj);
|
||||
},
|
||||
};
|
||||
|
||||
@ -60,10 +60,18 @@ describe('parseType', () => {
|
||||
});
|
||||
|
||||
it('Throws on invalid formator dates', () => {
|
||||
expect(() => parseType({ type: 'date', value: '-1029-11-02' })).toThrow();
|
||||
expect(() => parseType({ type: 'date', value: '2019-13-02' })).toThrow();
|
||||
expect(() => parseType({ type: 'date', value: '2019-12-32' })).toThrow();
|
||||
expect(() => parseType({ type: 'date', value: '2019-02-31' })).toThrow();
|
||||
expect(() => parseType({ type: 'date', value: '-1029-11-02' })).toThrow(
|
||||
'Invalid format, expected an ISO compatible date'
|
||||
);
|
||||
expect(() => parseType({ type: 'date', value: '2019-13-02' })).toThrow(
|
||||
'Invalid format, expected an ISO compatible date'
|
||||
);
|
||||
expect(() => parseType({ type: 'date', value: '2019-12-32' })).toThrow(
|
||||
'Invalid format, expected an ISO compatible date'
|
||||
);
|
||||
expect(() => parseType({ type: 'date', value: '2019-02-31' })).toThrow(
|
||||
'Invalid format, expected an ISO compatible date'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ const {
|
||||
toRegressedEnumValue,
|
||||
startsWithANumber,
|
||||
joinBy,
|
||||
toKebabCase,
|
||||
} = require('./string-formatting');
|
||||
const { removeUndefined } = require('./object-formatting');
|
||||
const { getConfigUrls, getAbsoluteAdminUrl, getAbsoluteServerUrl } = require('./config');
|
||||
@ -65,6 +66,7 @@ module.exports = {
|
||||
stringEquals,
|
||||
isKebabCase,
|
||||
isCamelCase,
|
||||
toKebabCase,
|
||||
contentTypes,
|
||||
webhook,
|
||||
env,
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
const _ = require('lodash');
|
||||
const { trimChars, trimCharsEnd, trimCharsStart } = require('lodash/fp');
|
||||
const slugify = require('@sindresorhus/slugify');
|
||||
const { kebabCase } = require('lodash');
|
||||
|
||||
const nameToSlug = (name, options = { separator: '-' }) => slugify(name, options);
|
||||
|
||||
@ -58,6 +59,8 @@ const joinBy = (joint, ...args) => {
|
||||
}, '');
|
||||
};
|
||||
|
||||
const toKebabCase = value => kebabCase(value);
|
||||
|
||||
module.exports = {
|
||||
nameToSlug,
|
||||
nameToCollectionName,
|
||||
@ -68,6 +71,7 @@ module.exports = {
|
||||
stringEquals,
|
||||
isCamelCase,
|
||||
isKebabCase,
|
||||
toKebabCase,
|
||||
toRegressedEnumValue,
|
||||
startsWithANumber,
|
||||
joinBy,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/utils",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Shared utilities for the Strapi packages",
|
||||
"keywords": [
|
||||
"strapi",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/generate-new",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Generate a new Strapi application.",
|
||||
"keywords": [
|
||||
"generate",
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
const tsUtils = require('@strapi/typescript-utils');
|
||||
|
||||
const getDestinationPrompts = require('./prompts/get-destination-prompts');
|
||||
const validateInput = require('./utils/validate-input');
|
||||
const getFilePath = require('./utils/get-file-path');
|
||||
|
||||
module.exports = plop => {
|
||||
@ -14,6 +15,7 @@ module.exports = plop => {
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Middleware name',
|
||||
validate: input => validateInput(input),
|
||||
},
|
||||
...getDestinationPrompts('middleware', plop.getDestBasePath(), { rootFolder: true }),
|
||||
],
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
const chalk = require('chalk');
|
||||
const { isUsingTypeScriptSync } = require('@strapi/typescript-utils');
|
||||
const { isKebabCase, toKebabCase } = require('@strapi/utils');
|
||||
|
||||
const validateInput = require('./utils/validate-input');
|
||||
|
||||
const logInstructions = (pluginName, { language }) => {
|
||||
const maxLength = ` resolve: './src/plugins/${pluginName}'`.length;
|
||||
@ -37,6 +40,7 @@ module.exports = plop => {
|
||||
type: 'input',
|
||||
name: 'pluginName',
|
||||
message: 'Plugin name',
|
||||
validate: input => validateInput(input),
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
@ -51,7 +55,14 @@ module.exports = plop => {
|
||||
const language = isTypescript ? 'ts' : 'js';
|
||||
const projectLanguage = isUsingTypeScriptSync(process.cwd()) ? 'ts' : 'js';
|
||||
|
||||
// TODO: Adds tsconfig & build command for TS plugins?
|
||||
if (!isKebabCase(answers.pluginName)) {
|
||||
answers.pluginName = toKebabCase(answers.pluginName);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`Strapi only supports kebab-cased names for plugins.\nYour plugin has been automatically renamed to "${answers.pluginName}".`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
const tsUtils = require('@strapi/typescript-utils');
|
||||
|
||||
const getDestinationPrompts = require('./prompts/get-destination-prompts');
|
||||
const validateInput = require('./utils/validate-input');
|
||||
const getFilePath = require('./utils/get-file-path');
|
||||
|
||||
module.exports = plop => {
|
||||
@ -14,6 +15,7 @@ module.exports = plop => {
|
||||
type: 'input',
|
||||
name: 'id',
|
||||
message: 'Policy name',
|
||||
validate: input => validateInput(input),
|
||||
},
|
||||
...getDestinationPrompts('policy', plop.getDestBasePath(), { rootFolder: true }),
|
||||
],
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const getFilePath = require('../get-file-path');
|
||||
|
||||
describe('Get-File-Path util', () => {
|
||||
test('with destination set as api', () => {
|
||||
const filePath = getFilePath('api');
|
||||
expect(filePath).toBe(`api/{{ api }}`);
|
||||
});
|
||||
|
||||
test('with destination set as plugin', () => {
|
||||
const filePath = getFilePath('plugin');
|
||||
expect(filePath).toBe(`plugins/{{ plugin }}/server`);
|
||||
});
|
||||
|
||||
test('with destination set as root', () => {
|
||||
const filePath = getFilePath('root');
|
||||
expect(filePath).toBe(`./`);
|
||||
});
|
||||
|
||||
test('with empty destination string', () => {
|
||||
const filePath = getFilePath('');
|
||||
expect(filePath).toBe(`api/{{ id }}`);
|
||||
});
|
||||
});
|
||||
@ -4,8 +4,9 @@
|
||||
"description": "This is the description of the plugin.",
|
||||
"strapi": {
|
||||
"name": "{{ pluginName }}",
|
||||
"description": "Description of {{ pluginName }} plugin",
|
||||
"kind": "plugin"
|
||||
"description": "Description of {{titleCase pluginName }} plugin",
|
||||
"kind": "plugin",
|
||||
"displayName": "{{titleCase pluginName }}"
|
||||
},
|
||||
"dependencies": {},
|
||||
"author": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/generators",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Interactive API generator.",
|
||||
"keywords": [
|
||||
"strapi",
|
||||
@ -29,9 +29,9 @@
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"@strapi/typescript-utils": "4.1.9",
|
||||
"@strapi/typescript-utils": "4.1.11",
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"chalk": "4.1.2",
|
||||
"fs-extra": "10.0.0",
|
||||
"node-plop": "0.26.3",
|
||||
|
||||
@ -147,8 +147,10 @@ describe('Build Component Schema', () => {
|
||||
|
||||
const expectedShape = {
|
||||
type: 'object',
|
||||
required: ['data'],
|
||||
properties: {
|
||||
data: {
|
||||
required: [],
|
||||
type: 'object',
|
||||
properties: { test: { type: 'string' } },
|
||||
},
|
||||
@ -237,6 +239,7 @@ describe('Build Component Schema', () => {
|
||||
|
||||
const expectedShape = {
|
||||
type: 'object',
|
||||
required: ['locale'],
|
||||
properties: { test: { type: 'string' } },
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-documentation",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Create an OpenAPI Document and visualize your API with SWAGGER UI.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -24,8 +24,8 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/helper-plugin": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/helper-plugin": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"bcryptjs": "2.4.3",
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"fs-extra": "10.0.0",
|
||||
|
||||
@ -48,7 +48,7 @@ module.exports = {
|
||||
properties: {
|
||||
data: {
|
||||
nullable: true,
|
||||
oneOf: [{ type: 'object' }, { type: 'array' }],
|
||||
oneOf: [{ type: 'object' }, { type: 'array', items: [] }],
|
||||
},
|
||||
error: {
|
||||
type: 'object',
|
||||
|
||||
@ -36,23 +36,24 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
||||
];
|
||||
const attributesForRequest = _.omit(attributes, attributesToOmit);
|
||||
|
||||
const requiredAttributes = Object.entries(attributesForRequest)
|
||||
.filter(([, attribute]) => attribute.required)
|
||||
.map(([attributeName, attribute]) => {
|
||||
return { [attributeName]: attribute };
|
||||
});
|
||||
// Get a list of required attribute names
|
||||
const requiredAttributes = Object.entries(attributesForRequest).reduce((acc, attribute) => {
|
||||
const [attributeKey, attributeValue] = attribute;
|
||||
|
||||
const requestAttributes =
|
||||
routeMethods.includes('POST') && requiredAttributes.length
|
||||
? Object.assign({}, ...requiredAttributes)
|
||||
: attributesForRequest;
|
||||
if (attributeValue.required) {
|
||||
acc.push(attributeKey);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (hasLocalizationPath) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}LocalizationRequest`]: {
|
||||
required: [...requiredAttributes, 'locale'],
|
||||
type: 'object',
|
||||
properties: cleanSchemaAttributes(requestAttributes, { isRequest: true }),
|
||||
properties: cleanSchemaAttributes(attributesForRequest, { isRequest: true }),
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -62,10 +63,12 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}Request`]: {
|
||||
type: 'object',
|
||||
required: ['data'],
|
||||
properties: {
|
||||
data: {
|
||||
required: requiredAttributes,
|
||||
type: 'object',
|
||||
properties: cleanSchemaAttributes(requestAttributes, { isRequest: true }),
|
||||
properties: cleanSchemaAttributes(attributesForRequest, { isRequest: true }),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-graphql",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Adds GraphQL endpoint with default API methods.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -30,7 +30,7 @@
|
||||
"@apollo/federation": "^0.28.0",
|
||||
"@graphql-tools/schema": "8.1.2",
|
||||
"@graphql-tools/utils": "^8.0.2",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"apollo-server-core": "3.1.2",
|
||||
"apollo-server-koa": "3.1.2",
|
||||
"glob": "^7.1.7",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-i18n",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "This plugin enables to create, to read and to update content in different languages, both from the Admin Panel and from the API",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -24,7 +24,7 @@
|
||||
"test:unit": "jest --verbose"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-sentry",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Send Strapi error events to Sentry",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-users-permissions",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Protect your API with a full-authentication process based on JWT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -27,8 +27,8 @@
|
||||
"test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/helper-plugin": "4.1.9",
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/helper-plugin": "4.1.11",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"bcryptjs": "2.4.3",
|
||||
"grant-koa": "5.4.8",
|
||||
"jsonwebtoken": "^8.1.0",
|
||||
|
||||
@ -378,7 +378,7 @@ module.exports = {
|
||||
throw new ValidationError('token.invalid');
|
||||
}
|
||||
|
||||
const user = await userService.fetch({ confirmationToken }, []);
|
||||
const [user] = await userService.fetchAll({ filters: { confirmationToken } });
|
||||
|
||||
if (!user) {
|
||||
throw new ValidationError('token.invalid');
|
||||
|
||||
@ -12,7 +12,7 @@ const { getService } = require('../utils');
|
||||
const { validateCreateUserBody, validateUpdateUserBody } = require('./validation/user');
|
||||
|
||||
const { sanitize } = utils;
|
||||
const { ApplicationError, ValidationError } = utils.errors;
|
||||
const { ApplicationError, ValidationError, NotFoundError } = utils.errors;
|
||||
|
||||
const sanitizeOutput = (user, ctx) => {
|
||||
const schema = strapi.getModel('plugin::users-permissions.user');
|
||||
@ -91,6 +91,9 @@ module.exports = {
|
||||
const { email, username, password } = ctx.request.body;
|
||||
|
||||
const user = await getService('user').fetch(id);
|
||||
if (!user) {
|
||||
throw new NotFoundError(`User not found`);
|
||||
}
|
||||
|
||||
await validateUpdateUserBody(ctx.request.body);
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ module.exports = ({ nexus, strapi }) => {
|
||||
async resolve(parent, args, context) {
|
||||
const { koaContext } = context;
|
||||
|
||||
koaContext.request.body = toPlainObject(args);
|
||||
koaContext.query = toPlainObject(args);
|
||||
|
||||
await strapi
|
||||
.plugin('users-permissions')
|
||||
|
||||
@ -19,7 +19,7 @@ module.exports = ({ nexus, strapi }) => {
|
||||
id: nonNull('ID'),
|
||||
},
|
||||
|
||||
description: 'Update an existing user',
|
||||
description: 'Delete an existing user',
|
||||
|
||||
async resolve(parent, args, context) {
|
||||
const { koaContext } = context;
|
||||
|
||||
@ -26,12 +26,12 @@ module.exports = ({ strapi }) => {
|
||||
|
||||
// Scoped auth for replaced CRUD operations
|
||||
// Role
|
||||
[`Mutation.${createRole}`]: { auth: { scope: [`${roleUID}.create`] } },
|
||||
[`Mutation.${updateRole}`]: { auth: { scope: [`${roleUID}.update`] } },
|
||||
[`Mutation.${deleteRole}`]: { auth: { scope: [`${roleUID}.delete`] } },
|
||||
[`Mutation.${createRole}`]: { auth: { scope: [`${roleUID}.createRole`] } },
|
||||
[`Mutation.${updateRole}`]: { auth: { scope: [`${roleUID}.updateRole`] } },
|
||||
[`Mutation.${deleteRole}`]: { auth: { scope: [`${roleUID}.deleteRole`] } },
|
||||
// User
|
||||
[`Mutation.${createUser}`]: { auth: { scope: [`${userUID}.create`] } },
|
||||
[`Mutation.${updateUser}`]: { auth: { scope: [`${userUID}.update`] } },
|
||||
[`Mutation.${deleteUser}`]: { auth: { scope: [`${userUID}.delete`] } },
|
||||
[`Mutation.${deleteUser}`]: { auth: { scope: [`${userUID}.destroy`] } },
|
||||
};
|
||||
};
|
||||
|
||||
@ -80,6 +80,22 @@ describe('Users API', () => {
|
||||
data.user = res.body;
|
||||
});
|
||||
|
||||
test('Updating unknown user returns 404', async () => {
|
||||
const res = await rq({
|
||||
method: 'PUT',
|
||||
url: '/users/99999999',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.body).toMatchObject({
|
||||
error: {
|
||||
message: 'User not found',
|
||||
name: 'NotFoundError',
|
||||
status: 404,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read users', () => {
|
||||
test('without filter', async () => {
|
||||
const res = await rq({
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-email-amazon-ses",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Amazon SES provider for strapi email",
|
||||
"keywords": [
|
||||
"email",
|
||||
@ -36,7 +36,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"node-ses": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-email-mailgun",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Mailgun provider for strapi email plugin",
|
||||
"keywords": [
|
||||
"email",
|
||||
@ -36,7 +36,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"mailgun-js": "0.22.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-email-nodemailer",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Nodemailer provider for Strapi 3",
|
||||
"keywords": [
|
||||
"strapi",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-email-sendgrid",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Sendgrid provider for strapi email",
|
||||
"keywords": [
|
||||
"email",
|
||||
@ -37,7 +37,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "7.4.7",
|
||||
"@strapi/utils": "4.1.9"
|
||||
"@strapi/utils": "4.1.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0 <=16.x.x",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-email-sendmail",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Sendmail provider for strapi email",
|
||||
"keywords": [
|
||||
"email",
|
||||
@ -35,7 +35,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"sendmail": "^1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -21,11 +21,13 @@ yarn add @strapi/provider-upload-aws-s3
|
||||
npm install @strapi/provider-upload-aws-s3 --save
|
||||
```
|
||||
|
||||
## Configurations
|
||||
## Configuration
|
||||
|
||||
Your configuration is passed down to the provider. (e.g: `new AWS.S3(config)`). You can see the complete list of options [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property)
|
||||
- `provider` defines the name of the provider
|
||||
- `providerOptions` is passed down during the construction of the provider. (ex: `new AWS.S3(config)`). [Complete list of options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property)
|
||||
- `actionOptions` is passed directly to the parameters to each method respectively. You can find the complete list of [upload/ uploadStream options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property) and [delete options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property)
|
||||
|
||||
See the [using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) documentation for information on installing and using a provider. And see the [environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables) for setting and using environment variables in your configs.
|
||||
See the [documentation about using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) for information on installing and using a provider. To understand how environment variables are used in Strapi, please refer to the [documentation about environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables).
|
||||
|
||||
### Provider Configuration
|
||||
|
||||
@ -45,6 +47,11 @@ module.exports = ({ env }) => ({
|
||||
Bucket: env('AWS_BUCKET'),
|
||||
},
|
||||
},
|
||||
actionOptions: {
|
||||
upload: {},
|
||||
uploadStream: {},
|
||||
delete: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
@ -90,6 +97,8 @@ module.exports = [
|
||||
];
|
||||
```
|
||||
|
||||
If you use dots in your bucket name, the url of the ressource is in directory style (`s3.yourRegion.amazonaws.com/your.bucket.name/image.jpg`) instead of `yourBucketName.s3.yourRegion.amazonaws.com/image.jpg`. Then only add `s3.yourRegion.amazonaws.com` to img-src and media-src directives.
|
||||
|
||||
## Required AWS Policy Actions
|
||||
|
||||
These are the minimum amount of permissions needed for this provider to work.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-upload-aws-s3",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "AWS S3 provider for strapi upload",
|
||||
"keywords": [
|
||||
"upload",
|
||||
|
||||
@ -21,13 +21,13 @@ yarn add @strapi/provider-upload-cloudinary
|
||||
npm install @strapi/provider-upload-cloudinary --save
|
||||
```
|
||||
|
||||
## Configurations
|
||||
## Configuration
|
||||
|
||||
Your configuration is passed down to the cloudinary configuration. (e.g: `cloudinary.config(config)`). You can see the complete list of options [here](https://cloudinary.com/documentation/cloudinary_sdks#configuration_parameters)
|
||||
- `provider` defines the name of the provider
|
||||
- `providerOptions` is passed down during the construction of the provider. (ex: `cloudinary.config`). [Complete list of options](https://cloudinary.com/documentation/cloudinary_sdks#configuration_parameters)
|
||||
- `actionOptions` is passed directly to each method respectively allowing for custom options. You can find the complete list of [upload/ uploadStream options](https://cloudinary.com/documentation/image_upload_api_reference#upload_optional_parameters) and [delete options](https://cloudinary.com/documentation/image_upload_api_reference#destroy_optional_parameters)
|
||||
|
||||
`actionOptions` are passed directly to the upload and delete functions respectively allowing for custom options such as folder, type, etc. You can see the complete list of upload options [here](https://cloudinary.com/documentation/image_upload_api_reference#upload_optional_parameters) and delete options [here](https://cloudinary.com/documentation/image_upload_api_reference#destroy_optional_parameters)
|
||||
|
||||
See the [using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) documentation for information on installing and using a provider. And see the [environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables) for setting and using environment variables in your configs.
|
||||
See the [documentation about using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) for information on installing and using a provider. To understand how environment variables are used in Strapi, please refer to the [documentation about environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables).
|
||||
|
||||
### Provider Configuration
|
||||
|
||||
@ -46,6 +46,7 @@ module.exports = ({ env }) => ({
|
||||
},
|
||||
actionOptions: {
|
||||
upload: {},
|
||||
uploadStream: {},
|
||||
delete: {},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-upload-cloudinary",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Cloudinary provider for strapi upload",
|
||||
"keywords": [
|
||||
"upload",
|
||||
@ -36,7 +36,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"cloudinary": "^1.25.1",
|
||||
"into-stream": "^5.1.0"
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-upload-local",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Local provider for strapi upload",
|
||||
"keywords": [
|
||||
"upload",
|
||||
@ -35,7 +35,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/utils": "4.1.9",
|
||||
"@strapi/utils": "4.1.11",
|
||||
"fs-extra": "10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -21,11 +21,13 @@ yarn add @strapi/provider-upload-rackspace
|
||||
npm install @strapi/provider-upload-rackspace --save
|
||||
```
|
||||
|
||||
## Configurations
|
||||
## Configuration
|
||||
|
||||
Your configuration is passed down to the client initialization. (e.g: `createClient(config)`). The implementation is based on the package `pkgcloud`. You can read the docs [here](https://github.com/pkgcloud/pkgcloud#storage).
|
||||
- `provider` defines the name of the provider
|
||||
- `providerOptions` is passed down during the construction of the provider. (ex: `createClient(config)`). [Complete list of options](https://github.com/pkgcloud/pkgcloud/blob/master/docs/providers/rackspace/README.md). The implementation is based on the package `pkgcloud`. [Documentation](https://github.com/pkgcloud/pkgcloud#storage)
|
||||
- `actionOptions` is passed directly to each method respectively allowing for custom options. You can find the complete list of [upload/ uploadStream options](https://github.com/pkgcloud/pkgcloud#upload-a-file) and [delete options](https://github.com/pkgcloud/pkgcloud/blob/master/docs/providers/rackspace/storage.md#clientremovefilecontainer-file-functionerr-result--)
|
||||
|
||||
See the [using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) documentation for information on installing and using a provider. And see the [environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables) for setting and using environment variables in your configs.
|
||||
See the [documentation about using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) for information on installing and using a provider. To understand how environment variables are used in Strapi, please refer to the [documentation about environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables).
|
||||
|
||||
### Provider Configuration
|
||||
|
||||
@ -43,6 +45,11 @@ module.exports = ({ env }) => ({
|
||||
region: env('RACKSPACE_REGION'),
|
||||
container: env('RACKSPACE_CONTAINER'),
|
||||
},
|
||||
actionOptions: {
|
||||
upload: {},
|
||||
uploadStream: {},
|
||||
delete: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/provider-upload-rackspace",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Rackspace provider for strapi upload",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/babel-plugin-switch-ee-ce",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"private": false,
|
||||
"description": "Babel plugin to switch from CE to EE at runtime",
|
||||
"repository": "git://github.com/strapi/strapi.git",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/logger",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Strapi's logger",
|
||||
"homepage": "https://strapi.io",
|
||||
"bugs": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/typescript-utils",
|
||||
"version": "4.1.9",
|
||||
"version": "4.1.11",
|
||||
"description": "Typescript support for Strapi",
|
||||
"keywords": [
|
||||
"strapi",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user