diff --git a/docs/3.0.0-beta.x/content-api/api-endpoints.md b/docs/3.0.0-beta.x/content-api/api-endpoints.md
index 1e0617564e..ce3cc5b751 100644
--- a/docs/3.0.0-beta.x/content-api/api-endpoints.md
+++ b/docs/3.0.0-beta.x/content-api/api-endpoints.md
@@ -162,6 +162,38 @@ Here is the list of endpoints generated for each of your **Content Types**.
:::
+::: tab Contact
+
+`Contact` **Content Type**
+
+
+
+| Method | Path | Description |
+| :----- | :--------- | :------------------------- |
+| GET | `/contact` | Get the contact content |
+| PUT | `/contact` | Update the contact content |
+| DELETE | `/contact` | Delete the contact content |
+
+
+
+:::
+
+::: tab About
+
+`About` **Content Type**
+
+
+
+| Method | Path | Description |
+| :----- | :------- | :----------------------- |
+| GET | `/about` | Get the about content |
+| PUT | `/about` | Update the about content |
+| DELETE | `/about` | Delete the about content |
+
+
+
+:::
+
::::
### Collection Types
diff --git a/docs/3.0.0-beta.x/getting-started/introduction.md b/docs/3.0.0-beta.x/getting-started/introduction.md
index 87ba6bcc35..fe24ef2e18 100644
--- a/docs/3.0.0-beta.x/getting-started/introduction.md
+++ b/docs/3.0.0-beta.x/getting-started/introduction.md
@@ -15,7 +15,7 @@ It's the origin purpose of the project.
### Custom content structure
-With the admin panel of Strapi, You can generate the admin panel in just a few clicks, and get your whole CMS setup in a few minutes.
+You can generate the admin panel in a few clicks and get your whole CMS setup in a few minutes.
### Manage content
diff --git a/docs/3.0.0-beta.x/installation/digitalocean-one-click.md b/docs/3.0.0-beta.x/installation/digitalocean-one-click.md
index c41cf57a44..7c5510dd62 100644
--- a/docs/3.0.0-beta.x/installation/digitalocean-one-click.md
+++ b/docs/3.0.0-beta.x/installation/digitalocean-one-click.md
@@ -29,6 +29,10 @@ To create a project head over to the Strapi [listing on the marketplace](https:/
Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets).
+::: warning
+After the droplet has started, it will take a few more minutes to finish the Strapi installation.
+:::
+
From here you will see the public ipv4 address that you can use to visit your Strapi application, just open that in a browser and it should ask you to create your first administrator!
You can also SSH into the virtual machine using `root` as the SSH user and your public ipv4 address, there is no password for SSH as DigitalOcean uses SSH keys by default with password authentication disabled.
@@ -101,13 +105,13 @@ upstream strapi {
### Strapi
-In the DigitalOcean one-click application a service user is used in which it's home directory is located at `/srv/strapi`. Likewise the actual Strapi application is located within this home directory at `/srv/strapi/strapi`.
+In the DigitalOcean one-click application a service user is used in which it's home directory is located at `/srv/strapi`. Likewise the actual Strapi application is located within this home directory at `/srv/strapi/strapi-development`.
Please note that with this application it is intially created and ran in the `development` environment to allow for creating models. **You should not use this directly in production**, it is recommended that you configure a private git repository to commit changes into and create a new application directory within the service user's home (Example: `/srv/strapi/strapi-production`). To run the new `production` or `staging` environments you can refer to the [PM2 Documentation](https://pm2.keymetrics.io/docs/usage/quick-start/#managing-processes).
## Using the Service Account
-By default the Strapi application will be running under a "service account", this is an account that is extremely limited into what it can do and access. The purpose of using a service account is to project your system from security threats.
+By default the Strapi application will be running under a "service account", this is an account that is extremely limited into what it can do and access. The purpose of using a service account is to help protect your system from security threats.
### Accessing the service account
@@ -137,8 +141,6 @@ Strapi will automatically start if the virtual machine is rebooted, you can also
## Changing the PostgreSQL Password
-Because of how the virtual machine is created, your database is setup with a long and random password, however for security you should change this password before moving into a production-like setting.
-
Use the following steps to change the PostgreSQL password and update Strapi's config:
- Make sure you are logged into the `strapi` service user
diff --git a/docs/3.0.0-beta.x/plugins/graphql.md b/docs/3.0.0-beta.x/plugins/graphql.md
index 52b5d71c76..e6da1c481f 100644
--- a/docs/3.0.0-beta.x/plugins/graphql.md
+++ b/docs/3.0.0-beta.x/plugins/graphql.md
@@ -46,7 +46,7 @@ By default, the [Shadow CRUD](#shadow-crud) feature is enabled and the GraphQL i
Security limits on maximum number of items in your response by default is limited to 100, however you can change this on the following config option `amountLimit`. This should only be changed after careful consideration of the drawbacks of a large query which can cause what would basically be a DDoS (Distributed Denial of Service). And may cause abnormal load on your Strapi server, as well as your database server.
-You can also enable the Apollo server tracing feature, which is supported by the playground to track the response time of each part of your query. To enable this feature just change/add the `"tracing": true` option in the GraphQL settings file. You can read more about the tracing feature from Apollo [here](https://www.apollographql.com/docs/engine/features/query-tracing.html).
+You can also enable the Apollo server tracing feature, which is supported by the playground to track the response time of each part of your query. To enable this feature just change/add the `"tracing": true` option in the GraphQL settings file. You can read more about the tracing feature from Apollo [here](https://www.apollographql.com/docs/apollo-server/federation/metrics/).
You can edit these configurations by creating following file.
diff --git a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js
index 55cf65c058..fe9dfa715f 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuIcon.js
@@ -5,14 +5,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const FaIcon = styled(({ small, ...props }) => )`
position: absolute;
- top: calc(50% - 0.9rem + 0.3rem);
- left: 1.6rem;
- margin-right: 1.2rem;
- margin-top: ${({ small }) => (small ? '.3rem' : null)};
- font-size: ${({ small }) => (small ? '.9rem' : '1.4rem')};
- width: 1.4rem;
- padding-bottom: 0.2rem;
- text-align: center;
+ top: ${({ small }) => (small ? 'calc(50% - 0.3rem)' : 'calc(50% - 0.9rem + 0.3rem)')};
+ left: ${({ small }) => (small ? '2.2rem' : '1.6rem')};
+ font-size: ${({ small }) => (small ? '.5rem' : '1.2rem')};
`;
const LeftMenuIcon = ({ icon }) => ;
diff --git a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js
index 2d90cde812..eff5ff3e35 100644
--- a/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js
+++ b/packages/strapi-admin/admin/src/components/LeftMenuLink/LeftMenuLinkContent.js
@@ -19,7 +19,7 @@ const LinkLabel = styled.span`
display: inline-block;
width: 100%;
padding-right: 1rem;
- padding-left: 2.6rem;
+ padding-left: 2.1rem;
`;
const LeftMenuLinkContent = ({
diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap
index e993002986..afa3f792a5 100644
--- a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap
+++ b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/tests/__snapshots__/index.test.js.snap
@@ -5,6 +5,7 @@ exports[`Admin | containers | ListView should match the snapshot 1`] = `
.c6 button {
width: 100%;
height: 54px;
+ border: 0;
border-top: 1px solid #aed4fb;
color: #007eff;
font-weight: 500;
diff --git a/packages/strapi-connector-bookshelf/lib/mount-models.js b/packages/strapi-connector-bookshelf/lib/mount-models.js
index 5246a99889..06aa5c2e51 100644
--- a/packages/strapi-connector-bookshelf/lib/mount-models.js
+++ b/packages/strapi-connector-bookshelf/lib/mount-models.js
@@ -661,9 +661,10 @@ module.exports = ({ models, target }, ctx) => {
await createComponentJoinTables({ definition, ORM });
} catch (err) {
- strapi.log.error(`Impossible to register the '${model}' model.`);
- strapi.log.error(err);
- strapi.stop();
+ if (err instanceof TypeError || err instanceof ReferenceError) {
+ strapi.stopWithError(err, `Impossible to register the '${model}' model.`);
+ }
+ strapi.stopWithError(err);
}
});
diff --git a/packages/strapi-connector-bookshelf/lib/populate.js b/packages/strapi-connector-bookshelf/lib/populate.js
index 0fabee5b39..50af973a4f 100644
--- a/packages/strapi-connector-bookshelf/lib/populate.js
+++ b/packages/strapi-connector-bookshelf/lib/populate.js
@@ -20,9 +20,8 @@ const populateFetch = (definition, options) => {
} else if (_.isEmpty(options.withRelated)) {
options.withRelated = populateComponents(definition);
} else {
- options.withRelated = formatPopulateOptions(
- definition,
- options.withRelated
+ options.withRelated = formatPopulateOptions(definition, options.withRelated).concat(
+ populateComponents(definition)
);
}
};
@@ -173,9 +172,7 @@ const formatPopulateOptions = (definition, withRelated) => {
continue;
}
- const assoc = tmpModel.associations.find(
- association => association.alias === part
- );
+ const assoc = tmpModel.associations.find(association => association.alias === part);
if (!assoc) return acc;
diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js
new file mode 100644
index 0000000000..dc51df3da4
--- /dev/null
+++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/Icon.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import styled from 'styled-components';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+import colors from '../../assets/styles/colors';
+
+const LeftMenuIcon = styled(({ ...props }) => )`
+ position: absolute;
+ top: calc(50% - 0.25rem);
+ left: 1.5rem;
+ font-size: 0.5rem;
+ color: ${colors.leftMenu.darkGrey};
+`;
+
+export default LeftMenuIcon;
diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js
index f340430f77..3bd532129c 100644
--- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js
+++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuLink/index.js
@@ -2,9 +2,12 @@ import React, { memo } from 'react';
import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
+import Icon from './Icon';
+
function LeftMenuLink({ children, to }) {
return (
+
{children}
);
diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js
index 185ac863d2..d3dcf6e62e 100644
--- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js
+++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/List.js
@@ -5,8 +5,7 @@ import colors from '../../assets/styles/colors';
const List = styled.ul`
margin-bottom: 0;
padding-left: 0;
- max-height: ${props =>
- props.numberOfVisibleItems ? `${props.numberOfVisibleItems * 35}px` : null};
+ max-height: 178px;
overflow-y: scroll;
li {
position: relative;
@@ -20,19 +19,6 @@ const List = styled.ul`
padding-left: 30px;
height: 34px;
border-radius: 2px;
- &::before {
- content: '•';
- position: absolute;
- top: calc(50% - 2px);
- left: 15px;
- font-weight: bold;
- display: block;
- width: 0.5em;
- height: 0.5em;
- color: ${colors.leftMenu.darkGrey};
- line-height: 5px;
- font-size: 10px;
- }
p {
color: ${colors.leftMenu.black};
font-size: 13px;
@@ -46,7 +32,7 @@ const List = styled.ul`
p {
font-weight: 600;
}
- &::before {
+ svg {
color: ${colors.leftMenu.black};
}
}
diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js
index 6dd93c0a09..74253e06d5 100644
--- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js
+++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuList/index.js
@@ -9,7 +9,7 @@ import LeftMenuHeader from '../LeftMenuHeader';
import List from './List';
import Wrapper from './Wrapper';
-function LeftMenuList({ customLink, links, title, searchable, numberOfVisibleItems }) {
+function LeftMenuList({ customLink, links, title, searchable }) {
const [search, setSearch] = useState('');
const { formatMessage } = useGlobalContext();
@@ -100,9 +100,7 @@ function LeftMenuList({ customLink, links, title, searchable, numberOfVisibleIte
-
- {getList().map((link, i) => renderCompo(link, i))}
-
+ {getList().map((link, i) => renderCompo(link, i))}
{Component && isValidElement() && }
@@ -114,7 +112,6 @@ LeftMenuList.defaultProps = {
links: [],
title: null,
searchable: false,
- numberOfVisibleItems: null,
};
LeftMenuList.propTypes = {
@@ -130,7 +127,6 @@ LeftMenuList.propTypes = {
id: PropTypes.string,
}),
searchable: PropTypes.bool,
- numberOfVisibleItems: PropTypes.number,
};
export default LeftMenuList;
diff --git a/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js b/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js
index 0418f98797..b2b9625359 100644
--- a/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js
+++ b/packages/strapi-helper-plugin/lib/src/components/LeftMenuSubList/Dropdown.js
@@ -11,8 +11,8 @@ const Dropdown = styled.div`
button {
position: relative;
padding: 0 10px 1px 15px;
- margin-bottom: 9px;
- margin-top: 9px;
+ margin-bottom: 10px;
+ margin-top: 8px;
font-weight: 600;
text-transform: capitalize;
&::before {
@@ -32,7 +32,7 @@ const Dropdown = styled.div`
padding-left: 10px;
}
.collapse {
- margin-bottom: 2px;
+ margin-bottom: 10px;
}
&:last-of-type {
margin-bottom: 0;
diff --git a/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js b/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js
index 50cc30de8a..90cad39125 100644
--- a/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js
+++ b/packages/strapi-plugin-content-manager/test/dynamiczones/with-media.test.e2e.js
@@ -18,9 +18,9 @@ const uploadImg = () => {
describe.each([
[
'CONTENT MANAGER',
- '/content-manager/explorer/application::withdynamiczone.withdynamiczone',
+ '/content-manager/explorer/application::withdynamiczonemedia.withdynamiczonemedia',
],
- ['GENERATED API', '/withdynamiczones'],
+ ['GENERATED API', '/withdynamiczonemedias'],
])('[%s] => Not required dynamiczone', (_, path) => {
beforeAll(async () => {
const token = await registerAndLogin();
@@ -61,17 +61,9 @@ describe.each([
},
});
- await modelsUtils.createContentTypeWithType(
- 'withdynamiczone',
- 'dynamiczone',
- {
- components: [
- 'default.single-media',
- 'default.multiple-media',
- 'default.with-nested',
- ],
- }
- );
+ await modelsUtils.createContentTypeWithType('withdynamiczonemedia', 'dynamiczone', {
+ components: ['default.single-media', 'default.multiple-media', 'default.with-nested'],
+ });
rq = authRq.defaults({
baseUrl: `http://localhost:1337${path}`,
@@ -82,7 +74,7 @@ describe.each([
await modelsUtils.deleteComponent('default.with-nested');
await modelsUtils.deleteComponent('default.single-media');
await modelsUtils.deleteComponent('default.multiple-media');
- await modelsUtils.deleteContentType('withdynamiczone');
+ await modelsUtils.deleteContentType('withdynamiczonemedia');
}, 60000);
describe('Contains components with medias', () => {
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js b/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js
index 9f482ff68d..f3851d908e 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/components/CustomLink/StyledCustomLink.js
@@ -8,7 +8,7 @@ import styled from 'styled-components';
const StyledCustomLink = styled.div`
padding-left: 15px;
- padding-top: 9px;
+ padding-top: 10px;
line-height: 0;
margin-left: -3px;
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js b/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js
index 1949e63bfe..76026aee67 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/components/ListRow/Wrapper.js
@@ -43,7 +43,6 @@ const Wrapper = styled.tr`
}}
p {
font-weight: 500;
- text-transform: capitalize;
}
}
td:last-child {
diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js
index da95d04055..8fc65a24c6 100644
--- a/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js
+++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/LeftMenu/index.js
@@ -169,7 +169,7 @@ function LeftMenu({ wait }) {
return (
{data.map(list => {
- return ;
+ return ;
})}
);
diff --git a/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js b/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js
index 6221592cdc..4ac7885a91 100644
--- a/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js
+++ b/packages/strapi-plugin-content-type-builder/controllers/validation/relations.js
@@ -35,5 +35,6 @@ module.exports = (obj, validNatures) => {
.test(isValidName)
.nullable(),
targetColumnName: yup.string().nullable(),
+ private: yup.boolean().nullable(),
};
};
diff --git a/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js b/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js
index d04817708d..d988636728 100644
--- a/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js
+++ b/packages/strapi-plugin-content-type-builder/services/schema-builder/index.js
@@ -60,18 +60,12 @@ function createSchemaBuilder({ components, contentTypes }) {
// init temporary ContentTypes
Object.keys(contentTypes).forEach(key => {
- tmpContentTypes.set(
- contentTypes[key].uid,
- createSchemaHandler(contentTypes[key])
- );
+ tmpContentTypes.set(contentTypes[key].uid, createSchemaHandler(contentTypes[key]));
});
// init temporary components
Object.keys(components).forEach(key => {
- tmpComponents.set(
- components[key].uid,
- createSchemaHandler(components[key])
- );
+ tmpComponents.set(components[key].uid, createSchemaHandler(components[key]));
});
return {
@@ -120,12 +114,14 @@ function createSchemaBuilder({ components, contentTypes }) {
columnName,
dominant,
autoPopulate,
+ private: isPrivate,
} = attribute;
const attr = {
unique: unique === true ? true : undefined,
columnName: columnName || undefined,
configurable: configurable === false ? false : undefined,
+ private: isPrivate === true ? true : undefined,
autoPopulate,
};
diff --git a/packages/strapi-plugin-graphql/services/type-builder.js b/packages/strapi-plugin-graphql/services/type-builder.js
index 647faae0c5..45943e8d69 100644
--- a/packages/strapi-plugin-graphql/services/type-builder.js
+++ b/packages/strapi-plugin-graphql/services/type-builder.js
@@ -243,7 +243,7 @@ module.exports = {
const inputs = `
input ${inputName} {
-
+
${Object.keys(model.attributes)
.map(attributeName => {
return `${attributeName}: ${this.convertType({
@@ -271,6 +271,7 @@ module.exports = {
.join('\n')}
}
`;
+
return inputs;
},
diff --git a/packages/strapi-plugin-graphql/services/type-definitions.js b/packages/strapi-plugin-graphql/services/type-definitions.js
index 1ef8790d8f..0d2dd8cb86 100644
--- a/packages/strapi-plugin-graphql/services/type-definitions.js
+++ b/packages/strapi-plugin-graphql/services/type-definitions.js
@@ -55,6 +55,7 @@ const buildTypeDefObj = model => {
// Change field definition for collection relations
associations
.filter(association => association.type === 'collection')
+ .filter(association => attributes[association.alias].private !== true)
.forEach(association => {
typeDef[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] =
typeDef[association.alias];
diff --git a/packages/strapi-plugin-graphql/services/utils.js b/packages/strapi-plugin-graphql/services/utils.js
index a3518b2f7d..610b5f9b08 100644
--- a/packages/strapi-plugin-graphql/services/utils.js
+++ b/packages/strapi-plugin-graphql/services/utils.js
@@ -33,7 +33,11 @@ const diffResolvers = (object, base) => {
Object.keys(object).forEach(type => {
Object.keys(object[type]).forEach(resolver => {
- if (!_.has(base, [type, resolver])) {
+ if (type === 'Query' || type === 'Mutation') {
+ if (!_.has(base, [type, resolver])) {
+ _.set(newObj, [type, resolver], _.get(object, [type, resolver]));
+ }
+ } else {
_.set(newObj, [type, resolver], _.get(object, [type, resolver]));
}
});
diff --git a/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js b/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js
index 35c6d346ec..99bacab6b6 100644
--- a/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js
+++ b/packages/strapi-plugin-graphql/test/graphqlRelations.test.e2e.js
@@ -9,7 +9,25 @@ let graphqlQuery;
let modelsUtils;
// utils
-const selectFields = doc => _.pick(doc, ['id', 'name']);
+const selectFields = doc => _.pick(doc, ['id', 'name', 'color']);
+
+const rgbColorComponent = {
+ attributes: {
+ name: {
+ type: 'text',
+ },
+ red: {
+ type: 'integer',
+ },
+ green: {
+ type: 'integer',
+ },
+ blue: {
+ type: 'integer',
+ },
+ },
+ name: 'rgbColor',
+};
const documentModel = {
attributes: {
@@ -37,6 +55,11 @@ const labelModel = {
target: 'application::document.document',
targetAttribute: 'labels',
},
+ color: {
+ type: 'component',
+ component: 'default.rgb-color',
+ repeatable: false,
+ },
},
connection: 'default',
name: 'label',
@@ -44,6 +67,41 @@ const labelModel = {
collectionName: '',
};
+const carModel = {
+ attributes: {
+ name: {
+ type: 'text',
+ },
+ },
+ connection: 'default',
+ name: 'car',
+ description: '',
+ collectionName: '',
+};
+
+const personModel = {
+ attributes: {
+ name: {
+ type: 'text',
+ },
+ privateName: {
+ type: 'text',
+ private: true,
+ },
+ privateCars: {
+ nature: 'oneToMany',
+ target: 'application::car.car',
+ dominant: false,
+ targetAttribute: 'person',
+ private: true,
+ },
+ },
+ connection: 'default',
+ name: 'person',
+ description: '',
+ collectionName: '',
+};
+
describe('Test Graphql Relations API End to End', () => {
beforeAll(async () => {
const token = await registerAndLogin();
@@ -59,17 +117,24 @@ describe('Test Graphql Relations API End to End', () => {
modelsUtils = createModelsUtils({ rq });
- await modelsUtils.createContentTypes([documentModel, labelModel]);
+ await modelsUtils.createComponent(rgbColorComponent);
+ await modelsUtils.createContentTypes([documentModel, labelModel, carModel, personModel]);
}, 60000);
- afterAll(() => modelsUtils.deleteContentTypes(['document', 'label']), 60000);
+ afterAll(() => modelsUtils.deleteContentTypes(['document', 'label', 'car', 'person']), 60000);
describe('Test relations features', () => {
let data = {
labels: [],
documents: [],
+ people: [],
+ cars: [],
};
- const labelsPayload = [{ name: 'label 1' }, { name: 'label 2' }];
+ const labelsPayload = [
+ { name: 'label 1', color: null },
+ { name: 'label 2', color: null },
+ { name: 'labelWithColor', color: { name: 'tomato', red: 255, green: 99, blue: 71 } },
+ ];
const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }];
test.each(labelsPayload)('Create label %o', async label => {
@@ -79,6 +144,12 @@ describe('Test Graphql Relations API End to End', () => {
createLabel(input: $input) {
label {
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
}
@@ -90,10 +161,8 @@ describe('Test Graphql Relations API End to End', () => {
},
});
- const { body } = res;
-
expect(res.statusCode).toBe(200);
- expect(body).toEqual({
+ expect(res.body).toEqual({
data: {
createLabel: {
label,
@@ -109,6 +178,12 @@ describe('Test Graphql Relations API End to End', () => {
labels {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
`,
@@ -124,52 +199,55 @@ describe('Test Graphql Relations API End to End', () => {
});
// assign for later use
- data.labels = res.body.data.labels;
+ data.labels = data.labels.concat(res.body.data.labels);
});
- test.each(documentsPayload)(
- 'Create document linked to every labels %o',
- async document => {
- const res = await graphqlQuery({
- query: /* GraphQL */ `
- mutation createDocument($input: createDocumentInput) {
- createDocument(input: $input) {
- document {
+ test.each(documentsPayload)('Create document linked to every labels %o', async document => {
+ const res = await graphqlQuery({
+ query: /* GraphQL */ `
+ mutation createDocument($input: createDocumentInput) {
+ createDocument(input: $input) {
+ document {
+ name
+ labels {
+ id
name
- labels {
- id
+ color {
name
+ red
+ green
+ blue
}
}
}
}
- `,
- variables: {
- input: {
- data: {
- ...document,
- labels: data.labels.map(t => t.id),
- },
+ }
+ `,
+ variables: {
+ input: {
+ data: {
+ ...document,
+ labels: data.labels.map(t => t.id),
},
},
- });
+ },
+ });
- const { body } = res;
+ const { body } = res;
- expect(res.statusCode).toBe(200);
+ expect(res.statusCode).toBe(200);
- expect(body).toMatchObject({
- data: {
- createDocument: {
- document: {
- ...selectFields(document),
- labels: expect.arrayContaining(data.labels.map(selectFields)),
- },
+ expect(body).toMatchObject({
+ data: {
+ createDocument: {
+ document: {
+ ...selectFields(document),
+ labels: expect.arrayContaining(data.labels.map(selectFields)),
},
},
- });
- }
- );
+ },
+ });
+ });
test('List documents with labels', async () => {
const res = await graphqlQuery({
@@ -181,6 +259,12 @@ describe('Test Graphql Relations API End to End', () => {
labels {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
}
@@ -212,6 +296,12 @@ describe('Test Graphql Relations API End to End', () => {
labels {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
documents {
id
name
@@ -229,9 +319,7 @@ describe('Test Graphql Relations API End to End', () => {
labels: expect.arrayContaining(
data.labels.map(label => ({
...selectFields(label),
- documents: expect.arrayContaining(
- data.documents.map(selectFields)
- ),
+ documents: expect.arrayContaining(data.documents.map(selectFields)),
}))
),
},
@@ -251,6 +339,12 @@ describe('Test Graphql Relations API End to End', () => {
labels {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
}
@@ -277,6 +371,12 @@ describe('Test Graphql Relations API End to End', () => {
labels {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
}
@@ -316,6 +416,12 @@ describe('Test Graphql Relations API End to End', () => {
label {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
}
@@ -350,6 +456,12 @@ describe('Test Graphql Relations API End to End', () => {
labels {
id
name
+ color {
+ name
+ red
+ green
+ blue
+ }
}
}
}
@@ -405,5 +517,184 @@ describe('Test Graphql Relations API End to End', () => {
});
}
});
+
+ test('Create person', async () => {
+ const person = {
+ name: 'Chuck Norris',
+ privateName: 'Jean-Eude',
+ };
+ const res = await graphqlQuery({
+ query: /* GraphQL */ `
+ mutation createPerson($input: createPersonInput) {
+ createPerson(input: $input) {
+ person {
+ id
+ name
+ }
+ }
+ }
+ `,
+ variables: {
+ input: {
+ data: person,
+ },
+ },
+ });
+
+ expect(res.statusCode).toBe(200);
+ expect(res.body).toEqual({
+ data: {
+ createPerson: {
+ person: {
+ id: expect.anything(),
+ name: person.name,
+ },
+ },
+ },
+ });
+ data.people.push(res.body.data.createPerson.person);
+ });
+
+ test("Can't list a private field", async () => {
+ const res = await graphqlQuery({
+ query: /* GraphQL */ `
+ {
+ people {
+ name
+ privateName
+ }
+ }
+ `,
+ });
+
+ expect(res.statusCode).toBe(400);
+ expect(res.body).toMatchObject({
+ errors: [
+ {
+ message: 'Cannot query field "privateName" on type "Person".',
+ },
+ ],
+ });
+ });
+
+ test('Create a car linked to a person (oneToMany)', async () => {
+ const car = {
+ name: 'Peugeot 508',
+ person: data.people[0].id,
+ };
+ const res = await graphqlQuery({
+ query: /* GraphQL */ `
+ mutation createCar($input: createCarInput) {
+ createCar(input: $input) {
+ car {
+ id
+ name
+ person {
+ id
+ name
+ }
+ }
+ }
+ }
+ `,
+ variables: {
+ input: {
+ data: {
+ ...car,
+ },
+ },
+ },
+ });
+
+ expect(res.statusCode).toBe(200);
+ expect(res.body).toMatchObject({
+ data: {
+ createCar: {
+ car: {
+ id: expect.anything(),
+ name: car.name,
+ person: data.people[0],
+ },
+ },
+ },
+ });
+
+ data.cars.push({ id: res.body.data.createCar.car.id });
+ });
+
+ test("Can't list a private oneToMany relation", async () => {
+ const res = await graphqlQuery({
+ query: /* GraphQL */ `
+ {
+ people {
+ name
+ privateCars
+ }
+ }
+ `,
+ });
+
+ expect(res.statusCode).toBe(400);
+ expect(res.body).toMatchObject({
+ errors: [
+ {
+ message: 'Cannot query field "privateCars" on type "Person".',
+ },
+ ],
+ });
+ });
+
+ test('Edit person/cars relations removes correctly a car', async () => {
+ const newPerson = {
+ name: 'Check Norris Junior',
+ privateCars: [],
+ };
+
+ const mutationRes = await graphqlQuery({
+ query: /* GraphQL */ `
+ mutation updatePerson($input: updatePersonInput) {
+ updatePerson(input: $input) {
+ person {
+ id
+ }
+ }
+ }
+ `,
+ variables: {
+ input: {
+ where: {
+ id: data.people[0].id,
+ },
+ data: {
+ ...newPerson,
+ },
+ },
+ },
+ });
+ expect(mutationRes.statusCode).toBe(200);
+
+ const queryRes = await graphqlQuery({
+ query: /* GraphQL */ `
+ query($id: ID!) {
+ car(id: $id) {
+ person {
+ id
+ }
+ }
+ }
+ `,
+ variables: {
+ id: data.cars[0].id,
+ },
+ });
+ expect(queryRes.statusCode).toBe(200);
+ expect(queryRes.body).toEqual({
+ data: {
+ car: {
+ person: null,
+ },
+ },
+ });
+ });
});
});
diff --git a/packages/strapi/lib/Strapi.js b/packages/strapi/lib/Strapi.js
index 8fc10a1c87..235a63a991 100644
--- a/packages/strapi/lib/Strapi.js
+++ b/packages/strapi/lib/Strapi.js
@@ -273,8 +273,11 @@ class Strapi extends EventEmitter {
};
}
- stopWithError(err) {
+ stopWithError(err, customMessage) {
this.log.debug(`⛔️ Server wasn't able to start properly.`);
+ if (customMessage) {
+ this.log.error(customMessage);
+ }
this.log.error(err);
return this.stop();
}