mirror of
https://github.com/strapi/strapi.git
synced 2025-08-15 12:18:38 +00:00
Merge branch 'front/ui-fixes' of github.com:strapi/strapi into front/ui-fixes
This commit is contained in:
commit
df1e81d317
@ -162,6 +162,38 @@ Here is the list of endpoints generated for each of your **Content Types**.
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
::: tab Contact
|
||||||
|
|
||||||
|
`Contact` **Content Type**
|
||||||
|
|
||||||
|
<div id="endpoint-table">
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| :----- | :--------- | :------------------------- |
|
||||||
|
| GET | `/contact` | Get the contact content |
|
||||||
|
| PUT | `/contact` | Update the contact content |
|
||||||
|
| DELETE | `/contact` | Delete the contact content |
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: tab About
|
||||||
|
|
||||||
|
`About` **Content Type**
|
||||||
|
|
||||||
|
<div id="endpoint-table">
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| :----- | :------- | :----------------------- |
|
||||||
|
| GET | `/about` | Get the about content |
|
||||||
|
| PUT | `/about` | Update the about content |
|
||||||
|
| DELETE | `/about` | Delete the about content |
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
::::
|
::::
|
||||||
|
|
||||||
### Collection Types
|
### Collection Types
|
||||||
|
@ -15,7 +15,7 @@ It's the origin purpose of the project.
|
|||||||
|
|
||||||
### Custom content structure
|
### 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
|
### Manage content
|
||||||
|
|
||||||
|
@ -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).
|
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!
|
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.
|
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
|
### 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).
|
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
|
## 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
|
### 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
|
## 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:
|
Use the following steps to change the PostgreSQL password and update Strapi's config:
|
||||||
|
|
||||||
- Make sure you are logged into the `strapi` service user
|
- Make sure you are logged into the `strapi` service user
|
||||||
|
@ -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.
|
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.
|
You can edit these configurations by creating following file.
|
||||||
|
|
||||||
@ -234,12 +234,7 @@ Return the second decade of users which have an email that contains `@strapi.io`
|
|||||||
|
|
||||||
```graphql
|
```graphql
|
||||||
query {
|
query {
|
||||||
users(
|
users(limit: 10, start: 10, sort: "username:asc", where: { email_contains: "@strapi.io" }) {
|
||||||
limit: 10
|
|
||||||
start: 10
|
|
||||||
sort: "username:asc"
|
|
||||||
where: { email_contains: "@strapi.io" }
|
|
||||||
) {
|
|
||||||
username
|
username
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
@ -720,10 +715,7 @@ module.exports = {
|
|||||||
Mutation: {
|
Mutation: {
|
||||||
createRestaurant: {
|
createRestaurant: {
|
||||||
description: 'Create a new restaurant',
|
description: 'Create a new restaurant',
|
||||||
policies: [
|
policies: ['plugins::users-permissions.isAuthenticated', 'global::logging'],
|
||||||
'plugins::users-permissions.isAuthenticated',
|
|
||||||
'global::logging',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -88,10 +88,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
idAttribute: _.get(definition, 'options.idAttribute', 'id'),
|
idAttribute: _.get(definition, 'options.idAttribute', 'id'),
|
||||||
associations: [],
|
associations: [],
|
||||||
defaults: Object.keys(definition.attributes).reduce((acc, current) => {
|
defaults: Object.keys(definition.attributes).reduce((acc, current) => {
|
||||||
if (
|
if (definition.attributes[current].type && definition.attributes[current].default) {
|
||||||
definition.attributes[current].type &&
|
|
||||||
definition.attributes[current].default
|
|
||||||
) {
|
|
||||||
acc[current] = definition.attributes[current].default;
|
acc[current] = definition.attributes[current].default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,9 +111,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
const pivot = this.pivot && !omitPivot && this.pivot.attributes;
|
const pivot = this.pivot && !omitPivot && this.pivot.attributes;
|
||||||
|
|
||||||
// Remove pivot attributes with prefix.
|
// Remove pivot attributes with prefix.
|
||||||
_.keys(pivot).forEach(
|
_.keys(pivot).forEach(key => delete attributes[`${PIVOT_PREFIX}${key}`]);
|
||||||
key => delete attributes[`${PIVOT_PREFIX}${key}`]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add pivot attributes without prefix.
|
// Add pivot attributes without prefix.
|
||||||
const pivotAttributes = _.mapKeys(
|
const pivotAttributes = _.mapKeys(
|
||||||
@ -147,16 +142,10 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { nature, verbose } =
|
const { nature, verbose } =
|
||||||
utilsModels.getNature(details, name, undefined, model.toLowerCase()) ||
|
utilsModels.getNature(details, name, undefined, model.toLowerCase()) || {};
|
||||||
{};
|
|
||||||
|
|
||||||
// Build associations key
|
// Build associations key
|
||||||
utilsModels.defineAssociations(
|
utilsModels.defineAssociations(model.toLowerCase(), definition, details, name);
|
||||||
model.toLowerCase(),
|
|
||||||
definition,
|
|
||||||
details,
|
|
||||||
name
|
|
||||||
);
|
|
||||||
|
|
||||||
let globalId;
|
let globalId;
|
||||||
const globalName = details.model || details.collection || '';
|
const globalName = details.model || details.collection || '';
|
||||||
@ -164,10 +153,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
// Exclude polymorphic association.
|
// Exclude polymorphic association.
|
||||||
if (globalName !== '*') {
|
if (globalName !== '*') {
|
||||||
globalId = details.plugin
|
globalId = details.plugin
|
||||||
? _.get(
|
? _.get(strapi.plugins, `${details.plugin}.models.${globalName.toLowerCase()}.globalId`)
|
||||||
strapi.plugins,
|
|
||||||
`${details.plugin}.models.${globalName.toLowerCase()}.globalId`
|
|
||||||
)
|
|
||||||
: _.get(strapi.models, `${globalName.toLowerCase()}.globalId`);
|
: _.get(strapi.models, `${globalName.toLowerCase()}.globalId`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,10 +211,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
}
|
}
|
||||||
case 'belongsTo': {
|
case 'belongsTo': {
|
||||||
loadedModel[name] = function() {
|
loadedModel[name] = function() {
|
||||||
return this.belongsTo(
|
return this.belongsTo(GLOBALS[globalId], _.get(details, 'columnName', name));
|
||||||
GLOBALS[globalId],
|
|
||||||
_.get(details, 'columnName', name)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -245,13 +228,9 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
details.isVirtual = true;
|
details.isVirtual = true;
|
||||||
|
|
||||||
if (nature === 'manyWay') {
|
if (nature === 'manyWay') {
|
||||||
const joinTableName = `${definition.collectionName}__${_.snakeCase(
|
const joinTableName = `${definition.collectionName}__${_.snakeCase(name)}`;
|
||||||
name
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
const foreignKey = `${singular(definition.collectionName)}_${
|
const foreignKey = `${singular(definition.collectionName)}_${definition.primaryKey}`;
|
||||||
definition.primaryKey
|
|
||||||
}`;
|
|
||||||
|
|
||||||
let otherKey = `${details.attribute}_${details.column}`;
|
let otherKey = `${details.attribute}_${details.column}`;
|
||||||
|
|
||||||
@ -277,10 +256,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
} else {
|
} else {
|
||||||
const joinTableName =
|
const joinTableName =
|
||||||
_.get(details, 'collectionName') ||
|
_.get(details, 'collectionName') ||
|
||||||
utilsModels.getCollectionName(
|
utilsModels.getCollectionName(targetModel.attributes[details.via], details);
|
||||||
targetModel.attributes[details.via],
|
|
||||||
details
|
|
||||||
);
|
|
||||||
|
|
||||||
const relationship = targetModel.attributes[details.via];
|
const relationship = targetModel.attributes[details.via];
|
||||||
|
|
||||||
@ -333,10 +309,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
details.via,
|
details.via,
|
||||||
`${definition.collectionName}`
|
`${definition.collectionName}`
|
||||||
).query(qb => {
|
).query(qb => {
|
||||||
qb.where(
|
qb.where(_.get(model, ['attributes', details.via, 'filter'], 'field'), name);
|
||||||
_.get(model, ['attributes', details.via, 'filter'], 'field'),
|
|
||||||
name
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@ -354,10 +327,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
details.via,
|
details.via,
|
||||||
`${definition.collectionName}`
|
`${definition.collectionName}`
|
||||||
).query(qb => {
|
).query(qb => {
|
||||||
qb.where(
|
qb.where(_.get(model, ['attributes', details.via, 'filter'], 'field'), name);
|
||||||
_.get(model, ['attributes', details.via, 'filter'], 'field'),
|
|
||||||
name
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@ -369,21 +339,17 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const morphValues = association.related.map(id => {
|
const morphValues = association.related.map(id => {
|
||||||
let models = Object.values(strapi.models).filter(
|
let models = Object.values(strapi.models).filter(model => model.globalId === id);
|
||||||
model => model.globalId === id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
models = Object.values(strapi.components).filter(
|
models = Object.values(strapi.components).filter(model => model.globalId === id);
|
||||||
model => model.globalId === id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
models = Object.keys(strapi.plugins).reduce((acc, current) => {
|
models = Object.keys(strapi.plugins).reduce((acc, current) => {
|
||||||
const models = Object.values(
|
const models = Object.values(strapi.plugins[current].models).filter(
|
||||||
strapi.plugins[current].models
|
model => model.globalId === id
|
||||||
).filter(model => model.globalId === id);
|
);
|
||||||
|
|
||||||
if (acc.length === 0 && models.length > 0) {
|
if (acc.length === 0 && models.length > 0) {
|
||||||
acc = models;
|
acc = models;
|
||||||
@ -395,9 +361,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
|
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
strapi.log.error(`Impossible to register the '${model}' model.`);
|
strapi.log.error(`Impossible to register the '${model}' model.`);
|
||||||
strapi.log.error(
|
strapi.log.error('The collection name cannot be found for the morphTo method.');
|
||||||
'The collection name cannot be found for the morphTo method.'
|
|
||||||
);
|
|
||||||
strapi.stop();
|
strapi.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,10 +381,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
related: function() {
|
related: function() {
|
||||||
return this.morphTo(
|
return this.morphTo(
|
||||||
name,
|
name,
|
||||||
...association.related.map((id, index) => [
|
...association.related.map((id, index) => [GLOBALS[id], morphValues[index]])
|
||||||
GLOBALS[id],
|
|
||||||
morphValues[index],
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -434,16 +395,10 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
// Upload has many Upload_morph that morph to different model.
|
// Upload has many Upload_morph that morph to different model.
|
||||||
loadedModel[name] = function() {
|
loadedModel[name] = function() {
|
||||||
if (verbose === 'belongsToMorph') {
|
if (verbose === 'belongsToMorph') {
|
||||||
return this.hasOne(
|
return this.hasOne(GLOBALS[options.tableName], `${definition.collectionName}_id`);
|
||||||
GLOBALS[options.tableName],
|
|
||||||
`${definition.collectionName}_id`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.hasMany(
|
return this.hasMany(GLOBALS[options.tableName], `${definition.collectionName}_id`);
|
||||||
GLOBALS[options.tableName],
|
|
||||||
`${definition.collectionName}_id`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -469,9 +424,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
return _.mapKeys(params, (value, key) => {
|
return _.mapKeys(params, (value, key) => {
|
||||||
const attr = definition.attributes[key] || {};
|
const attr = definition.attributes[key] || {};
|
||||||
|
|
||||||
return _.isPlainObject(attr) && _.isString(attr['columnName'])
|
return _.isPlainObject(attr) && _.isString(attr['columnName']) ? attr['columnName'] : key;
|
||||||
? attr['columnName']
|
|
||||||
: key;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -504,20 +457,16 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
case 'component': {
|
case 'component': {
|
||||||
const { repeatable } = attr;
|
const { repeatable } = attr;
|
||||||
|
|
||||||
const components = relations[key]
|
const components = relations[key].toJSON().map(el => el.component);
|
||||||
.toJSON()
|
|
||||||
.map(el => el.component);
|
|
||||||
|
|
||||||
attrs[key] =
|
attrs[key] = repeatable === true ? components : _.first(components) || null;
|
||||||
repeatable === true ? components : _.first(components) || null;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'dynamiczone': {
|
case 'dynamiczone': {
|
||||||
attrs[key] = relations[key].toJSON().map(el => {
|
attrs[key] = relations[key].toJSON().map(el => {
|
||||||
const componentKey = Object.keys(strapi.components).find(
|
const componentKey = Object.keys(strapi.components).find(
|
||||||
key =>
|
key => strapi.components[key].collectionName === el.component_type
|
||||||
strapi.components[key].collectionName === el.component_type
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -540,9 +489,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
|
|
||||||
if (relation) {
|
if (relation) {
|
||||||
// Extract raw JSON data.
|
// Extract raw JSON data.
|
||||||
attrs[association.alias] = relation.toJSON
|
attrs[association.alias] = relation.toJSON ? relation.toJSON(options) : relation;
|
||||||
? relation.toJSON(options)
|
|
||||||
: relation;
|
|
||||||
|
|
||||||
// Retrieve opposite model.
|
// Retrieve opposite model.
|
||||||
const model = strapi.getModel(
|
const model = strapi.getModel(
|
||||||
@ -553,8 +500,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
// Reformat data by bypassing the many-to-many relationship.
|
// Reformat data by bypassing the many-to-many relationship.
|
||||||
switch (association.nature) {
|
switch (association.nature) {
|
||||||
case 'oneToManyMorph':
|
case 'oneToManyMorph':
|
||||||
attrs[association.alias] =
|
attrs[association.alias] = attrs[association.alias][model.collectionName] || null;
|
||||||
attrs[association.alias][model.collectionName] || null;
|
|
||||||
break;
|
break;
|
||||||
case 'manyToManyMorph':
|
case 'manyToManyMorph':
|
||||||
attrs[association.alias] = attrs[association.alias].map(
|
attrs[association.alias] = attrs[association.alias].map(
|
||||||
@ -602,9 +548,7 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
|
|
||||||
if (relation) {
|
if (relation) {
|
||||||
// Extract raw JSON data.
|
// Extract raw JSON data.
|
||||||
attrs[association.alias] = relation.toJSON
|
attrs[association.alias] = relation.toJSON ? relation.toJSON(options) : relation;
|
||||||
? relation.toJSON(options)
|
|
||||||
: relation;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -717,9 +661,10 @@ module.exports = ({ models, target }, ctx) => {
|
|||||||
|
|
||||||
await createComponentJoinTables({ definition, ORM });
|
await createComponentJoinTables({ definition, ORM });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
strapi.log.error(`Impossible to register the '${model}' model.`);
|
if (err instanceof TypeError || err instanceof ReferenceError) {
|
||||||
strapi.log.error(err);
|
strapi.stopWithError(err, `Impossible to register the '${model}' model.`);
|
||||||
strapi.stop();
|
}
|
||||||
|
strapi.stopWithError(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,9 +20,8 @@ const populateFetch = (definition, options) => {
|
|||||||
} else if (_.isEmpty(options.withRelated)) {
|
} else if (_.isEmpty(options.withRelated)) {
|
||||||
options.withRelated = populateComponents(definition);
|
options.withRelated = populateComponents(definition);
|
||||||
} else {
|
} else {
|
||||||
options.withRelated = formatPopulateOptions(
|
options.withRelated = formatPopulateOptions(definition, options.withRelated).concat(
|
||||||
definition,
|
populateComponents(definition)
|
||||||
options.withRelated
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -173,9 +172,7 @@ const formatPopulateOptions = (definition, withRelated) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assoc = tmpModel.associations.find(
|
const assoc = tmpModel.associations.find(association => association.alias === part);
|
||||||
association => association.alias === part
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!assoc) return acc;
|
if (!assoc) return acc;
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ const uploadImg = () => {
|
|||||||
describe.each([
|
describe.each([
|
||||||
[
|
[
|
||||||
'CONTENT MANAGER',
|
'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) => {
|
])('[%s] => Not required dynamiczone', (_, path) => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const token = await registerAndLogin();
|
const token = await registerAndLogin();
|
||||||
@ -61,17 +61,9 @@ describe.each([
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await modelsUtils.createContentTypeWithType(
|
await modelsUtils.createContentTypeWithType('withdynamiczonemedia', 'dynamiczone', {
|
||||||
'withdynamiczone',
|
components: ['default.single-media', 'default.multiple-media', 'default.with-nested'],
|
||||||
'dynamiczone',
|
});
|
||||||
{
|
|
||||||
components: [
|
|
||||||
'default.single-media',
|
|
||||||
'default.multiple-media',
|
|
||||||
'default.with-nested',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
rq = authRq.defaults({
|
rq = authRq.defaults({
|
||||||
baseUrl: `http://localhost:1337${path}`,
|
baseUrl: `http://localhost:1337${path}`,
|
||||||
@ -82,7 +74,7 @@ describe.each([
|
|||||||
await modelsUtils.deleteComponent('default.with-nested');
|
await modelsUtils.deleteComponent('default.with-nested');
|
||||||
await modelsUtils.deleteComponent('default.single-media');
|
await modelsUtils.deleteComponent('default.single-media');
|
||||||
await modelsUtils.deleteComponent('default.multiple-media');
|
await modelsUtils.deleteComponent('default.multiple-media');
|
||||||
await modelsUtils.deleteContentType('withdynamiczone');
|
await modelsUtils.deleteContentType('withdynamiczonemedia');
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|
||||||
describe('Contains components with medias', () => {
|
describe('Contains components with medias', () => {
|
||||||
|
@ -43,7 +43,6 @@ const Wrapper = styled.tr`
|
|||||||
}}
|
}}
|
||||||
p {
|
p {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
td:last-child {
|
td:last-child {
|
||||||
@ -53,11 +52,7 @@ const Wrapper = styled.tr`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.relation-row {
|
&.relation-row {
|
||||||
background: linear-gradient(
|
background: linear-gradient(135deg, rgba(28, 93, 231, 0.05), rgba(239, 243, 253, 0));
|
||||||
135deg,
|
|
||||||
rgba(28, 93, 231, 0.05),
|
|
||||||
rgba(239, 243, 253, 0)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
&.clickable {
|
&.clickable {
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -18,26 +18,20 @@ module.exports = {
|
|||||||
return ctx.send({ error }, 400);
|
return ctx.send({ error }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentTypeService =
|
const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
|
||||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
|
||||||
|
|
||||||
const contentTypes = Object.keys(strapi.contentTypes)
|
const contentTypes = Object.keys(strapi.contentTypes)
|
||||||
.filter(uid => {
|
.filter(uid => {
|
||||||
if (uid.startsWith('strapi::')) return false;
|
if (uid.startsWith('strapi::')) return false;
|
||||||
if (uid === 'plugins::upload.file') return false; // TODO: add a flag in the content type instead
|
if (uid === 'plugins::upload.file') return false; // TODO: add a flag in the content type instead
|
||||||
|
|
||||||
if (
|
if (kind && _.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind) {
|
||||||
kind &&
|
|
||||||
_.get(strapi.contentTypes[uid], 'kind', 'collectionType') !== kind
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map(uid =>
|
.map(uid => contentTypeService.formatContentType(strapi.contentTypes[uid]));
|
||||||
contentTypeService.formatContentType(strapi.contentTypes[uid])
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.send({
|
ctx.send({
|
||||||
data: contentTypes,
|
data: contentTypes,
|
||||||
@ -53,8 +47,7 @@ module.exports = {
|
|||||||
return ctx.send({ error: 'contentType.notFound' }, 404);
|
return ctx.send({ error: 'contentType.notFound' }, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentTypeService =
|
const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
|
||||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
|
||||||
|
|
||||||
ctx.send({ data: contentTypeService.formatContentType(contentType) });
|
ctx.send({ data: contentTypeService.formatContentType(contentType) });
|
||||||
},
|
},
|
||||||
@ -71,8 +64,7 @@ module.exports = {
|
|||||||
try {
|
try {
|
||||||
strapi.reload.isWatching = false;
|
strapi.reload.isWatching = false;
|
||||||
|
|
||||||
const contentTypeService =
|
const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
|
||||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
|
||||||
|
|
||||||
const component = await contentTypeService.createContentType({
|
const component = await contentTypeService.createContentType({
|
||||||
contentType: body.contentType,
|
contentType: body.contentType,
|
||||||
@ -112,8 +104,7 @@ module.exports = {
|
|||||||
try {
|
try {
|
||||||
strapi.reload.isWatching = false;
|
strapi.reload.isWatching = false;
|
||||||
|
|
||||||
const contentTypeService =
|
const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
|
||||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
|
||||||
|
|
||||||
const component = await contentTypeService.editContentType(uid, {
|
const component = await contentTypeService.editContentType(uid, {
|
||||||
contentType: body.contentType,
|
contentType: body.contentType,
|
||||||
@ -139,8 +130,7 @@ module.exports = {
|
|||||||
try {
|
try {
|
||||||
strapi.reload.isWatching = false;
|
strapi.reload.isWatching = false;
|
||||||
|
|
||||||
const contentTypeService =
|
const contentTypeService = strapi.plugins['content-type-builder'].services.contenttypes;
|
||||||
strapi.plugins['content-type-builder'].services.contenttypes;
|
|
||||||
|
|
||||||
const component = await contentTypeService.deleteContentType(uid);
|
const component = await contentTypeService.deleteContentType(uid);
|
||||||
|
|
||||||
|
@ -35,5 +35,6 @@ module.exports = (obj, validNatures) => {
|
|||||||
.test(isValidName)
|
.test(isValidName)
|
||||||
.nullable(),
|
.nullable(),
|
||||||
targetColumnName: yup.string().nullable(),
|
targetColumnName: yup.string().nullable(),
|
||||||
|
private: yup.boolean().nullable(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -60,18 +60,12 @@ function createSchemaBuilder({ components, contentTypes }) {
|
|||||||
|
|
||||||
// init temporary ContentTypes
|
// init temporary ContentTypes
|
||||||
Object.keys(contentTypes).forEach(key => {
|
Object.keys(contentTypes).forEach(key => {
|
||||||
tmpContentTypes.set(
|
tmpContentTypes.set(contentTypes[key].uid, createSchemaHandler(contentTypes[key]));
|
||||||
contentTypes[key].uid,
|
|
||||||
createSchemaHandler(contentTypes[key])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// init temporary components
|
// init temporary components
|
||||||
Object.keys(components).forEach(key => {
|
Object.keys(components).forEach(key => {
|
||||||
tmpComponents.set(
|
tmpComponents.set(components[key].uid, createSchemaHandler(components[key]));
|
||||||
components[key].uid,
|
|
||||||
createSchemaHandler(components[key])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -120,12 +114,14 @@ function createSchemaBuilder({ components, contentTypes }) {
|
|||||||
columnName,
|
columnName,
|
||||||
dominant,
|
dominant,
|
||||||
autoPopulate,
|
autoPopulate,
|
||||||
|
private: isPrivate,
|
||||||
} = attribute;
|
} = attribute;
|
||||||
|
|
||||||
const attr = {
|
const attr = {
|
||||||
unique: unique === true ? true : undefined,
|
unique: unique === true ? true : undefined,
|
||||||
columnName: columnName || undefined,
|
columnName: columnName || undefined,
|
||||||
configurable: configurable === false ? false : undefined,
|
configurable: configurable === false ? false : undefined,
|
||||||
|
private: isPrivate === true ? true : undefined,
|
||||||
autoPopulate,
|
autoPopulate,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,8 +16,7 @@ const GraphQLLong = require('graphql-type-long');
|
|||||||
const Time = require('../types/time');
|
const Time = require('../types/time');
|
||||||
const { toSingular, toInputName } = require('./naming');
|
const { toSingular, toInputName } = require('./naming');
|
||||||
|
|
||||||
const isScalarAttribute = ({ type }) =>
|
const isScalarAttribute = ({ type }) => type && !['component', 'dynamiczone'].includes(type);
|
||||||
type && !['component', 'dynamiczone'].includes(type);
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
@ -90,9 +89,7 @@ module.exports = {
|
|||||||
typeName =
|
typeName =
|
||||||
action === 'update'
|
action === 'update'
|
||||||
? `edit${_.upperFirst(toSingular(globalId))}Input`
|
? `edit${_.upperFirst(toSingular(globalId))}Input`
|
||||||
: `${_.upperFirst(toSingular(globalId))}Input${
|
: `${_.upperFirst(toSingular(globalId))}Input${required ? '!' : ''}`;
|
||||||
required ? '!' : ''
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repeatable === true) {
|
if (repeatable === true) {
|
||||||
@ -104,9 +101,7 @@ module.exports = {
|
|||||||
if (attribute.type === 'dynamiczone') {
|
if (attribute.type === 'dynamiczone') {
|
||||||
const { required } = attribute;
|
const { required } = attribute;
|
||||||
|
|
||||||
const unionName = `${modelName}${_.upperFirst(
|
const unionName = `${modelName}${_.upperFirst(_.camelCase(attributeName))}DynamicZone`;
|
||||||
_.camelCase(attributeName)
|
|
||||||
)}DynamicZone`;
|
|
||||||
|
|
||||||
let typeName = unionName;
|
let typeName = unionName;
|
||||||
|
|
||||||
@ -202,9 +197,7 @@ module.exports = {
|
|||||||
addPolymorphicUnionType(definition) {
|
addPolymorphicUnionType(definition) {
|
||||||
const types = graphql
|
const types = graphql
|
||||||
.parse(definition)
|
.parse(definition)
|
||||||
.definitions.filter(
|
.definitions.filter(def => def.kind === 'ObjectTypeDefinition' && def.name.value !== 'Query')
|
||||||
def => def.kind === 'ObjectTypeDefinition' && def.name.value !== 'Query'
|
|
||||||
)
|
|
||||||
.map(def => def.name.value);
|
.map(def => def.name.value);
|
||||||
|
|
||||||
if (types.length > 0) {
|
if (types.length > 0) {
|
||||||
@ -278,6 +271,7 @@ module.exports = {
|
|||||||
.join('\n')}
|
.join('\n')}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return inputs;
|
return inputs;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -12,12 +12,7 @@ const DynamicZoneScalar = require('../types/dynamiczoneScalar');
|
|||||||
|
|
||||||
const { formatModelConnectionsGQL } = require('./build-aggregation');
|
const { formatModelConnectionsGQL } = require('./build-aggregation');
|
||||||
const types = require('./type-builder');
|
const types = require('./type-builder');
|
||||||
const {
|
const { mergeSchemas, convertToParams, convertToQuery, amountLimiting } = require('./utils');
|
||||||
mergeSchemas,
|
|
||||||
convertToParams,
|
|
||||||
convertToQuery,
|
|
||||||
amountLimiting,
|
|
||||||
} = require('./utils');
|
|
||||||
const { toSDL, getTypeDescription } = require('./schema-definitions');
|
const { toSDL, getTypeDescription } = require('./schema-definitions');
|
||||||
const { toSingular, toPlural } = require('./naming');
|
const { toSingular, toPlural } = require('./naming');
|
||||||
const { buildQuery, buildMutation } = require('./resolvers-builder');
|
const { buildQuery, buildMutation } = require('./resolvers-builder');
|
||||||
@ -60,10 +55,10 @@ const buildTypeDefObj = model => {
|
|||||||
// Change field definition for collection relations
|
// Change field definition for collection relations
|
||||||
associations
|
associations
|
||||||
.filter(association => association.type === 'collection')
|
.filter(association => association.type === 'collection')
|
||||||
|
.filter(association => attributes[association.alias].private !== true)
|
||||||
.forEach(association => {
|
.forEach(association => {
|
||||||
typeDef[
|
typeDef[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] =
|
||||||
`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`
|
typeDef[association.alias];
|
||||||
] = typeDef[association.alias];
|
|
||||||
|
|
||||||
delete typeDef[association.alias];
|
delete typeDef[association.alias];
|
||||||
});
|
});
|
||||||
@ -90,9 +85,7 @@ const generateDynamicZoneDefinitions = (attributes, globalId, schema) => {
|
|||||||
.forEach(attribute => {
|
.forEach(attribute => {
|
||||||
const { components } = attributes[attribute];
|
const { components } = attributes[attribute];
|
||||||
|
|
||||||
const typeName = `${globalId}${_.upperFirst(
|
const typeName = `${globalId}${_.upperFirst(_.camelCase(attribute))}DynamicZone`;
|
||||||
_.camelCase(attribute)
|
|
||||||
)}DynamicZone`;
|
|
||||||
|
|
||||||
if (components.length === 0) {
|
if (components.length === 0) {
|
||||||
// Create dummy type because graphql doesn't support empty ones
|
// Create dummy type because graphql doesn't support empty ones
|
||||||
@ -111,9 +104,7 @@ const generateDynamicZoneDefinitions = (attributes, globalId, schema) => {
|
|||||||
return compo.globalId;
|
return compo.globalId;
|
||||||
});
|
});
|
||||||
|
|
||||||
const unionType = `union ${typeName} = ${componentsTypeNames.join(
|
const unionType = `union ${typeName} = ${componentsTypeNames.join(' | ')}`;
|
||||||
' | '
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
schema.definition += `\n${unionType}\n`;
|
schema.definition += `\n${unionType}\n`;
|
||||||
}
|
}
|
||||||
@ -137,8 +128,7 @@ const generateDynamicZoneDefinitions = (attributes, globalId, schema) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildAssocResolvers = model => {
|
const buildAssocResolvers = model => {
|
||||||
const contentManager =
|
const contentManager = strapi.plugins['content-manager'].services['contentmanager'];
|
||||||
strapi.plugins['content-manager'].services['contentmanager'];
|
|
||||||
|
|
||||||
const { primaryKey, associations = [] } = model;
|
const { primaryKey, associations = [] } = model;
|
||||||
|
|
||||||
@ -194,8 +184,7 @@ const buildAssocResolvers = model => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
((association.nature === 'manyToMany' &&
|
((association.nature === 'manyToMany' && association.dominant) ||
|
||||||
association.dominant) ||
|
|
||||||
association.nature === 'manyWay') &&
|
association.nature === 'manyWay') &&
|
||||||
_.has(obj, association.alias) // if populated
|
_.has(obj, association.alias) // if populated
|
||||||
) {
|
) {
|
||||||
@ -203,31 +192,21 @@ const buildAssocResolvers = model => {
|
|||||||
queryOpts,
|
queryOpts,
|
||||||
['query', targetModel.primaryKey],
|
['query', targetModel.primaryKey],
|
||||||
obj[association.alias]
|
obj[association.alias]
|
||||||
? obj[association.alias]
|
? obj[association.alias].map(val => val[targetModel.primaryKey] || val).sort()
|
||||||
.map(val => val[targetModel.primaryKey] || val)
|
|
||||||
.sort()
|
|
||||||
: []
|
: []
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_.set(
|
_.set(queryOpts, ['query', association.via], obj[targetModel.primaryKey]);
|
||||||
queryOpts,
|
|
||||||
['query', association.via],
|
|
||||||
obj[targetModel.primaryKey]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return association.model
|
return association.model
|
||||||
? strapi.plugins.graphql.services['data-loaders'].loaders[
|
? strapi.plugins.graphql.services['data-loaders'].loaders[targetModel.uid].load({
|
||||||
targetModel.uid
|
|
||||||
].load({
|
|
||||||
params,
|
params,
|
||||||
options: queryOpts,
|
options: queryOpts,
|
||||||
single: true,
|
single: true,
|
||||||
})
|
})
|
||||||
: strapi.plugins.graphql.services['data-loaders'].loaders[
|
: strapi.plugins.graphql.services['data-loaders'].loaders[targetModel.uid].load({
|
||||||
targetModel.uid
|
|
||||||
].load({
|
|
||||||
options: queryOpts,
|
options: queryOpts,
|
||||||
association,
|
association,
|
||||||
});
|
});
|
||||||
@ -308,9 +287,7 @@ const buildSingleType = model => {
|
|||||||
|
|
||||||
const singularName = toSingular(modelName);
|
const singularName = toSingular(modelName);
|
||||||
|
|
||||||
const _schema = _.cloneDeep(
|
const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
|
||||||
_.get(strapi.plugins, 'graphql.config._schema.graphql', {})
|
|
||||||
);
|
|
||||||
|
|
||||||
const globalType = _.get(_schema, ['type', model.globalId], {});
|
const globalType = _.get(_schema, ['type', model.globalId], {});
|
||||||
|
|
||||||
@ -357,9 +334,7 @@ const buildCollectionType = model => {
|
|||||||
const singularName = toSingular(modelName);
|
const singularName = toSingular(modelName);
|
||||||
const pluralName = toPlural(modelName);
|
const pluralName = toPlural(modelName);
|
||||||
|
|
||||||
const _schema = _.cloneDeep(
|
const _schema = _.cloneDeep(_.get(strapi.plugins, 'graphql.config._schema.graphql', {}));
|
||||||
_.get(strapi.plugins, 'graphql.config._schema.graphql', {})
|
|
||||||
);
|
|
||||||
|
|
||||||
const globalType = _.get(_schema, ['type', model.globalId], {});
|
const globalType = _.get(_schema, ['type', model.globalId], {});
|
||||||
|
|
||||||
|
@ -33,9 +33,13 @@ const diffResolvers = (object, base) => {
|
|||||||
|
|
||||||
Object.keys(object).forEach(type => {
|
Object.keys(object).forEach(type => {
|
||||||
Object.keys(object[type]).forEach(resolver => {
|
Object.keys(object[type]).forEach(resolver => {
|
||||||
|
if(type === 'Query' || type === 'Mutation') {
|
||||||
if (!_.has(base, [type, resolver])) {
|
if (!_.has(base, [type, resolver])) {
|
||||||
_.set(newObj, [type, resolver], _.get(object, [type, resolver]));
|
_.set(newObj, [type, resolver], _.get(object, [type, resolver]));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
_.set(newObj, [type, resolver], _.get(object, [type, resolver]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,7 +9,25 @@ let graphqlQuery;
|
|||||||
let modelsUtils;
|
let modelsUtils;
|
||||||
|
|
||||||
// utils
|
// 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 = {
|
const documentModel = {
|
||||||
attributes: {
|
attributes: {
|
||||||
@ -37,6 +55,11 @@ const labelModel = {
|
|||||||
target: 'application::document.document',
|
target: 'application::document.document',
|
||||||
targetAttribute: 'labels',
|
targetAttribute: 'labels',
|
||||||
},
|
},
|
||||||
|
color: {
|
||||||
|
type: 'component',
|
||||||
|
component: 'default.rgb-color',
|
||||||
|
repeatable: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
connection: 'default',
|
connection: 'default',
|
||||||
name: 'label',
|
name: 'label',
|
||||||
@ -44,6 +67,41 @@ const labelModel = {
|
|||||||
collectionName: '',
|
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', () => {
|
describe('Test Graphql Relations API End to End', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const token = await registerAndLogin();
|
const token = await registerAndLogin();
|
||||||
@ -59,17 +117,24 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
|
|
||||||
modelsUtils = createModelsUtils({ rq });
|
modelsUtils = createModelsUtils({ rq });
|
||||||
|
|
||||||
await modelsUtils.createContentTypes([documentModel, labelModel]);
|
await modelsUtils.createComponent(rgbColorComponent);
|
||||||
|
await modelsUtils.createContentTypes([documentModel, labelModel, carModel, personModel]);
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|
||||||
afterAll(() => modelsUtils.deleteContentTypes(['document', 'label']), 60000);
|
afterAll(() => modelsUtils.deleteContentTypes(['document', 'label', 'car', 'person']), 60000);
|
||||||
|
|
||||||
describe('Test relations features', () => {
|
describe('Test relations features', () => {
|
||||||
let data = {
|
let data = {
|
||||||
labels: [],
|
labels: [],
|
||||||
documents: [],
|
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' }];
|
const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }];
|
||||||
|
|
||||||
test.each(labelsPayload)('Create label %o', async label => {
|
test.each(labelsPayload)('Create label %o', async label => {
|
||||||
@ -79,6 +144,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
createLabel(input: $input) {
|
createLabel(input: $input) {
|
||||||
label {
|
label {
|
||||||
name
|
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(res.statusCode).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(res.body).toEqual({
|
||||||
data: {
|
data: {
|
||||||
createLabel: {
|
createLabel: {
|
||||||
label,
|
label,
|
||||||
@ -109,6 +178,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@ -124,12 +199,10 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// assign for later use
|
// assign for later use
|
||||||
data.labels = res.body.data.labels;
|
data.labels = data.labels.concat(res.body.data.labels);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(documentsPayload)(
|
test.each(documentsPayload)('Create document linked to every labels %o', async document => {
|
||||||
'Create document linked to every labels %o',
|
|
||||||
async document => {
|
|
||||||
const res = await graphqlQuery({
|
const res = await graphqlQuery({
|
||||||
query: /* GraphQL */ `
|
query: /* GraphQL */ `
|
||||||
mutation createDocument($input: createDocumentInput) {
|
mutation createDocument($input: createDocumentInput) {
|
||||||
@ -139,6 +212,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,8 +247,7 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
test('List documents with labels', async () => {
|
test('List documents with labels', async () => {
|
||||||
const res = await graphqlQuery({
|
const res = await graphqlQuery({
|
||||||
@ -181,6 +259,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,6 +296,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
documents {
|
documents {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -229,9 +319,7 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels: expect.arrayContaining(
|
labels: expect.arrayContaining(
|
||||||
data.labels.map(label => ({
|
data.labels.map(label => ({
|
||||||
...selectFields(label),
|
...selectFields(label),
|
||||||
documents: expect.arrayContaining(
|
documents: expect.arrayContaining(data.documents.map(selectFields)),
|
||||||
data.documents.map(selectFields)
|
|
||||||
),
|
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -251,6 +339,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,6 +371,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,6 +416,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
label {
|
label {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
color {
|
||||||
|
name
|
||||||
|
red
|
||||||
|
green
|
||||||
|
blue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,6 +456,12 @@ describe('Test Graphql Relations API End to End', () => {
|
|||||||
labels {
|
labels {
|
||||||
id
|
id
|
||||||
name
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,10 +22,7 @@ const getPrefixedDeps = require('./utils/get-prefixed-dependencies');
|
|||||||
|
|
||||||
const createEventHub = require('./services/event-hub');
|
const createEventHub = require('./services/event-hub');
|
||||||
const createWebhookRunner = require('./services/webhook-runner');
|
const createWebhookRunner = require('./services/webhook-runner');
|
||||||
const {
|
const { webhookModel, createWebhookStore } = require('./services/webhook-store');
|
||||||
webhookModel,
|
|
||||||
createWebhookStore,
|
|
||||||
} = require('./services/webhook-store');
|
|
||||||
const { createCoreStore, coreStoreModel } = require('./services/core-store');
|
const { createCoreStore, coreStoreModel } = require('./services/core-store');
|
||||||
const createEntityService = require('./services/entity-service');
|
const createEntityService = require('./services/entity-service');
|
||||||
const createEntityValidator = require('./services/entity-validator');
|
const createEntityValidator = require('./services/entity-validator');
|
||||||
@ -133,10 +130,7 @@ class Strapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
requireProjectBootstrap() {
|
requireProjectBootstrap() {
|
||||||
const bootstrapPath = path.resolve(
|
const bootstrapPath = path.resolve(this.dir, 'config/functions/bootstrap.js');
|
||||||
this.dir,
|
|
||||||
'config/functions/bootstrap.js'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fse.existsSync(bootstrapPath)) {
|
if (fse.existsSync(bootstrapPath)) {
|
||||||
require(bootstrapPath);
|
require(bootstrapPath);
|
||||||
@ -159,10 +153,7 @@ class Strapi extends EventEmitter {
|
|||||||
[chalk.blue('Launched in'), Date.now() - this.config.launchedAt + ' ms'],
|
[chalk.blue('Launched in'), Date.now() - this.config.launchedAt + ' ms'],
|
||||||
[chalk.blue('Environment'), this.config.environment],
|
[chalk.blue('Environment'), this.config.environment],
|
||||||
[chalk.blue('Process PID'), process.pid],
|
[chalk.blue('Process PID'), process.pid],
|
||||||
[
|
[chalk.blue('Version'), `${this.config.info.strapi} (node v${this.config.info.node})`]
|
||||||
chalk.blue('Version'),
|
|
||||||
`${this.config.info.strapi} (node v${this.config.info.node})`,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(infoTable.toString());
|
console.log(infoTable.toString());
|
||||||
@ -176,9 +167,7 @@ class Strapi extends EventEmitter {
|
|||||||
|
|
||||||
console.log(chalk.bold('One more thing...'));
|
console.log(chalk.bold('One more thing...'));
|
||||||
console.log(
|
console.log(
|
||||||
chalk.grey(
|
chalk.grey('Create your first administrator 💻 by going to the administration panel at:')
|
||||||
'Create your first administrator 💻 by going to the administration panel at:'
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
@ -194,11 +183,7 @@ class Strapi extends EventEmitter {
|
|||||||
console.log(chalk.bold('Welcome back!'));
|
console.log(chalk.bold('Welcome back!'));
|
||||||
|
|
||||||
if (this.config.serveAdminPanel === true) {
|
if (this.config.serveAdminPanel === true) {
|
||||||
console.log(
|
console.log(chalk.grey('To manage your project 🚀, go to the administration panel at:'));
|
||||||
chalk.grey(
|
|
||||||
'To manage your project 🚀, go to the administration panel at:'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
console.log(chalk.bold(this.config.admin.url));
|
console.log(chalk.bold(this.config.admin.url));
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
@ -243,11 +228,7 @@ class Strapi extends EventEmitter {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(this.config.environment === 'development' &&
|
(this.config.environment === 'development' &&
|
||||||
_.get(
|
_.get(this.config.currentEnvironment, 'server.admin.autoOpen', true) !== false) ||
|
||||||
this.config.currentEnvironment,
|
|
||||||
'server.admin.autoOpen',
|
|
||||||
true
|
|
||||||
) !== false) ||
|
|
||||||
!isInitialised
|
!isInitialised
|
||||||
) {
|
) {
|
||||||
await utils.openBrowser.call(this);
|
await utils.openBrowser.call(this);
|
||||||
@ -265,9 +246,7 @@ class Strapi extends EventEmitter {
|
|||||||
// handle port in use cleanly
|
// handle port in use cleanly
|
||||||
this.server.on('error', err => {
|
this.server.on('error', err => {
|
||||||
if (err.code === 'EADDRINUSE') {
|
if (err.code === 'EADDRINUSE') {
|
||||||
return this.stopWithError(
|
return this.stopWithError(`The port ${err.port} is already used by another application.`);
|
||||||
`The port ${err.port} is already used by another application.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log.error(err);
|
this.log.error(err);
|
||||||
@ -294,8 +273,11 @@ class Strapi extends EventEmitter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
stopWithError(err) {
|
stopWithError(err, customMessage) {
|
||||||
this.log.debug(`⛔️ Server wasn't able to start properly.`);
|
this.log.debug(`⛔️ Server wasn't able to start properly.`);
|
||||||
|
if (customMessage) {
|
||||||
|
this.log.error(customMessage);
|
||||||
|
}
|
||||||
this.log.error(err);
|
this.log.error(err);
|
||||||
return this.stop();
|
return this.stop();
|
||||||
}
|
}
|
||||||
@ -445,9 +427,7 @@ class Strapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pluginBoostraps = Object.keys(this.plugins).map(plugin => {
|
const pluginBoostraps = Object.keys(this.plugins).map(plugin => {
|
||||||
return execBootstrap(
|
return execBootstrap(_.get(this.plugins[plugin], 'config.functions.bootstrap')).catch(err => {
|
||||||
_.get(this.plugins[plugin], 'config.functions.bootstrap')
|
|
||||||
).catch(err => {
|
|
||||||
strapi.log.error(`Bootstrap function in plugin "${plugin}" failed`);
|
strapi.log.error(`Bootstrap function in plugin "${plugin}" failed`);
|
||||||
strapi.log.error(err);
|
strapi.log.error(err);
|
||||||
strapi.stop();
|
strapi.stop();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user