Merge branch 'master' into master

This commit is contained in:
Николай 2018-01-08 16:27:26 +03:00 committed by GitHub
commit d9af887dc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1161 additions and 766 deletions

View File

@ -82,4 +82,4 @@ For general help using Strapi, please refer to [the official Strapi documentatio
## License
[MIT License](LICENSE.md) Copyright (c) 2015-2017 [Strapi Solutions](http://strapi.io/).
[MIT License](LICENSE.md) Copyright (c) 2015-2018 [Strapi Solutions](http://strapi.io/).

View File

@ -16,8 +16,10 @@
* [Table of contents](concepts/concepts.md)
### Guides
* [Authentification](guides/authentification.md)
* [Configurations](configurations/configurations.md)
* [Controllers](guides/controllers.md)
* [Deployment](guides/deployment.md)
* [Filters](guides/filters.md)
* [Internationalization](guides/i18n.md)
* [Models](guides/models.md)
@ -27,8 +29,6 @@
* [Responses](guides/responses.md)
* [Routing](guides/routing.md)
* [Services](guides/services.md)
* [Authentification](guides/authentification.md)
* [Deployment](guides/deployment.md)
### Plugins
* [Quick start](plugins/quick-start.md)

View File

@ -4,7 +4,7 @@
## How to create a policy?
There is several ways to create a policy.
There are several ways to create a policy.
- Using the CLI `strapi generate:policy isAuthenticated`. Read the [CLI documentation](../cli/CLI.md) for more information.
- Manually create a JavaScript file named `isAuthenticated.js` in `./config/policies/`.
@ -20,7 +20,7 @@ module.exports = async (ctx, next) => {
};
```
In this example, we are verifying that a session is open. If it is the case, we are calling the `next()` method that will execute the next policy or controller's action. Otherwise, a 401 error is returned.
In this example, we are verifying that a session is open. If it is the case, we call the `next()` method that will execute the next policy or controller's action. Otherwise, a 401 error is returned.
> Note: You can access to any controllers, services or models thanks to the global variable `strapi` in a policy.
@ -82,7 +82,7 @@ The policy `isAuthenticated` located in `./plugins/auth/config/policies/isAuthen
### Scoped Policies
The scoped policies can only be associated to the routes defining in the API where they have been declared.
The scoped policies can only be associated to the routes defined in the API where they have been declared.
**Path —** `./api/car/config/policies/isAdmin.js`.
```js

View File

@ -68,6 +68,24 @@ Please refer to the [Controllers documentation](../guides/controllers.md) for mo
A plugin can have its own models.
##### Table/Collection naming
Sometimes it happens that the plugins inject models that have the same name as yours. Let's take a quick example.
You already have `User` model defining in your `./api/user/models/User.settings.json` API. And you decide to install the `Users & Permissions` plugin. This plugin also contains a `User` model. To avoid the conflicts, the plugins' models are not globally exposed which means you cannot access to the plugin's model like this:
```js
module.exports = {
findUser: async function (params) {
// This `User` global variable will always make a reference the User model defining in your `./api/xxx/models/User.settings.json`.
return await User.find();
}
}
```
Also, the table/collection name won't be `users` because you already have a `User` model. That's why, the framework will automatically prefix the table/collection name for this model with the name of the plugin. Which means in our example, the table/collection name of the `User` model of our plugin `Users & Permissions` will be `users-permissions_users`. If you want to force the table/collection name of the plugin's model, you can add the `collectionName` attribute in your model.
Please refer to the [Models documentation](../guides/models.md) for more informations.
### Policies

View File

@ -1,6 +1,6 @@
{
"private": true,
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"devDependencies": {
"assert": "~1.3.0",
"babel-eslint": "^6.1.2",

View File

@ -5,7 +5,7 @@
*/
import React from 'react';
import { startsWith } from 'lodash';
import { startsWith, upperFirst } from 'lodash';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
@ -17,6 +17,10 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
// We need to create our own active url checker,
// because of the two levels router.
const isLinkActive = startsWith(window.location.pathname.replace('/admin', ''), this.props.destination);
const plugin = this.props.source !== 'content-manager' && this.props.source !== '' ?
(<div className={styles.plugin}>
<span>{upperFirst(this.props.source.split('-').join(' '))}</span>
</div>) : '';
return (
<li className={styles.item}>
@ -32,11 +36,12 @@ class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer
id={this.props.label}
defaultMessage='{label}'
values={{
label: this.props.label,
label: `${this.props.label}`,
}}
className={styles.linkLabel}
/>
</Link>
{plugin}
</li>
);
}

View File

@ -2,9 +2,41 @@
@import "../../styles/variables/variables";
.item {
position: relative;
overflow: hidden;
&:not(:first-child) {
margin-top: 0;
}
.plugin {
cursor: pointer;
position: absolute;
top: 10px; left: calc(100% - 4px);
display: inline-block;
width: auto;
height: 20px;
transition: right 1s ease-in-out;
span{
display: inline-block;
overflow: hidden;
width: auto;
height: 20px;
padding: 0 14px 0 10px;
color: #ffffff;
font-size: 12px;
line-height: 20px;
background: #0097f7;
border-radius: 3px;
transition: transform .3s ease-in-out;
white-space: pre;
&:hover{
transform: translateX(calc(-100% + 9px));
}
}
}
}
.link {

View File

@ -40,8 +40,8 @@ function LeftMenuLinkContainer({ plugins }) {
<div key={j}>
<p className={styles.title}>{pluginsSections[current].name}</p>
<ul className={styles.list}>
{sortBy(pluginsSections[current].links, 'label').map(link =>
<LeftMenuLink key={link.label} icon={link.icon || 'link'} label={link.label} destination={`/plugins/${link.plugin}/${link.destination}`} source={link.source} />
{sortBy(pluginsSections[current].links, 'label').map((link, i) =>
<LeftMenuLink key={`${i}-${link.label}`} icon={link.icon || 'link'} label={link.label} destination={`/plugins/${link.plugin}/${link.destination}`} source={link.source} />
)}
</ul>
</div>

View File

@ -1,6 +1,6 @@
{
"name": "strapi-admin",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Strapi Admin",
"repository": {
"type": "git",
@ -26,8 +26,8 @@
"devDependencies": {
"sanitize.css": "^4.1.0",
"shelljs": "^0.7.8",
"strapi-helper-plugin": "3.0.0-alpha.7.2",
"strapi-utils": "3.0.0-alpha.7.2"
"strapi-helper-plugin": "3.0.0-alpha.7.3",
"strapi-utils": "3.0.0-alpha.7.3"
},
"author": {
"name": "Strapi",

View File

@ -19,6 +19,7 @@ const utils = require('./utils/');
const utilsModels = require('strapi-utils').models;
const PIVOT_PREFIX = '_pivot_';
const GLOBALS = {};
/**
* Bookshelf hook
@ -40,18 +41,9 @@ module.exports = function(strapi) {
*/
initialize: cb => {
let globalName;
const connections = _.pickBy(strapi.config.connections, { connector: 'strapi-bookshelf' });
// Initialize collections
_.set(strapi, 'bookshelf.collections', {});
const connections = _.pickBy(strapi.config.connections, {
connector: 'strapi-bookshelf'
});
const done = _.after(_.size(connections), () => {
cb();
});
const done = _.after(_.size(connections), cb);
_.forEach(connections, (connection, connectionName) => {
// Apply defaults
@ -73,55 +65,38 @@ module.exports = function(strapi) {
}
// Load plugins
if (_.get(connection, 'options.plugins') !== false) {
if (_.get(connection, 'options.plugins', true) !== false) {
ORM.plugin('visibility');
ORM.plugin('pagination');
}
// Select models concerned by this connection
let models = _.pickBy(strapi.models, {
connection: connectionName
});
if (connectionName === strapi.config.currentEnvironment.database.defaultConnection) {
_.assign(models, _.pickBy(strapi.models, (model) => model.connection === undefined));
}
const loadedHook = _.after(_.size(models), () => {
done();
});
const models = _.pickBy(strapi.models, { connection: connectionName });
// Will call the done() method when every models will be loaded.
const loadedHook = _.after(_.size(models), done);
const mountModels = (models, target, plugin = false) => {
// Parse every registered model.
_.forEach(models, (definition, model) => {
if (plugin) {
definition.globalId = _.upperFirst(_.camelCase(`${plugin}-${model}`));
}
globalName = _.upperFirst(_.camelCase(definition.globalId));
definition.globalName = _.upperFirst(_.camelCase(definition.globalId));
_.defaults(definition, {
primaryKey: 'id'
});
// Make sure the model has a table name.
// If not, use the model name.
if (_.isEmpty(definition.collectionName)) {
definition.collectionName = model;
}
// Define local GLOBALS to expose every models in this file.
GLOBALS[definition.globalId] = {};
// Add some informations about ORM & client connection
// Add some informations about ORM & client connection & tableName
definition.orm = 'bookshelf';
definition.client = _.get(connection.settings, 'client');
// Register the final model for Bookshelf.
const loadedModel = _.assign(
{
const loadedModel = _.assign({
tableName: definition.collectionName,
hasTimestamps: _.get(definition, 'options.timestamps') === true,
idAttribute: _.get(definition, 'options.idAttribute') || 'id'
},
definition.options
);
idAttribute: _.get(definition, 'options.idAttribute', 'id')
}, definition.options);
if (_.isString(_.get(connection, 'options.pivot_prefix'))) {
loadedModel.toJSON = function(options = {}) {
@ -129,20 +104,13 @@ module.exports = function(strapi) {
const attributes = this.serialize(options);
if (!shallow) {
const pivot = this.pivot &&
!omitPivot &&
this.pivot.attributes;
const pivot = this.pivot && !omitPivot && this.pivot.attributes;
// Remove pivot attributes with prefix.
_.keys(pivot).forEach(
key => delete attributes[`${PIVOT_PREFIX}${key}`]
);
_.keys(pivot).forEach(key => delete attributes[`${PIVOT_PREFIX}${key}`]);
// Add pivot attributes without prefix.
const pivotAttributes = _.mapKeys(
pivot,
(value, key) => `${connection.options.pivot_prefix}${key}`
);
const pivotAttributes = _.mapKeys(pivot, (value, key) => `${connection.options.pivot_prefix}${key}`);
return Object.assign({}, attributes, pivotAttributes);
}
@ -154,7 +122,7 @@ module.exports = function(strapi) {
// Initialize the global variable with the
// capitalized model name.
if (!plugin) {
global[globalName] = {};
global[definition.globalName] = {};
}
// Call this callback function after we are done parsing
@ -222,26 +190,24 @@ module.exports = function(strapi) {
)
);
if (!plugin) {
global[globalName] = ORM.Model.extend(loadedModel);
global[pluralize(globalName)] = ORM.Collection.extend({
model: global[globalName]
});
GLOBALS[definition.globalId] = ORM.Model.extend(loadedModel);
// Expose ORM functions through the `target` object.
target[model] = _.assign(global[globalName], target[model]);
} else {
target[model] = _.assign(ORM.Model.extend(loadedModel), target[model]);
if (!plugin) {
// Only expose as real global variable the models which
// are not scoped in a plugin.
global[definition.globalId] = GLOBALS[definition.globalId];
}
// Expose ORM functions through the `strapi.models[xxx]`
// or `strapi.plugins[xxx].models[yyy]` object.
target[model] = _.assign(GLOBALS[definition.globalId], target[model]);
// Push attributes to be aware of model schema.
target[model]._attributes = definition.attributes;
loadedHook();
} catch (err) {
strapi.log.error(
'Impossible to register the `' + model + '` model.'
);
strapi.log.error('Impossible to register the `' + model + '` model.');
strapi.log.error(err);
strapi.stop();
}
@ -261,82 +227,90 @@ module.exports = function(strapi) {
// Build associations key
utilsModels.defineAssociations(
globalName,
definition.globalName,
definition,
details,
name
);
const globalId = details.plugin ?
_.get(strapi.plugins,`${details.plugin}.models.${(details.model || details.collection || '').toLowerCase()}.globalId`):
_.get(strapi.models, `${(details.model || details.collection || '').toLowerCase()}.globalId`);
switch (verbose) {
case 'hasOne': {
const FK = _.findKey(
strapi.models[details.model].attributes,
details => {
if (
details.hasOwnProperty('model') &&
details.model === model &&
details.hasOwnProperty('via') &&
details.via === name
) {
return details;
const FK = details.plugin ?
_.findKey(
strapi.plugins[details.plugin].models[details.model].attributes,
details => {
if (
details.hasOwnProperty('model') &&
details.model === model &&
details.hasOwnProperty('via') &&
details.via === name
) {
return details;
}
}
}
);
):
_.findKey(
strapi.models[details.model].attributes,
details => {
if (
details.hasOwnProperty('model') &&
details.model === model &&
details.hasOwnProperty('via') &&
details.via === name
) {
return details;
}
}
);
const globalId = _.get(
strapi.models,
`${details.model.toLowerCase()}.globalId`
);
const columnName = details.plugin ?
_.get(strapi.plugins, `${details.plugin}.models.${details.model}.attributes.${FK}.columnName`, FK):
_.get(strapi.models, `${details.model}.attributes.${FK}.columnName`, FK);
loadedModel[name] = function() {
return this.hasOne(
global[globalId],
_.get(
strapi.models[details.model].attributes,
`${FK}.columnName`
) || FK
GLOBALS[globalId],
columnName
);
};
break;
}
case 'hasMany': {
const globalId = _.get(
strapi.models,
`${details.collection.toLowerCase()}.globalId`
);
const FKTarget = _.get(
strapi.models[globalId.toLowerCase()].attributes,
`${details.via}.columnName`
) || details.via;
const columnName = details.plugin ?
_.get(strapi.plugins, `${details.plugin}.models.${globalId.toLowerCase()}.attributes.${details.via}.columnName`, details.via):
_.get(strapi.models[globalId.toLowerCase()].attributes, `${details.via}.columnName`, details.via);
// Set this info to be able to see if this field is a real database's field.
details.isVirtual = true;
loadedModel[name] = function() {
return this.hasMany(global[globalId], FKTarget);
return this.hasMany(GLOBALS[globalId], columnName);
};
break;
}
case 'belongsTo': {
const globalId = _.get(
strapi.models,
`${details.model.toLowerCase()}.globalId`
);
loadedModel[name] = function() {
return this.belongsTo(
global[globalId],
_.get(details, 'columnName') || name
GLOBALS[globalId],
_.get(details, 'columnName', name)
);
};
break;
}
case 'belongsToMany': {
const collection = details.plugin ?
strapi.plugins[details.plugin].models[details.collection]:
strapi.models[details.collection];
const collectionName = _.get(details, 'collectionName') ||
_.map(
_.sortBy(
[
strapi.models[details.collection].attributes[
collection.attributes[
details.via
],
details
@ -353,7 +327,7 @@ module.exports = function(strapi) {
).join('__');
const relationship = _.clone(
strapi.models[details.collection].attributes[details.via]
collection.attributes[details.via]
);
// Force singular foreign key
@ -378,11 +352,6 @@ module.exports = function(strapi) {
relationship.attribute = pluralize.singular(details.via);
}
const globalId = _.get(
strapi.models,
`${details.collection.toLowerCase()}.globalId`
);
// Set this info to be able to see if this field is a real database's field.
details.isVirtual = true;
@ -392,7 +361,7 @@ module.exports = function(strapi) {
!_.isEmpty(details.withPivot)
) {
return this.belongsToMany(
global[globalId],
GLOBALS[globalId],
collectionName,
relationship.attribute + '_' + relationship.column,
details.attribute + '_' + details.column
@ -400,7 +369,7 @@ module.exports = function(strapi) {
}
return this.belongsToMany(
global[globalId],
GLOBALS[globalId],
collectionName,
relationship.attribute + '_' + relationship.column,
details.attribute + '_' + details.column
@ -418,15 +387,12 @@ module.exports = function(strapi) {
});
};
mountModels(models, strapi.models);
// Mount `./api` models.
mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models);
// Mount `./plugins` models.
_.forEach(strapi.plugins, (plugin, name) => {
models = _.pickBy(strapi.plugins[name].models, { connection: connectionName })
if (connectionName === strapi.config.currentEnvironment.database.defaultConnection) {
_.assign(models, _.pickBy(strapi.plugins[name].models, (model) => model.connection === undefined));
}
mountModels(models, plugin.models, name);
mountModels(_.pickBy(strapi.plugins[name].models, { connection: connectionName }), plugin.models, name);
});
});
},
@ -549,7 +515,7 @@ module.exports = function(strapi) {
.then(response => {
const record = response ? response.toJSON() : response;
if (record && _.isObject(record[details.via])) {
if (record && _.isObject(record[details.via]) && record[details.via][current] !== value[current]) {
return this.manageRelations(model, {
id: record[details.via][models[details.model || details.collection].primaryKey] || record[details.via].id,
values: {

View File

@ -36,7 +36,7 @@ module.exports = {
} catch (e) {
// Collection undefined try to get the collection based on collectionIdentity
if (typeof strapi !== 'undefined') {
collection = _.get(strapi.bookshelf.collections, collectionIdentity);
collection = _.get(strapi, `bookshelf.collections.${collectionIdentity}`);
}
// Impossible to match collectionIdentity before, try to use idAttribute

View File

@ -1,6 +1,6 @@
{
"name": "strapi-bookshelf",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Bookshelf hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -19,8 +19,8 @@
"bookshelf": "^0.10.3",
"lodash": "^4.17.4",
"pluralize": "^6.0.0",
"strapi-knex": "3.0.0-alpha.7.2",
"strapi-utils": "3.0.0-alpha.7.2"
"strapi-knex": "3.0.0-alpha.7.3",
"strapi-utils": "3.0.0-alpha.7.3"
},
"strapi": {
"isHook": true,

View File

@ -1,6 +1,6 @@
{
"name": "strapi-ejs",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "EJS hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-admin",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate the default admin panel for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -15,7 +15,7 @@
"dependencies": {
"fs-extra": "^4.0.1",
"lodash": "^4.17.4",
"strapi-admin": "3.0.0-alpha.7.2"
"strapi-admin": "3.0.0-alpha.7.3"
},
"author": {
"email": "hi@strapi.io",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-api",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate an API for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-controller",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate a controller for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-model",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate a model for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-new",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate a new Strapi application.",
"homepage": "http://strapi.io",
"keywords": [
@ -17,7 +17,7 @@
"fs-extra": "^4.0.0",
"get-installed-path": "^3.0.1",
"lodash": "^4.17.4",
"strapi-utils": "3.0.0-alpha.7.2",
"strapi-utils": "3.0.0-alpha.7.3",
"uuid": "^3.1.0"
},
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-plugin",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate an plugin for a Strapi application.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-policy",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate a policy for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate-service",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Generate a service for a Strapi API.",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-generate",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Master of ceremonies for the Strapi generators.",
"homepage": "http://strapi.io",
"keywords": [
@ -17,7 +17,7 @@
"fs-extra": "^4.0.0",
"lodash": "^4.17.4",
"reportback": "^2.0.1",
"strapi-utils": "3.0.0-alpha.7.2"
"strapi-utils": "3.0.0-alpha.7.3"
},
"author": {
"name": "Strapi team",

View File

@ -6,7 +6,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { isEmpty } from 'lodash';
import { isEmpty, upperFirst } from 'lodash';
import { FormattedMessage } from 'react-intl';
import cn from 'classnames';

View File

@ -26,7 +26,6 @@
height: 3rem;
position: relative;
border-radius: 0.3rem;
text-transform: capitalize;
margin-right: 1.8rem;
cursor: pointer;
font-family: Lato;

View File

@ -7,7 +7,7 @@
import React from 'react';
import moment from 'moment';
import PropTypes from 'prop-types';
import { get, isEmpty, map, mapKeys, isObject, reject, includes } from 'lodash';
import { get, isEmpty, map, mapKeys, isObject, reject, includes, upperFirst } from 'lodash';
import { FormattedMessage } from 'react-intl';
import DateTime from 'react-datetime';
import DateTimeStyle from 'react-datetime/css/react-datetime.css';
@ -159,12 +159,13 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
return (
<div className={`${styles.inputDate} ${styles.input} ${this.props.customBootstrapClass || 'col-md-4'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={this.props.label} />
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<DateTime
value={value}
dateFormat='YYYY-MM-DD'
timeFormat='HH:mm:ss'
tabIndex={this.props.tabIndex}
utc={true}
inputProps={{
placeholder: this.props.placeholder,
@ -214,6 +215,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
disabled={this.props.disabled}
type="email"
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
@ -244,6 +246,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
autoComplete="off"
disabled={this.props.disabled}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
@ -262,7 +265,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
return (
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={this.props.label} />
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<FormattedMessage id={this.props.placeholder || this.props.label} values={this.props.labelValues}>
{(placeholder) => (
@ -278,6 +281,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
disabled={this.props.disabled}
type={type}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
@ -348,7 +352,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
return (
<div className={`${styles.input} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={this.props.label} />
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<div className={`input-group ${styles.inputSearch}`} style={{ marginBottom: '1rem'}}>
<span className={`input-group-addon ${styles.addonSearch}`} />
@ -366,6 +370,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
disabled={this.props.disabled}
type="text"
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
@ -391,7 +396,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
return (
<div className={`${styles.inputTextArea} ${this.props.customBootstrapClass || 'col-md-6'} ${requiredClass}`}>
<label htmlFor={this.props.label}>
<FormattedMessage id={`${this.props.label}`} defaultMessage={this.props.label} />
<FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} />
</label>
<FormattedMessage id={this.props.placeholder || this.props.label}>
{(placeholder) => (
@ -406,6 +411,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
placeholder={placeholder}
disabled={this.props.disabled}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>
)}
</FormattedMessage>
@ -448,7 +454,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
const handleBlur = this.props.onBlur || this.handleBlur;
const placeholder = this.props.placeholder || this.props.label;
const label = this.props.label ?
<label htmlFor={this.props.label}><FormattedMessage id={`${this.props.label}`} defaultMessage={this.props.label} /></label>
<label htmlFor={this.props.label}><FormattedMessage id={`${this.props.label}`} defaultMessage={upperFirst(this.props.label)} /></label>
: <label htmlFor={this.props.label} />;
const requiredClass = get(this.props.validations, 'required') && this.props.addRequiredInputDesign ?
@ -467,6 +473,7 @@ class Input extends React.Component { // eslint-disable-line react/prefer-statel
placeholder={placeholder}
disabled={this.props.disabled}
autoFocus={this.props.autoFocus}
tabIndex={this.props.tabIndex}
/>;
const link = !isEmpty(this.props.linkContent) ? <a href={this.props.linkContent.link} target="_blank"><FormattedMessage id={this.props.linkContent.description} /></a> : '';
@ -616,6 +623,7 @@ Input.propTypes = {
search: PropTypes.bool,
selectOptions: PropTypes.array,
selectOptionsFetchSucceeded: PropTypes.bool,
tabIndex: PropTypes.string,
title: PropTypes.string,
type: PropTypes.string.isRequired,
validations: PropTypes.object.isRequired,
@ -644,6 +652,7 @@ Input.defaultProps = {
search: false,
selectOptions: [],
selectOptionsFetchSucceeded: false,
tabIndex: '0',
value: ''
};

View File

@ -131,7 +131,6 @@
label {
margin-bottom: 0;
font-weight: 500;
text-transform: capitalize;
}
input {
@ -229,7 +228,6 @@
> label {
margin-bottom: 0;
font-weight: 500;
text-transform: capitalize;
}
> textarea {
height: 10.6rem;
@ -282,7 +280,6 @@
label {
margin-bottom: 0;
font-weight: 500;
text-transform: capitalize;
}
input {
@ -359,7 +356,6 @@
.toggleLabel {
margin-bottom: 0;
font-weight: 500;
text-transform: capitalize;
}
.gradientOff {

View File

@ -93,7 +93,7 @@
.secondary {
// height: 32px !important;
color: #F64D0A !important;
border: 0.1rem solid #F64D0A;
border: 0.1rem solid #F64D0A !important;
position: relative;
border-radius: 3px;
overflow: hidden;

View File

@ -1,6 +1,6 @@
{
"name": "strapi-helper-plugin",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Helper for Strapi plugins development",
"engines": {
"node": ">= 8.0.0",

View File

@ -95,6 +95,9 @@ module.exports = strapi => {
charset: _.get(connection.settings, 'charset'),
schema: _.get(connection.settings, 'schema') || 'public',
port: _.get(connection.settings, 'port'),
socket: _.get(connection.settings, 'socketPath'),
ssl: _.get(connection.settings, 'ssl') || false
},
debug: _.get(connection.options, 'debug') || false,
acquireConnectionTimeout: _.get(connection.options, 'acquireConnectionTimeout'),

View File

@ -1,6 +1,6 @@
{
"name": "strapi-knex",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Knex hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -1,6 +1,6 @@
{
"name": "strapi-middleware-views",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Views hook to enable server-side rendering for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -38,10 +38,9 @@ module.exports = function (strapi) {
*/
initialize: cb => {
let globalName;
_.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-mongoose'}), (connection, connectionName) => {
const instance = new Mongoose();
const {host, port, username, password, database} = _.defaults(connection.settings, strapi.config.hook.settings.mongoose);
const { host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose);
// Connect to mongo database
if (_.isEmpty(username) || _.isEmpty(password)) {
@ -65,12 +64,6 @@ module.exports = function (strapi) {
// Handle success
instance.connection.on('open', () => {
// Select models concerned by this connection
let models = _.pickBy(strapi.models, { connection: connectionName });
if (connectionName === strapi.config.currentEnvironment.database.defaultConnection) {
_.assign(models, _.pickBy(strapi.models, (model) => model.connection === undefined));
}
const mountModels = (models, target, plugin = false) => {
if (!target) return;
@ -141,11 +134,10 @@ module.exports = function (strapi) {
virtuals: true
});
if (!plugin) {
global[definition.globalName] = instance.model(definition.globalName, collection.schema);
global[definition.globalName] = instance.model(definition.globalName, collection.schema, definition.collectionName);
} else {
instance.model(definition.globalName, collection.schema);
instance.model(definition.globalName, collection.schema, definition.collectionName);
}
// Expose ORM functions through the `target` object.
@ -166,10 +158,6 @@ module.exports = function (strapi) {
// Parse every registered model.
_.forEach(models, (definition, model) => {
if (plugin) {
definition.globalId = _.upperFirst(_.camelCase(_.get(strapi.config.hook.settings.mongoose.collections, mongooseUtils.toCollectionName(model)) ? `${plugin}-${model}` : model));
}
definition.globalName = _.upperFirst(_.camelCase(definition.globalId));
// Make sure the model has a connection.
@ -227,22 +215,24 @@ module.exports = function (strapi) {
definition.loadedModel[name].type = utils(instance).convertType(details.type);
}
let FK;
switch (verbose) {
case 'hasOne':
case 'hasOne': {
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId;
definition.loadedModel[name] = {
type: instance.Schema.Types.ObjectId,
ref: _.capitalize(details.model)
ref
};
break;
case 'hasMany':
FK = _.find(definition.associations, {alias: name});
}
case 'hasMany': {
const FK = _.find(definition.associations, {alias: name});
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId;
if (FK) {
definition.loadedModel[name] = {
type: 'virtual',
ref: _.capitalize(details.collection),
ref,
via: FK.via,
justOne: false
};
@ -252,17 +242,19 @@ module.exports = function (strapi) {
} else {
definition.loadedModel[name] = [{
type: instance.Schema.Types.ObjectId,
ref: _.capitalize(details.collection)
ref
}];
}
break;
case 'belongsTo':
FK = _.find(definition.associations, {alias: name});
}
case 'belongsTo': {
const FK = _.find(definition.associations, {alias: name});
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId;
if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne') {
definition.loadedModel[name] = {
type: 'virtual',
ref: _.capitalize(details.model),
ref,
via: FK.via,
justOne: true
};
@ -272,19 +264,21 @@ module.exports = function (strapi) {
} else {
definition.loadedModel[name] = {
type: instance.Schema.Types.ObjectId,
ref: _.capitalize(details.model)
ref
};
}
break;
case 'belongsToMany':
FK = _.find(definition.associations, {alias: name});
}
case 'belongsToMany': {
const FK = _.find(definition.associations, {alias: name});
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.collection].globalId : strapi.models[details.collection].globalId;
// One-side of the relationship has to be a virtual field to be bidirectional.
if ((FK && _.isUndefined(FK.via)) || details.dominant !== true) {
definition.loadedModel[name] = {
type: 'virtual',
ref: _.capitalize(FK.collection),
ref,
via: FK.via
};
@ -293,10 +287,11 @@ module.exports = function (strapi) {
} else {
definition.loadedModel[name] = [{
type: instance.Schema.Types.ObjectId,
ref: _.capitalize(details.collection)
ref
}];
}
break;
}
default:
break;
}
@ -306,15 +301,12 @@ module.exports = function (strapi) {
});
};
mountModels(models, strapi.models);
// Mount `./api` models.
mountModels(_.pickBy(strapi.models, { connection: connectionName }), strapi.models);
// Mount `./plugins` models.
_.forEach(strapi.plugins, (plugin, name) => {
models = _.pickBy(strapi.plugins[name].models, { connection: connectionName })
if (connectionName === strapi.config.currentEnvironment.database.defaultConnection) {
_.assign(models, _.pickBy(strapi.plugins[name].models, (model) => model.connection === undefined));
}
mountModels(models, plugin.models, name);
mountModels(_.pickBy(strapi.plugins[name].models, { connection: connectionName }), plugin.models, name);
});
cb();
@ -418,7 +410,7 @@ module.exports = function (strapi) {
.findOne({ id : recordId })
.populate(_.keys(_.groupBy(_.reject(models[details.model || details.collection].associations, {autoPopulate: false}), 'alias')).join(' '))
.then(record => {
if (record && _.isObject(record[details.via])) {
if (record && _.isObject(record[details.via]) && record[details.via][current] !== value[current]) {
return this.manageRelations(details.model || details.collection, {
id: record[details.via][Model.primaryKey] || record[details.via].id,
values: {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-mongoose",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Mongoose hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -19,7 +19,7 @@
"mongoose": "^4.11.10",
"mongoose-float": "^1.0.2",
"pluralize": "^6.0.0",
"strapi-utils": "3.0.0-alpha.7.2"
"strapi-utils": "3.0.0-alpha.7.3"
},
"strapi": {
"isHook": true

View File

@ -50,14 +50,14 @@ class EditForm extends React.Component {
render() {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
const currentLayout = get(this.props.layout, [this.props.currentModelName, 'attributes']);
// Remove `id` field
const displayedFields = merge(get(currentLayout), omit(currentSchema.fields, 'id'));
// List fields inputs
const fields = Object.keys(displayedFields).map(attr => {
const fields = Object.keys(displayedFields).map((attr, key) => {
const details = displayedFields[attr];
const errorIndex = findIndex(this.props.formErrors, ['name', attr]);
const errors = errorIndex !== -1 ? this.props.formErrors[errorIndex].errors : [];
@ -74,6 +74,7 @@ class EditForm extends React.Component {
return (
<Input
autoFocus={key === 0}
key={attr}
type={get(layout, 'type', this.getInputType(details.type))}
label={get(layout, 'label') || details.label || ''}

View File

@ -20,14 +20,17 @@ import styles from './styles.scss';
class EditFormRelations extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentDidMount() {
if (size(get(this.props.schema, [this.props.currentModelName, 'relations'])) === 0 && !this.props.isNull) {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
if (size(get(currentSchema, ['relations'])) === 0 && !this.props.isNull) {
this.props.toggleNull();
}
}
render() {
const source = getQueryParameters(this.props.location.search, 'source');
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', source, this.props.currentModelName]);
const currentSchema = source !== 'content-manager' ? get(this.props.schema, ['plugins', source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
const relations = map(currentSchema.relations, (relation, i) => {
@ -43,6 +46,7 @@ class EditFormRelations extends React.Component { // eslint-disable-line react/p
relation={relation}
schema={this.props.schema}
setRecordAttribute={this.props.setRecordAttribute}
location={this.props.location}
/>
);
}
@ -57,6 +61,7 @@ class EditFormRelations extends React.Component { // eslint-disable-line react/p
relation={relation}
schema={this.props.schema}
setRecordAttribute={this.props.setRecordAttribute}
location={this.props.location}
/>
);
default:

View File

@ -27,6 +27,7 @@ class SelectMany extends React.Component { // eslint-disable-line react/prefer-s
getOptions = (query) => {
const params = {
limit: 20,
source: this.props.relation.plugin || 'content-manager',
};
// Set `query` parameter if necessary

View File

@ -8,7 +8,7 @@ import React from 'react';
import Select from 'react-select';
import PropTypes from 'prop-types';
import 'react-select/dist/react-select.css';
import { map, isArray, isNull, isUndefined } from 'lodash';
import { map, isArray, isNull, isUndefined, isFunction, get } from 'lodash';
import request from 'utils/request';
import templateObject from 'utils/templateObject';
@ -27,6 +27,7 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
getOptions = (query) => {
const params = {
limit: 20,
source: this.props.relation.plugin || 'content-manager',
};
// Set `query` parameter if necessary
@ -83,8 +84,8 @@ class SelectOne extends React.Component { // eslint-disable-line react/prefer-st
loadOptions={this.getOptions}
simpleValue
value={isNull(value) || isUndefined(value) ? null : {
value: value.toJS(),
label: templateObject({ mainField: this.props.relation.displayedAttribute }, value.toJS()).mainField || value.toJS().id,
value: isFunction(value.toJS) ? value.toJS() : value,
label: templateObject({ mainField: this.props.relation.displayedAttribute }, isFunction(value.toJS) ? value.toJS() : value).mainField || (isFunction(value.toJS) ? get(value.toJS(), 'id') : get(value, 'id')),
}}
/>
</div>

View File

@ -28,12 +28,12 @@ class TableRow extends React.Component {
* @param value {*} Value stored in database
* @returns {*}
*/
getDisplayedValue(type, value) {
getDisplayedValue(type, value, name) {
switch (type.toLowerCase()) {
case 'string':
case 'text':
case 'email':
return value && !isEmpty(value.toString()) ? value.toString() : '-';
return (value && !isEmpty(value.toString())) || name === 'id' ? value.toString() : '-';
case 'float':
case 'integer':
case 'biginteger':
@ -71,7 +71,8 @@ class TableRow extends React.Component {
<div className={styles.truncated}>
{this.getDisplayedValue(
header.type,
this.props.record[header.name]
this.props.record[header.name],
header.name,
)}
</div>
</div>

View File

@ -153,7 +153,7 @@ export class Edit extends React.Component {
}
handleChange = (e) => {
const currentSchema = get(this.props.schema, [this.props.currentModelName]) || get(this.props.schema, ['plugins', this.source, this.props.currentModelName]);
const currentSchema = this.source !== 'content-manager' ? get(this.props.schema, ['plugins', this.source, this.props.currentModelName]) : get(this.props.schema, [this.props.currentModelName]);
let formattedValue = e.target.value;

View File

@ -75,6 +75,8 @@ export class List extends React.Component {
}
componentWillReceiveProps(nextProps) {
this.source = getQueryParameters(nextProps.location.search, 'source');
const locationChanged = nextProps.location.pathname !== this.props.location.pathname;
if (locationChanged) {

View File

@ -11,9 +11,9 @@ module.exports = {
qb.orderBy(params.sort);
}
qb.offset(params.skip);
qb.offset(_.toNumber(params.skip));
qb.limit(params.limit);
qb.limit(_.toNumber(params.limit));
}).fetchAll({
withRelated: this.associations.map(x => x.alias)
});
@ -80,12 +80,11 @@ module.exports = {
case 'oneToOne':
if (response[current] !== params.values[current]) {
const value = _.isNull(params.values[current]) ? response[current] : params.values;
const recordId = _.isNull(params.values[current]) ? value[this.primaryKey] || value.id || value._id : value[current];
if (response[current] && _.isObject(response[current]) && response[current][this.primaryKey] !== value[current]) {
virtualFields.push(
strapi.query(details.collection || details.model).update({
strapi.query(details.collection || details.model, details.plugin).update({
id: response[current][this.primaryKey],
values: {
[details.via]: null
@ -97,9 +96,9 @@ module.exports = {
// Remove previous relationship asynchronously if it exists.
virtualFields.push(
strapi.query(details.model || details.collection).findOne({ id : recordId })
strapi.query(details.model || details.collection, details.plugin).findOne({ id : recordId })
.then(record => {
if (record && _.isObject(record[details.via])) {
if (record && _.isObject(record[details.via]) && record[details.via][current] !== value[current]) {
return module.exports.update.call(this, {
id: record[details.via][this.primaryKey] || record[details.via].id,
values: {
@ -115,7 +114,7 @@ module.exports = {
// Update the record on the other side.
// When params.values[current] is null this means that we are removing the relation.
virtualFields.push(strapi.query(details.model || details.collection).update({
virtualFields.push(strapi.query(details.model || details.collection, details.plugin).update({
id: recordId,
values: {
[details.via]: _.isNull(params.values[current]) ? null : value[this.primaryKey] || value.id || value._id
@ -147,7 +146,7 @@ module.exports = {
toAdd.forEach(value => {
value[details.via] = params.values[this.primaryKey] || params[this.primaryKey];
virtualFields.push(strapi.query(details.model || details.collection).addRelation({
virtualFields.push(strapi.query(details.model || details.collection, details.plugin).addRelation({
id: value[this.primaryKey] || value.id || value._id,
values: association.nature === 'manyToMany' ? params.values : value,
foreignKey: current
@ -157,7 +156,7 @@ module.exports = {
toRemove.forEach(value => {
value[details.via] = null;
virtualFields.push(strapi.query(details.model || details.collection).removeRelation({
virtualFields.push(strapi.query(details.model || details.collection, details.plugin).removeRelation({
id: value[this.primaryKey] || value.id || value._id,
values: association.nature === 'manyToMany' ? params.values : value,
foreignKey: current
@ -212,6 +211,7 @@ module.exports = {
switch (association.nature) {
case 'oneToOne':
case 'oneToMany':
case 'manyToOne':
return module.exports.update.call(this, params);
case 'manyToMany':
return this.forge({
@ -234,6 +234,7 @@ module.exports = {
switch (association.nature) {
case 'oneToOne':
case 'oneToMany':
case 'manyToOne':
return module.exports.update.call(this, params);
case 'manyToMany':
return this.forge({

View File

@ -66,7 +66,7 @@ module.exports = {
if (response[current] && _.isObject(response[current]) && response[current][this.primaryKey] !== value[current]) {
virtualFields.push(
strapi.query(details.collection || details.model).update({
strapi.query(details.collection || details.model, details.plugin).update({
id: response[current][this.primaryKey],
values: {
[details.via]: null
@ -78,7 +78,7 @@ module.exports = {
// Remove previous relationship asynchronously if it exists.
virtualFields.push(
strapi.query(details.model || details.collection).findOne({ id : recordId })
strapi.query(details.model || details.collection, details.plugin).findOne({ id : recordId })
.then(record => {
if (record && _.isObject(record[details.via])) {
return module.exports.update.call(this, {
@ -96,7 +96,7 @@ module.exports = {
// Update the record on the other side.
// When params.values[current] is null this means that we are removing the relation.
virtualFields.push(strapi.query(details.model || details.collection).update({
virtualFields.push(strapi.query(details.model || details.collection, details.plugin).update({
id: recordId,
values: {
[details.via]: _.isNull(params.values[current]) ? null : value[this.primaryKey] || value.id || value._id
@ -127,14 +127,16 @@ module.exports = {
// Push the work into the flow process.
toAdd.forEach(value => {
if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
value[details.via] = (value[details.via] || []).concat([(params.values[this.primaryKey] || params[this.primaryKey])]).filter(x => {
return x !== null && x !== undefined;
});
value[details.via] = (value[details.via] || [])
.concat([(params.values[this.primaryKey] || params[this.primaryKey])])
.filter(x => {
return x !== null && x !== undefined;
});
} else {
value[details.via] = params[this.primaryKey] || params.id;
}
virtualFields.push(strapi.query(details.model || details.collection).addRelation({
virtualFields.push(strapi.query(details.model || details.collection, details.plugin).addRelation({
id: value[this.primaryKey] || value.id || value._id,
values: value,
foreignKey: current
@ -148,7 +150,7 @@ module.exports = {
value[details.via] = null;
}
virtualFields.push(strapi.query(details.model || details.collection).removeRelation({
virtualFields.push(strapi.query(details.model || details.collection, details.plugin).removeRelation({
id: value[this.primaryKey] || value.id || value._id,
values: value,
foreignKey: current

View File

@ -35,25 +35,14 @@ module.exports = {
},
find: async ctx => {
const { limit, skip = 0, sort, query, queryAttribute, source, page } = ctx.request.query;
// Find entries using `queries` system
const entries = await strapi.query(ctx.params.model, source).find({
limit,
skip,
sort,
query,
queryAttribute
});
ctx.body = entries;
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].fetchAll(ctx.params, ctx.request.query);
},
count: async ctx => {
const { source } = ctx.request.query;
// Count using `queries` system
const count = await strapi.query(ctx.params.model, source).count();
const count = await strapi.plugins['content-manager'].services['contentmanager'].count(ctx.params, source);
ctx.body = {
count: _.isNumber(count) ? count : _.toNumber(count)
@ -64,9 +53,7 @@ module.exports = {
const { source } = ctx.request.query;
// Find an entry using `queries` system
const entry = await strapi.query(ctx.params.model, source).findOne({
id: ctx.params.id
});
const entry = await strapi.plugins['content-manager'].services['contentmanager'].fetch(ctx.params, source);
// Entry not found
if (!entry) {
@ -81,12 +68,9 @@ module.exports = {
try {
// Create an entry using `queries` system
const entryCreated = await strapi.query(ctx.params.model, source).create({
values: ctx.request.body
});
ctx.body = entryCreated;
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].add(ctx.params, ctx.request.body, source);
} catch(error) {
console.log(error);
ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: error.message, field: error.field }] }] : error.message);
}
},
@ -95,14 +79,8 @@ module.exports = {
const { source } = ctx.request.query;
try {
// Add current model to the flow of updates.
const entry = strapi.query(ctx.params.model, source).update({
id: ctx.params.id,
values: ctx.request.body
});
// Return the last one which is the current model.
ctx.body = entry;
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].edit(ctx.params, ctx.request.body, source);
} catch(error) {
// TODO handle error update
ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: error.message, field: error.field }] }] : error.message);
@ -110,34 +88,6 @@ module.exports = {
},
delete: async ctx => {
const { source } = ctx.request.query;
const params = ctx.params;
const response = await strapi.query(params.model, source).findOne({
id: params.id
});
params.values = Object.keys(JSON.parse(JSON.stringify(response))).reduce((acc, current) => {
const association = (strapi.models[params.model] || strapi.plugins[source].models[params.model]).associations.filter(x => x.alias === current)[0];
// Remove relationships.
if (association) {
acc[current] = _.isArray(response[current]) ? [] : null;
}
return acc;
}, {});
if (!_.isEmpty(params.values)) {
// Run update to remove all relationships.
await strapi.query(params.model, source).update(params);
}
// Delete an entry using `queries` system
const entryDeleted = await strapi.query(params.model, source).delete({
id: params.id
});
ctx.body = entryDeleted;
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].delete(ctx.params, ctx.request.query);
},
};

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-manager",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "A powerful UI to easily manage your data.",
"engines": {
"node": ">= 8.0.0",
@ -46,6 +46,6 @@
},
"devDependencies": {
"react-select": "^1.0.0-rc.5",
"strapi-helper-plugin": "3.0.0-alpha.7.2"
"strapi-helper-plugin": "3.0.0-alpha.7.3"
}
}

View File

@ -0,0 +1,73 @@
'use strict';
const _ = require('lodash');
/**
* A set of functions called "actions" for `ContentManager`
*/
module.exports = {
fetchAll: async (params, query) => {
const { limit, skip = 0, sort, query : request, queryAttribute, source, page } = query;
// Find entries using `queries` system
return await strapi.query(params.model, source).find({
limit,
skip,
sort,
query: request,
queryAttribute
});
},
count: async (params, source) => {
return await strapi.query(params.model, source).count();
},
fetch: async (params, source) => {
return await strapi.query(params.model, source).findOne({
id: params.id
});
},
add: async (params, values, source) => {
// Create an entry using `queries` system
return await strapi.query(params.model, source).create({
values
});
},
edit: async (params, values, source) => {
return strapi.query(params.model, source).update({
id: params.id,
values
});
},
delete: async (params, { source }) => {
const response = await strapi.query(params.model, source).findOne({
id: params.id
});
params.values = Object.keys(JSON.parse(JSON.stringify(response))).reduce((acc, current) => {
const association = (strapi.models[params.model] || strapi.plugins[source].models[params.model]).associations.filter(x => x.alias === current)[0];
// Remove relationships.
if (association) {
acc[current] = _.isArray(response[current]) ? [] : null;
}
return acc;
}, {});
if (!_.isEmpty(params.values)) {
// Run update to remove all relationships.
await strapi.query(params.model, source).update(params);
}
// Delete an entry using `queries` system
return await strapi.query(params.model, source).delete({
id: params.id
});
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

View File

@ -10,8 +10,10 @@ import { FormattedMessage } from 'react-intl';
import IcoBoolean from '../../assets/images/icon_boolean.png';
import IcoDate from '../../assets/images/icon_date.png';
import IcoEmail from '../../assets/images/icon_email.png';
import IcoImage from '../../assets/images/icon_image.png';
import IcoJson from '../../assets/images/icon_json.png';
import IcoPassword from '../../assets/images/icon_password.png';
import IcoNumber from '../../assets/images/icon_number.png';
import IcoRelation from '../../assets/images/icon_relation.png';
import IcoString from '../../assets/images/icon_string.png';
@ -23,9 +25,11 @@ import styles from './styles.scss';
const asset = {
'boolean': IcoBoolean,
'date': IcoDate,
'email': IcoEmail,
'media': IcoImage,
'number': IcoNumber,
'json': IcoJson,
'password': IcoPassword,
'relation': IcoRelation,
'string': IcoString,
'text': IcoText,

View File

@ -5,7 +5,7 @@
align-items: center;
height: 4rem;
margin-top: .6rem;
margin-bottom: .9rem;
margin-bottom: .8rem;
padding: 0 1rem 0 1rem;
border: 1px solid #E3E9F3;
border-radius: 0.25rem;

View File

@ -7,16 +7,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { capitalize } from 'lodash';
import { capitalize, has } from 'lodash';
import PopUpWarning from 'components/PopUpWarning';
import IcoContainer from 'components/IcoContainer';
import IcoBoolean from '../../assets/images/icon_boolean.png';
import IcoDate from '../../assets/images/icon_date.png';
import IcoEmail from '../../assets/images/icon_email.png';
import IcoImage from '../../assets/images/icon_image.png';
import IcoNumber from '../../assets/images/icon_number.png';
import IcoJson from '../../assets/images/icon_json.png';
import IcoPassword from '../../assets/images/icon_password.png';
import IcoRelation from '../../assets/images/icon_relation.png';
import IcoString from '../../assets/images/icon_string.png';
import IcoText from '../../assets/images/icon_text.png';
@ -38,6 +40,8 @@ class AttributeRow extends React.Component { // eslint-disable-line react/prefer
'integer': IcoNumber,
'float': IcoNumber,
'decimal': IcoNumber,
'email': IcoEmail,
'password': IcoPassword,
};
this.state = {
showWarning: false,
@ -62,15 +66,38 @@ class AttributeRow extends React.Component { // eslint-disable-line react/prefer
}
render() {
const isNotEditable = has(this.props.row.params, 'configurable') && !this.props.row.params.configurable;
const relationType = this.props.row.params.type ?
<FormattedMessage id={`content-type-builder.attribute.${this.props.row.params.type}`} />
: <div><FormattedMessage id="content-type-builder.modelPage.attribute.relationWith" /> <span style={{ fontStyle: 'italic' }}>{capitalize(this.props.row.params.target)}</span></div>;
: (
<div>
<FormattedMessage id="content-type-builder.modelPage.attribute.relationWith" />
&nbsp;
<FormattedMessage id="content-type-builder.from">
{(message) => (
<span style={{ fontStyle: 'italic' }}>
{capitalize(this.props.row.params.target)}&nbsp;
{this.props.row.params.pluginValue ? (
`(${message}: ${this.props.row.params.pluginValue})`
) : ''}
</span>
)}
</FormattedMessage>
</div>
);
const relationStyle = !this.props.row.params.type ? styles.relation : '';
const icons = [{ icoType: 'pencil', onClick: this.handleEdit }, { icoType: 'trash', onClick: () => this.setState({ showWarning: !this.state.showWarning }) }];
const icons = isNotEditable ? [{ icoType: 'lock' }] : [{ icoType: 'pencil', onClick: this.handleEdit }, { icoType: 'trash', onClick: () => this.setState({ showWarning: !this.state.showWarning }) }];
const editableStyle = isNotEditable ? '' : styles.editable;
return (
<li className={`${styles.attributeRow} ${relationStyle}`} onClick={this.handleEdit}>
<li
className={`${styles.attributeRow} ${editableStyle} ${relationStyle}`}
onClick={() => {
isNotEditable ? () => {} : this.handleEdit();
}}
>
<div className={styles.flex}>
<div className={styles.nameContainer}>
{this.renderAttributesBox()}

View File

@ -1,5 +1,4 @@
.attributeRow { /* stylelint-disable */
cursor: pointer;
min-height: 5.3rem;
margin-top: 0!important;
list-style: none;
@ -10,9 +9,14 @@
border-bottom: none;
}
}
.editable {
&:hover {
background-color: #F7F8F8;
}
cursor: pointer;
}
.flex {

View File

@ -84,6 +84,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
render() {
const navContainer = this.props.noNav ? '' : this.renderNavContainer();
const modalBodyStyle = this.props.renderModalBody ? { paddingTop: '2.3rem' } : {};
const modalBody = this.props.renderModalBody ? this.props.renderModalBody()
: map(this.props.form.items, (item, key ) => this.renderInput(item, key));
@ -108,7 +109,7 @@ class PopUpForm extends React.Component { // eslint-disable-line react/prefer-st
</div>
{navContainer}
</div>
<ModalBody className={styles.modalBody}>
<ModalBody className={styles.modalBody} style={modalBodyStyle}>
<form onSubmit={this.props.onSubmit}>
<div className="container-fluid">
<div className={`row ${this.props.renderModalBody ? 'justify-content-center' : ''}`}>

View File

@ -28,25 +28,33 @@ class PopUpRelations extends React.Component { // eslint-disable-line react/pref
componentDidMount() {
if (!isEmpty(this.props.dropDownItems) && !this.props.isEditting) {
const target = {
name: 'params.target',
type: 'string',
value: get(this.props.dropDownItems[0], 'name'),
};
this.props.onChange({ target });
this.init(this.props);
}
}
componentWillReceiveProps(nextProps) {
if (isEmpty(this.props.dropDownItems) && !isEmpty(nextProps.dropDownItems) && !this.props.isEditting) {
const target = {
name: 'params.target',
type: 'string',
value: get(nextProps.dropDownItems[0], 'name'),
};
this.init(nextProps);
}
}
this.props.onChange({ target });
init = (props) => {
const target = {
name: 'params.target',
type: 'string',
value: get(props.dropDownItems[0], 'name'),
};
this.props.onChange({ target });
if (get(props.dropDownItems[0], 'source')) {
this.props.onChange({
target: {
type: 'string',
name: 'params.pluginValue',
value: get(props.dropDownItems[0], 'source'),
},
});
}
}
@ -111,42 +119,49 @@ class PopUpRelations extends React.Component { // eslint-disable-line react/pref
</ModalBody>
)
renderModalBodyRelations = () => (
<ModalBody className={`${styles.modalBody} ${styles.flex}`}>
<RelationBox
tabIndex="1"
relationType={get(this.props.values, ['params', 'nature'])}
contentTypeTargetPlaceholder={get(this.props.values, ['params', 'target'])}
isFirstContentType
header={this.props.contentType}
input={get(this.props.form, ['items', '0'])}
value={get(this.props.values, 'name')}
onSubmit={this.props.onSubmit}
onChange={this.props.onChange}
didCheckErrors={this.props.didCheckErrors}
errors={findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '0', 'name'])]) !== -1 ? this.props.formErrors[findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '0', 'name'])])].errors : []}
/>
<RelationNaturePicker
selectedIco={get(this.props.values, ['params', 'nature'])}
onChange={this.props.onChange}
contentTypeName={get(this.props.contentType, 'name')}
contentTypeTarget={get(this.props.values, ['params', 'target'])}
/>
<RelationBox
tabIndex="2"
contentTypeTargetPlaceholder={get(this.props.contentType, 'name')}
relationType={get(this.props.values, ['params', 'nature'])}
onSubmit={this.props.onSubmit}
header={get(this.props.dropDownItems, [findIndex(this.props.dropDownItems, ['name', get(this.props.values, ['params', 'target'])])])}
input={get(this.props.form, ['items', '1'])}
value={get(this.props.values, ['params', 'key'])}
onChange={this.props.onChange}
didCheckErrors={this.props.didCheckErrors}
errors={findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '1', 'name'])]) !== -1 ? this.props.formErrors[findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '1', 'name'])])].errors : []}
dropDownItems={this.props.dropDownItems}
/>
</ModalBody>
)
renderModalBodyRelations = () => {
const header = get(this.props.values, ['params', 'pluginValue']) ?
get(this.props.dropDownItems, [findIndex(this.props.dropDownItems, {'name': get(this.props.values, ['params', 'target']), source: get(this.props.values, ['params', 'pluginValue']) })])
: get(this.props.dropDownItems, [findIndex(this.props.dropDownItems, ['name', get(this.props.values, ['params', 'target'])])]);
return (
<ModalBody className={`${styles.modalBody} ${styles.flex}`}>
<RelationBox
autoFocus
tabIndex="1"
relationType={get(this.props.values, ['params', 'nature'])}
contentTypeTargetPlaceholder={get(this.props.values, ['params', 'target'])}
isFirstContentType
header={this.props.contentType}
input={get(this.props.form, ['items', '0'])}
value={get(this.props.values, 'name')}
onSubmit={this.props.onSubmit}
onChange={this.props.onChange}
didCheckErrors={this.props.didCheckErrors}
errors={findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '0', 'name'])]) !== -1 ? this.props.formErrors[findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '0', 'name'])])].errors : []}
/>
<RelationNaturePicker
selectedIco={get(this.props.values, ['params', 'nature'])}
onChange={this.props.onChange}
contentTypeName={get(this.props.contentType, 'name')}
contentTypeTarget={get(this.props.values, ['params', 'target'])}
/>
<RelationBox
tabIndex="2"
contentTypeTargetPlaceholder={get(this.props.contentType, 'name')}
relationType={get(this.props.values, ['params', 'nature'])}
onSubmit={this.props.onSubmit}
header={header}
input={get(this.props.form, ['items', '1'])}
value={get(this.props.values, ['params', 'key'])}
onChange={this.props.onChange}
didCheckErrors={this.props.didCheckErrors}
errors={findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '1', 'name'])]) !== -1 ? this.props.formErrors[findIndex(this.props.formErrors, ['name', get(this.props.form, ['items', '1', 'name'])])].errors : []}
dropDownItems={this.props.dropDownItems}
/>
</ModalBody>
);
}
render() {
const loader = this.props.showLoader ?

View File

@ -6,6 +6,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { get, isEmpty, map, startCase } from 'lodash';
import pluralize from 'pluralize';
@ -25,13 +26,22 @@ class RelationBox extends React.Component { // eslint-disable-line react/prefer-
}
handleClick = (e) => {
const value = e.target.id.split('.');
const target = {
type: 'string',
value: e.target.id,
value: value[0],
name: 'params.target',
};
this.props.onChange({ target });
this.props.onChange({
target: {
type: 'string',
value: value[1] !== 'undefined' ? value[1] : '',
name: 'params.pluginValue',
},
});
}
toggle = () => this.setState({ showMenu: !this.state.showMenu });
@ -43,13 +53,30 @@ class RelationBox extends React.Component { // eslint-disable-line react/prefer-
</DropdownToggle>
<DropdownMenu className={styles.dropDownContent}>
{map(this.props.dropDownItems, (value, key) => {
const divStyle = get(this.props.header, 'name') === value.name ? { color: '#323740', fontWeight: 'bold'} : { color: 'rgba(50,55,64, 0.75)'};
const id = value.source ? `${value.name}.${value.source}` : `${value.name}. `;
let divStyle;
if (get(this.props.header, 'name') === value.name && !isEmpty(get(this.props.header,'source')) && value.source) {
divStyle = { color: '#323740', fontWeight: 'bold'};
} else if (value.source === get(this.props.header, 'source') && value.name === get(this.props.header, 'name')) {
divStyle = { color: '#323740', fontWeight: 'bold'};
} else {
divStyle = { color: 'rgba(50,55,64,0.75)' };
}
return (
<div style={{ height: '3.6rem'}} key={key}>
<DropdownItem onClick={this.handleClick} id={value.name}>
<div style={divStyle} id={value.name}>
<i className={`fa ${value.icon}`} style={divStyle} />
{value.name}
<DropdownItem onClick={this.handleClick} id={id}>
<div style={divStyle} id={`${value.name}.${value.source}`}>
<i className={`fa ${value.icon}`} style={divStyle} id={id} />
{value.name}&nbsp;
{value.source ? (
<FormattedMessage id="content-type-builder.from">
{(message) => (
<span style={{ fontStyle: 'italic' }} id={id}>({message}: {value.source})</span>
)}
</FormattedMessage>
) : ''}
</div>
</DropdownItem>
</div>
@ -92,6 +119,7 @@ class RelationBox extends React.Component { // eslint-disable-line react/prefer-
errors={this.props.errors}
didCheckErrors={this.props.didCheckErrors}
pluginID="content-type-builder"
autoFocus={this.props.autoFocus}
/>;
const dropDown = !isEmpty(this.props.dropDownItems) ? this.renderDropdownMenu() : '';
@ -100,7 +128,12 @@ class RelationBox extends React.Component { // eslint-disable-line react/prefer-
<div className={styles.relationBox}>
<div className={styles.headerContainer}>
<i className={`fa ${get(this.props.header, 'icon')}`} />
{startCase(get(this.props.header, 'name'))}
{startCase(get(this.props.header, 'name'))}&nbsp;
<span style={{ fontStyle: 'italic', fontWeight: '500' }}>
{get(this.props.header, 'source') ? (
`(${get(this.props.header, 'source')})`
): ''}
</span>
{dropDown}
</div>
<div className={styles.inputContainer}>
@ -118,6 +151,7 @@ class RelationBox extends React.Component { // eslint-disable-line react/prefer-
}
RelationBox.propTypes = {
autoFocus: PropTypes.bool,
contentTypeTargetPlaceholder: PropTypes.string,
didCheckErrors: PropTypes.bool.isRequired,
dropDownItems: PropTypes.array,
@ -133,6 +167,7 @@ RelationBox.propTypes = {
};
RelationBox.defaultProps = {
autoFocus: false,
contentTypeTargetPlaceholder: '',
dropDownItems: [],
errors: [],

View File

@ -35,7 +35,7 @@ class TableListRow extends React.Component { // eslint-disable-line react/prefer
}
handleGoTo = () => {
router.push(`/plugins/content-type-builder/models/${this.props.rowItem.name}`);
router.push(`/plugins/content-type-builder/models/${this.props.rowItem.name}${this.props.rowItem.source ? `&source=${this.props.rowItem.source}`: ''}`);
}
toggleModalWarning = () => this.setState({ showWarning: !this.state.showWarning });
@ -43,15 +43,16 @@ class TableListRow extends React.Component { // eslint-disable-line react/prefer
handleShowModalWarning = () => this.setState({ showWarning: !this.state.showWarning });
render() {
const pluginSource = this.props.rowItem.source ? <FormattedMessage id="content-type-builder.from">{(message) =><span style={{ fontStyle: 'italic', color: '#787E8F', fontWeight: '500' }}>({message}: {this.props.rowItem.source})</span>}</FormattedMessage> : '';
const temporary = this.props.rowItem.isTemporary ? <FormattedMessage id="content-type-builder.contentType.temporaryDisplay" /> : '';
const description = isEmpty(this.props.rowItem.description) ? '-' : this.props.rowItem.description;
const spanStyle = this.props.rowItem.isTemporary ? '60%' : '100%';
const icons = [{ icoType: 'pencil', onClick: this.handleEdit }, { icoType: 'trash', onClick: this.handleShowModalWarning }];
const icons = this.props.rowItem.source ? [] : [{ icoType: 'pencil', onClick: this.handleEdit }, { icoType: 'trash', onClick: this.handleShowModalWarning }];
return (
<ListRow onClick={this.handleGoTo}>
<div className="col-md-1"><i className={`fa ${this.props.rowItem.icon}`} /></div>
<div className={`col-md-3 ${styles.italic} ${styles.nameContainer}`}><span style={{ width: spanStyle }}>{startCase(this.props.rowItem.name)}</span> {temporary}</div>
<div className={`col-md-3 ${styles.italic} ${styles.nameContainer}`}><span style={{ width: spanStyle }}>{startCase(this.props.rowItem.name)} &nbsp;{pluginSource}</span> {temporary}</div>
<div className="col-md-5 text-center">{description}</div>
<div className="col-md-2 text-center">{this.props.rowItem.fields}</div>
<div className="col-md-1">

View File

@ -65,7 +65,7 @@ export function modelsFetch() {
export function modelsFetchSucceeded(models) {
const modelNumber = size(models.models) > 1 ? 'plural' : 'singular';
const sections = storeData.getMenu() || map(models.models, (model) => ({icon: 'fa-caret-square-o-right', name: model.name }));
const sections = storeData.getMenu() || map(models.models, (model) => ({icon: 'fa-caret-square-o-right', name: model.name, source: model.source }));
if (!storeData.getMenu()){
sections.push({ icon: 'fa-plus', name: 'button.contentType.add' });

View File

@ -75,6 +75,14 @@
"type": "json",
"description": "content-type-builder.popUpForm.attributes.json.description"
},
{
"type": "email",
"description": "content-type-builder.popUpForm.attributes.email.description"
},
{
"type": "password",
"description": "content-type-builder.popUpForm.attributes.password.description"
},
{
"type": "relation",
"description": "content-type-builder.popUpForm.attributes.relation.description"
@ -312,6 +320,142 @@
]
}
},
"email": {
"baseSettings": {
"items": [
{
"label": "content-type-builder.form.attribute.item.string.name",
"name": "name",
"type": "string",
"value": "",
"validations": {
"required": true
}
}
]
},
"advancedSettings": {
"items": [
{
"title": "content-type-builder.form.attribute.item.settings.name",
"label": "content-type-builder.form.attribute.item.requiredField",
"name": "params.required",
"type": "checkbox",
"value": false,
"validations": {},
"inputDescription": "content-type-builder.form.attribute.item.requiredField.description"
},
{
"label": "content-type-builder.form.attribute.item.uniqueField",
"name": "params.unique",
"type": "checkbox",
"value": false,
"validations": {},
"inputDescription": "content-type-builder.form.attribute.item.uniqueField.description"
},
{
"label": "content-type-builder.form.attribute.item.minimumLength",
"name": "params.minLength",
"type": "checkbox",
"value": false,
"validations": {},
"items": [
{
"label": "",
"name": "params.minLengthValue",
"value": "",
"type": "number",
"validations": {
"required": true
}
}
]
},
{
"label": "content-type-builder.form.attribute.item.maximumLength",
"name": "params.maxLength",
"type": "checkbox",
"value": false,
"validations": {},
"items": [
{
"label": "",
"name": "params.maxLengthValue",
"value": "",
"type": "number",
"validations": {
"required": true
}
}
]
}
]
}
},
"password": {
"baseSettings": {
"items": [
{
"label": "content-type-builder.form.attribute.item.string.name",
"name": "name",
"type": "string",
"value": "",
"validations": {
"required": true
}
}
]
},
"advancedSettings": {
"items": [
{
"title": "content-type-builder.form.attribute.item.settings.name",
"label": "content-type-builder.form.attribute.item.requiredField",
"name": "params.required",
"type": "checkbox",
"value": false,
"validations": {},
"inputDescription": "content-type-builder.form.attribute.item.requiredField.description"
},
{
"label": "content-type-builder.form.attribute.item.minimumLength",
"name": "params.minLength",
"type": "checkbox",
"value": false,
"validations": {},
"items": [
{
"label": "",
"name": "params.minLengthValue",
"value": "",
"type": "number",
"validations": {
"required": true
}
}
]
},
{
"label": "content-type-builder.form.attribute.item.maximumLength",
"name": "params.maxLength",
"type": "checkbox",
"value": false,
"validations": {},
"items": [
{
"label": "",
"name": "params.maxLengthValue",
"value": "",
"type": "number",
"validations": {
"required": true
}
}
]
}
]
}
},
"date": {
"baseSettings": {
"items": [

View File

@ -530,6 +530,9 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
const selectOptions = includes(this.props.hash, 'attributenumber') ? get(this.props.form, ['items', '1', 'items']) : this.props.selectOptions;
if (includes(popUpFormType, 'relation')) {
const contentType = this.props.modelName.split('&source=');
const contentTypeIndex = contentType.length === 2 ? { name: contentType[0], source: contentType[1] } : { name: contentType[0] };
return (
<PopUpRelations
isOpen={this.state.showModal}
@ -537,7 +540,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer-
renderCustomPopUpHeader={renderCustomPopUpHeader}
popUpTitle={popUpTitle}
routePath={`${this.props.routePath}/${this.props.hash}`}
contentType={get(dropDownItems, [findIndex(dropDownItems, ['name', this.props.modelName])])}
contentType={get(dropDownItems, [findIndex(dropDownItems, contentTypeIndex)])}
form={this.props.form}
showRelation={includes(this.props.hash, 'defineRelation')}
onChange={this.handleChange}

View File

@ -57,7 +57,7 @@ export function* editContentType(action) {
strapi.notification.success('content-type-builder.notification.success.message.contentType.edit');
}
} catch(error) {
strapi.notification.error(error);
strapi.notification.error(get(error, ['response', 'payload', 'message'], 'notification.error'));
}
}
@ -75,10 +75,15 @@ export function* fetchConnections() {
export function* fetchContentType(action) {
try {
const requestUrl = `/content-type-builder/models/${action.contentTypeName.split('&source=')[0]}`;
const params = {};
const source = action.contentTypeName.split('&source=')[1];
const requestUrl = `/content-type-builder/models/${action.contentTypeName}`;
if (source) {
params.source = source;
}
const data = yield call(request, requestUrl, { method: 'GET' });
const data = yield call(request, requestUrl, { method: 'GET', params });
yield put(contentTypeFetchSucceeded(data));

View File

@ -155,10 +155,11 @@ export function setButtonLoader() {
};
}
export function submit(context) {
export function submit(context, modelName) {
return {
type: SUBMIT,
context,
modelName,
};
}

View File

@ -8,7 +8,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { bindActionCreators, compose } from 'redux';
import { get, has, isEmpty, size, replace, startCase, findIndex } from 'lodash';
import { get, has, includes, isEmpty, size, replace, startCase, findIndex } from 'lodash';
import { FormattedMessage } from 'react-intl';
import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
@ -97,7 +97,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
return acc.concat(models);
}, []);
const shouldRedirect = allowedPaths.filter(el => el === this.props.match.params.modelName).length === 0;
const shouldRedirect = allowedPaths.filter(el => el === this.props.match.params.modelName.split('&')[0]).length === 0;
if (shouldRedirect) {
this.props.history.push('/404');
@ -176,7 +176,6 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
const index = findIndex(this.props.modelPage.model.attributes, ['name', attributeName]);
const attribute = this.props.modelPage.model.attributes[index];
const settingsType = attribute.params.type ? 'baseSettings' : 'defineRelation';
const parallelAttributeIndex = findIndex(this.props.modelPage.model.attributes, ['name', attribute.params.key]);
const hasParallelAttribute = settingsType === 'defineRelation' && parallelAttributeIndex !== -1 ? `::${parallelAttributeIndex}` : '';
@ -188,6 +187,10 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
case 'decimal':
attributeType = 'number';
break;
case 'email':
case 'password':
attributeType = 'string';
break;
default:
attributeType = attribute.params.type ? attribute.params.type : 'relation';
}
@ -196,7 +199,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
}
handleSubmit = () => {
this.props.submit(this.context);
this.props.submit(this.context, this.props.match.params.modelName);
}
toggleModal = () => {
@ -218,21 +221,25 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
renderCustomLi = (row, key) => <AttributeRow key={key} row={row} onEditAttribute={this.handleEditAttribute} onDelete={this.handleDelete} />
renderCustomLink = (props, linkStyles) => {
if (props.link.name === 'button.contentType.add') return this.renderAddLink(props, linkStyles);
if (props.link.name === 'button.contentType.add') {
return this.renderAddLink(props, linkStyles);
}
const temporary = props.link.isTemporary || this.props.modelPage.showButtons && props.link.name === this.props.match.params.modelName ? <FormattedMessage id="content-type-builder.contentType.temporaryDisplay" /> : '';
const spanStyle = props.link.isTemporary || this.props.modelPage.showButtons && props.link.name === this.props.match.params.modelName ? styles.leftMenuSpan : '';
const linkName = props.link.source ? `${props.link.name}&source=${props.link.source}` : props.link.name;
const temporary = props.link.isTemporary || this.props.modelPage.showButtons && linkName === this.props.match.params.modelName ? <FormattedMessage id="content-type-builder.contentType.temporaryDisplay" /> : '';
const spanStyle = props.link.isTemporary || this.props.modelPage.showButtons && linkName === this.props.match.params.modelName || isEmpty(temporary) && props.link.source ? styles.leftMenuSpan : '';
const pluginSource = isEmpty(temporary) && props.link.source ? <FormattedMessage id="content-type-builder.from">{(message) => <span style={{ marginRight: '10px' }}>({message}: {props.link.source})</span>}</FormattedMessage>: '';
return (
<li className={linkStyles.pluginLeftMenuLink}>
<NavLink className={linkStyles.link} to={`/plugins/content-type-builder/models/${props.link.name}`} activeClassName={linkStyles.linkActive}>
<NavLink className={linkStyles.link} to={`/plugins/content-type-builder/models/${props.link.name}${props.link.source ? `&source=${props.link.source}` : ''}`} activeClassName={linkStyles.linkActive}>
<div>
<i className={`fa fa-caret-square-o-right`} />
</div>
<div className={styles.contentContainer}>
<span className={spanStyle}>{startCase(props.link.name)}</span>
<span style={{ marginLeft: '1rem', fontStyle: 'italic' }}>{temporary}</span>
<span style={{ marginLeft: '1rem', fontStyle: 'italic' }}>{temporary}{pluginSource}</span>
</div>
</NavLink>
</li>
@ -283,7 +290,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
renderCustomLi={this.renderCustomLi}
onButtonClick={this.handleClickAddAttribute}
/>;
const icoType = includes(this.props.match.params.modelName, '&source=') ? '' : 'pencil';
return (
<div className={styles.modelPage}>
<div className="container-fluid">
@ -299,7 +306,7 @@ export class ModelPage extends React.Component { // eslint-disable-line react/pr
<ContentHeader
name={this.props.modelPage.model.name}
description={contentHeaderDescription}
icoType="pencil"
icoType={icoType}
editIcon
editPath={`${redirectRoute}/${this.props.match.params.modelName}#edit${this.props.match.params.modelName}::contentType::baseSettings`}
addButtons={addButtons}

View File

@ -1,6 +1,7 @@
import { LOCATION_CHANGE } from 'react-router-redux';
import {
capitalize,
cloneDeep,
forEach,
get,
includes,
@ -35,6 +36,7 @@ import { makeSelectModel } from './selectors';
export function* getTableExistance() {
try {
// TODO check table existance for plugin model
const model = yield select(makeSelectModel());
const modelName = !isEmpty(model.collectionName) ? model.collectionName : model.name;
const requestUrl = `/content-type-builder/checkTableExists/${model.connection}/${modelName}`;
@ -49,9 +51,15 @@ export function* getTableExistance() {
export function* fetchModel(action) {
try {
const requestUrl = `/content-type-builder/models/${action.modelName}`;
const requestUrl = `/content-type-builder/models/${action.modelName.split('&source=')[0]}`;
const params = {};
const source = action.modelName.split('&source=')[1];
const data = yield call(request, requestUrl, { method: 'GET' });
if (source) {
params.source = source;
}
const data = yield call(request, requestUrl, { method: 'GET', params });
yield put(modelFetchSucceeded(data));
@ -68,8 +76,8 @@ export function* submitChanges(action) {
yield put(setButtonLoader());
const modelName = get(storeData.getContentType(), 'name');
const body = yield select(makeSelectModel());
const data = yield select(makeSelectModel());
const body = cloneDeep(data);
map(body.attributes, (attribute, index) => {
// Remove the connection key from attributes
@ -82,18 +90,27 @@ export function* submitChanges(action) {
delete body.attributes[index].params.dominant;
}
if (includes(key, 'Value')) {
if (includes(key, 'Value') && key !== 'pluginValue') {
// Remove and set needed keys for params
set(body.attributes[index].params, replace(key, 'Value', ''), value);
unset(body.attributes[index].params, key);
}
if (key === 'pluginValue' && value) {
set(body.attributes[index].params, 'plugin', true);
}
if (!value) {
const paramsKey = includes(key, 'Value') ? replace(key,'Value', '') : key;
unset(body.attributes[index].params, paramsKey);
}
});
});
const pluginModel = action.modelName.split('&source=')[1];
if (pluginModel) {
set(body, 'plugin', pluginModel);
}
const method = modelName === body.name ? 'POST' : 'PUT';
const baseUrl = '/content-type-builder/models/';
@ -130,7 +147,8 @@ export function* submitChanges(action) {
}
} catch(error) {
strapi.notification.error(error);
strapi.notification.error(get(error, ['response', 'payload', 'message'], 'notification.error'));
yield put(unsetButtonLoader());
}
}

View File

@ -9,9 +9,12 @@
"attribute.date": "Date",
"attribute.json": "JSON",
"attribute.media": "Media",
"attribute.email": "Email",
"attribute.password": "Password",
"attribute.relation": "Relation",
"contentType.temporaryDisplay": "(Not saved)",
"from": "from",
"home.contentTypeBuilder.name": "Content Types",
"home.contentTypeBuilder.description": "Create, update your own content types.",
"home.emptyContentType.title": "There are no Content Types Available",
@ -107,6 +110,8 @@
"popUpForm.attributes.json.description": "Data in JSON format",
"popUpForm.attributes.media.description": "Images, videos, PDFs and other files",
"popUpForm.attributes.relation.description": "Refers to a Content Type",
"popUpForm.attributes.email.description": "User's email...",
"popUpForm.attributes.password.description": "User password...",
"popUpForm.attributes.string.name": "String",
"popUpForm.attributes.text.name": "Text",
@ -116,6 +121,8 @@
"popUpForm.attributes.media.name": "Media",
"popUpForm.attributes.number.name": "Number",
"popUpForm.attributes.relation.name": "Relation",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "Password",
"popUpForm.create": "Add New",
"popUpForm.edit": "Edit",
"popUpForm.field": "Field",

View File

@ -9,9 +9,14 @@
"attribute.date": "Date",
"attribute.json": "JSON",
"attribute.media": "Media",
"attribute.password": "Mot de passe",
"attribute.email": "Email",
"attribute.relation": "Relation",
"contentType.temporaryDisplay": "(Non sauvegardé)",
"from": "De",
"home.contentTypeBuilder.name": "Content Types",
"home.contentTypeBuilder.description": "Créez, éditer vos modèles.",
"home.emptyContentType.title": "Il n'y a pas de model disponible",
@ -108,6 +113,8 @@
"popUpForm.attributes.json.description": "Données au format JSON",
"popUpForm.attributes.media.description": "Images, vidéos, PDFs et autres fichiers",
"popUpForm.attributes.relation.description": "Pointe vers un autre Modèle",
"popUpForm.attributes.password.description": "Mot de passe utilisateur...",
"popUpForm.attributes.email.description": "Email utilisateurs",
"popUpForm.attributes.string.name": "Chaîne de caractères",
"popUpForm.attributes.text.name": "Text",
@ -117,6 +124,8 @@
"popUpForm.attributes.json.name": "JSON",
"popUpForm.attributes.media.name": "Media",
"popUpForm.attributes.relation.name": "Relation",
"popUpForm.attributes.email.name": "Email",
"popUpForm.attributes.password.name": "Mot de passe",
"popUpForm.create": "Ajouter un Nouveau",
"popUpForm.edit": "Modifer",
"popUpForm.field": "Champ",

View File

@ -13,13 +13,19 @@ module.exports = {
getModel: async ctx => {
const Service = strapi.plugins['content-type-builder'].services.contenttypebuilder;
const { source } = ctx.request.query;
let { model } = ctx.params;
model = _.toLower(model);
if (!_.get(strapi.models, model)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
if (!source && !_.get(strapi.models, model)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
ctx.send({ model: Service.getModel(model) });
if (source && !_.get(strapi.plugins, [source, 'models', model])) {
return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
}
ctx.send({ model: Service.getModel(model, source) });
},
getConnections: async ctx => {
@ -27,14 +33,14 @@ module.exports = {
},
createModel: async ctx => {
const { name, description, connection, collectionName, attributes = [] } = ctx.request.body;
const { name, description, connection, collectionName, attributes = [], plugin } = ctx.request.body;
if (!name) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.name.missing' }] }]);
if (!_.includes(Service.getConnections(), connection)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.connection.unknow' }] }]);
if (strapi.models[name]) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.exist' }] }]);
if (!_.isNaN(parseFloat(name[0]))) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.name' }] }]);
const [formatedAttributes, attributesErrors] = Service.formatAttributes(attributes);
const [formatedAttributes, attributesErrors] = Service.formatAttributes(attributes, name, plugin);
if (!_.isEmpty(attributesErrors)) {
return ctx.badRequest(null, [{ messages: attributesErrors }]);
@ -44,24 +50,20 @@ module.exports = {
await Service.generateAPI(name, description, connection, collectionName, []);
const [modelFilePath, modelFilePathErrors] = Service.getModelPath(name);
if (!_.isEmpty(modelFilePathErrors)) {
return ctx.badRequest(null, [{ messages: modelFilePathErrors }]);
}
const modelFilePath = Service.getModelPath(name, plugin);
try {
const modelJSON = require(modelFilePath);
const modelJSON = _.cloneDeep(require(modelFilePath));
modelJSON.attributes = formatedAttributes;
const clearRelationsErrors = Service.clearRelations(name);
const clearRelationsErrors = Service.clearRelations(name, plugin);
if (!_.isEmpty(clearRelationsErrors)) {
return ctx.badRequest(null, [{ messages: clearRelationsErrors }]);
}
const createRelationsErrors = Service.createRelations(name, attributes);
const createRelationsErrors = Service.createRelations(name, attributes, plugin);
if (!_.isEmpty(createRelationsErrors)) {
return ctx.badRequest(null, [{ messages: createRelationsErrors }]);
@ -83,25 +85,23 @@ module.exports = {
updateModel: async ctx => {
const { model } = ctx.params;
const { name, description, connection, collectionName, attributes = [] } = ctx.request.body;
const { name, description, connection, collectionName, attributes = [], plugin } = ctx.request.body;
if (!name) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.name.missing' }] }]);
if (!_.includes(Service.getConnections(), connection)) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.connection.unknow' }] }]);
if (strapi.models[_.toLower(name)] && name !== model) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.exist' }] }]);
if (!strapi.models[_.toLower(model)]) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.unknow' }] }]);
if (!_.isNaN(parseFloat(name[0]))) return ctx.badRequest(null, [{ messages: [{ id: 'request.error.model.name' }] }]);
if (plugin && !strapi.plugins[_.toLower(plugin)]) return ctx.badRequest(null, [{ message: [{ id: 'request.error.plugin.name' }] }]);
if (plugin && !strapi.plugins[_.toLower(plugin)].models[_.toLower(model)]) return ctx.badRequest(null, [{ message: [{ id: 'request.error.model.unknow' }] }]);
const [formatedAttributes, attributesErrors] = Service.formatAttributes(attributes);
const [formatedAttributes, attributesErrors] = Service.formatAttributes(attributes, name.toLowerCase(), plugin);
if (!_.isEmpty(attributesErrors)) {
return ctx.badRequest(null, [{ messages: attributesErrors }]);
}
let [modelFilePath, modelFilePathErrors] = Service.getModelPath(model);
if (!_.isEmpty(modelFilePathErrors)) {
return ctx.badRequest(null, [{ messages: modelFilePathErrors }]);
}
let modelFilePath = Service.getModelPath(model, plugin);
strapi.reload.isWatching = false;
@ -110,7 +110,7 @@ module.exports = {
}
try {
const modelJSON = require(modelFilePath);
const modelJSON = _.cloneDeep(require(modelFilePath));
modelJSON.attributes = formatedAttributes;
modelJSON.info = {
@ -120,13 +120,13 @@ module.exports = {
modelJSON.connection = connection;
modelJSON.collectionName = collectionName;
const clearRelationsErrors = Service.clearRelations(model);
const clearRelationsErrors = Service.clearRelations(model, plugin);
if (!_.isEmpty(clearRelationsErrors)) {
return ctx.badRequest(null, [{ messages: clearRelationsErrors }]);
}
const createRelationsErrors = Service.createRelations(name, attributes);
const createRelationsErrors = Service.createRelations(name, attributes, plugin);
if (!_.isEmpty(createRelationsErrors)) {
return ctx.badRequest(null, [{ messages: createRelationsErrors }]);
@ -139,11 +139,7 @@ module.exports = {
return ctx.badRequest(null, [{ messages: removeModelErrors }]);
}
[modelFilePath, modelFilePathErrors] = Service.getModelPath(name);
if (!_.isEmpty(modelFilePathErrors)) {
return ctx.badRequest(null, [{ messages: modelFilePathErrors }]);
}
modelFilePath = Service.getModelPath(name, plugin);
}
try {

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-content-type-builder",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Strapi plugin to create content type (API).",
"strapi": {
"name": "Content Type Builder",
@ -25,11 +25,11 @@
},
"dependencies": {
"pluralize": "^7.0.0",
"strapi-generate": "3.0.0-alpha.7.2",
"strapi-generate-api": "3.0.0-alpha.7.2"
"strapi-generate": "3.0.0-alpha.7.3",
"strapi-generate-api": "3.0.0-alpha.7.3"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.7.2"
"strapi-helper-plugin": "3.0.0-alpha.7.3"
},
"author": {
"name": "Strapi team",

View File

@ -18,13 +18,29 @@ module.exports = {
});
});
return models;
const pluginModels = Object.keys(strapi.plugins).reduce((acc, current) => {
_.forEach(strapi.plugins[current].models, (model, name) => {
acc.push({
icon: 'fa-cube',
name: _.get(model, 'info.name', 'model.name.missing'),
description: _.get(model, 'info.description', 'model.description.missing'),
fields: _.keys(model.attributes).length,
source: current,
});
});
return acc;
}, []);
return models.concat(pluginModels);
},
getModel: name => {
getModel: (name, source) => {
name = _.toLower(name);
const model = _.get(strapi.models, name);
const model = source ? _.get(strapi.plugins, [source, 'models', name]) : _.get(strapi.models, name);
// const model = _.get(strapi.models, name);
const attributes = [];
_.forEach(model.attributes, (params, name) => {
@ -58,8 +74,6 @@ module.exports = {
},
generateAPI: (name, description, connection, collectionName, attributes) => {
description = _.replace(description, /\"/g, '\\"');
const template = _.get(strapi.config.currentEnvironment, `database.connections.${connection}.connector`, 'strapi-mongoose').split('-')[1];
return new Promise((resolve, reject) => {
@ -69,7 +83,7 @@ module.exports = {
rootPath: strapi.config.appPath,
args: {
api: name,
description,
description: _.replace(description, /\"/g, '\\"'),
attributes,
connection,
collectionName: !_.isEmpty(collectionName) ? collectionName : undefined,
@ -81,59 +95,49 @@ module.exports = {
success: () => {
resolve();
},
error: () => {
reject();
error: (err) => {
reject(err);
}
});
});
},
getModelPath: model => {
model = _.toLower(model);
getModelPath: (model, plugin) => {
// Retrieve where is located the model.
// Note: The target is not found when we are creating a new API. That's why, we are returning the lowercased model.
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), model.toLowerCase()))[0] || model.toLowerCase();
let searchFilePath;
const errors = [];
const searchFileName = `${model}.settings.json`;
const apiPath = path.join(strapi.config.appPath, 'api');
// Retrieve the filename of the model.
const filename = fs.readdirSync(plugin ? path.join(strapi.config.appPath, 'plugins', target, 'models') : path.join(strapi.config.appPath, 'api', target, 'models'))
.filter(x => x[0] !== '.')
.filter(x => x.split('.settings.json')[0].toLowerCase() === model.toLowerCase())[0];
try {
const apis = fs.readdirSync(apiPath).filter(x => x[0] !== '.');
_.forEach(apis, api => {
const modelsPath = path.join(apiPath, api, 'models');
try {
const models = fs.readdirSync(modelsPath).filter(x => x[0] !== '.');
const modelIndex = _.indexOf(_.map(models, model => _.toLower(model)), searchFileName);
if (modelIndex !== -1) searchFilePath = `${modelsPath}/${models[modelIndex]}`;
} catch (e) {
errors.push({
id: 'request.error.folder.read',
params: {
folderPath: modelsPath
}
});
}
});
} catch (e) {
errors.push({
id: 'request.error.folder.read',
params: {
folderPath: apiPath
}
});
}
return [searchFilePath, errors];
return plugin ?
path.resolve(strapi.config.appPath, 'plugins', target, 'models', filename):
path.resolve(strapi.config.appPath, 'api', target, 'models', filename);
},
formatAttributes: attributes => {
formatAttributes: (attributes, name, plugin) => {
const errors = [];
const attrs = {};
_.forEach(attributes, attribute => {
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), name))[0] || name.toLowerCase();
const model = (plugin ? _.get(strapi.plugins, [target, 'models', name]) : _.get(strapi.api, [target, 'models', name])) || {};
// Only select configurable attributes.
const attributesConfigurable = attributes.filter(attribute => _.get(model, ['attributes', attribute.name, 'configurable'], true) !== false);
const attributesNotConfigurable = Object.keys(model.attributes || {})
.filter(attribute => _.get(model, ['attributes', attribute, 'configurable'], true) === false)
.reduce((acc, attribute) => {
acc[attribute] = model.attributes[attribute];
return acc;
}, {});
_.forEach(attributesConfigurable, attribute => {
if (_.has(attribute, 'params.type')) {
attrs[attribute.name] = attribute.params;
} else if (_.has(attribute, 'params.target')) {
@ -158,6 +162,7 @@ module.exports = {
attr.via = relation.key;
attr.dominant = relation.dominant;
attr.plugin = relation.pluginValue;
attrs[attribute.name] = attr;
}
@ -172,171 +177,166 @@ module.exports = {
}
});
return [attrs, errors];
Object.assign(attributesNotConfigurable, attrs);
return [attributesNotConfigurable, errors];
},
clearRelations: model => {
model = _.toLower(model);
clearRelations: (model, source) => {
const errors = [];
const apiPath = path.join(strapi.config.appPath, 'api');
const structure = {
models: strapi.models,
plugins: Object.keys(strapi.plugins).reduce((acc, current) => {
acc[current] = {
models: strapi.plugins[current].models
};
try {
const apis = fs.readdirSync(apiPath).filter(x => x[0] !== '.');
return acc;
}, {})
};
_.forEach(apis, api => {
const modelsPath = path.join(apiPath, api, 'models');
// Method to delete the association of the models.
const deleteAssociations = (models, plugin) => {
Object.keys(models).forEach(name => {
const relationsToDelete = _.get(plugin ? strapi.plugins[plugin].models[name] : strapi.models[name], 'associations', []).filter(association => {
if (source) {
return association[association.type] === model && association.plugin === source;
}
try {
const models = fs.readdirSync(modelsPath).filter(x => x[0] !== '.');
return association[association.type] === model;
});
_.forEach(models, modelPath => {
if (_.endsWith(modelPath, '.settings.json')) {
const modelObject = strapi.models[_.lowerCase(_.first(modelPath.split('.')))];
if (!_.isEmpty(relationsToDelete)) {
// Retrieve where is located the model.
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), name))[0];
const relationsToDelete = _.filter(_.get(modelObject, 'associations', []), association => {
return association[association.type] === model;
});
// Retrieve the filename of the model.
const filename = fs.readdirSync(plugin ? path.join(strapi.config.appPath, 'plugins', target, 'models') : path.join(strapi.config.appPath, 'api', target, 'models'))
.filter(x => x[0] !== '.')
.filter(x => x.split('.settings.json')[0].toLowerCase() === name)[0];
const modelFilePath = path.join(modelsPath, modelPath);
// Path to access to the model.
const pathToModel = plugin ?
path.resolve(strapi.config.appPath, 'plugins', target, 'models', filename):
path.resolve(strapi.config.appPath, 'api', target, 'models', filename);
try {
const modelJSON = require(modelFilePath);
// Require the model.
const modelJSON = require(pathToModel);
_.forEach(relationsToDelete, relation => {
modelJSON.attributes[relation.alias] = undefined;
});
_.forEach(relationsToDelete, relation => {
modelJSON.attributes[relation.alias] = undefined;
});
try {
fs.writeFileSync(modelFilePath, JSON.stringify(modelJSON, null, 2), 'utf8');
} catch (e) {
errors.push({
id: 'request.error.model.write',
params: {
filePath: modelFilePath
}
});
}
} catch (e) {
errors.push({
id: 'request.error.model.read',
params: {
filePath: modelFilePath
}
});
try {
fs.writeFileSync(pathToModel, JSON.stringify(modelJSON, null, 2), 'utf8');
} catch (e) {
errors.push({
id: 'request.error.model.write',
params: {
filePath: pathToModel
}
}
});
} catch (e) {
errors.push({
id: 'request.error.folder.read',
params: {
folderPath: modelsPath
}
});
});
}
}
});
} catch (e) {
errors.push({
id: 'request.error.folder.read',
params: {
folderPath: apiPath
}
});
}
};
// Update `./api` models.
deleteAssociations(structure.models);
Object.keys(structure.plugins).forEach(name => {
// Update `./plugins/${name}` models.
deleteAssociations(structure.plugins[name].models, name);
});
return errors;
},
createRelations: (model, attributes) => {
model = _.toLower(model);
createRelations: (model, attributes, source) => {
const errors = [];
const apiPath = path.join(strapi.config.appPath, 'api');
const structure = {
models: strapi.models,
plugins: Object.keys(strapi.plugins).reduce((acc, current) => {
acc[current] = {
models: strapi.plugins[current].models
};
try {
const apis = fs.readdirSync(apiPath).filter(x => x[0] !== '.');
return acc;
}, {})
};
_.forEach(apis, api => {
const modelsPath = path.join(apiPath, api, 'models');
// Method to update the model
const update = (models, plugin) => {
Object.keys(models).forEach(name => {
const relationsToCreate = attributes.filter(attribute => {
if (plugin) {
return _.get(attribute, 'params.target') === name && _.get(attribute, 'params.pluginValue') === plugin;
}
try {
const models = fs.readdirSync(modelsPath).filter(x => x[0] !== '.');
return _.get(attribute, 'params.target') === name && _.isEmpty(_.get(attribute, 'params.pluginValue', ''));
});
_.forEach(models, modelPath => {
if (_.endsWith(modelPath, '.settings.json')) {
const modelName = _.lowerCase(_.first(modelPath.split('.')));
if (!_.isEmpty(relationsToCreate)) {
// Retrieve where is located the model.
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), name))[0];
const relationsToCreate = _.filter(attributes, attribute => {
return _.get(attribute, 'params.target') === modelName;
});
// Retrieve the filename of the model.
const filename = fs.readdirSync(plugin ? path.join(strapi.config.appPath, 'plugins', target, 'models') : path.join(strapi.config.appPath, 'api', target, 'models'))
.filter(x => x[0] !== '.')
.filter(x => x.split('.settings.json')[0].toLowerCase() === name)[0];
if (!_.isEmpty(relationsToCreate)) {
const modelFilePath = path.join(modelsPath, modelPath);
// Path to access to the model.
const pathToModel = plugin ?
path.resolve(strapi.config.appPath, 'plugins', target, 'models', filename):
path.resolve(strapi.config.appPath, 'api', target, 'models', filename);
try {
const modelJSON = require(modelFilePath);
const modelJSON = require(pathToModel);
_.forEach(relationsToCreate, ({ name, params }) => {
const attr = {
columnName: params.targetColumnName,
};
_.forEach(relationsToCreate, ({ name, params }) => {
const attr = {};
switch (params.nature) {
case 'oneToOne':
case 'oneToMany':
attr.model = model;
break;
case 'manyToOne':
case 'manyToMany':
attr.collection = model;
break;
default:
}
switch (params.nature) {
case 'oneToOne':
case 'oneToMany':
attr.model = model.toLowerCase();
break;
case 'manyToOne':
case 'manyToMany':
attr.collection = model.toLowerCase();
break;
default:
}
attr.via = name;
attr.via = name;
attr.columnName = params.targetColumnName;
attr.plugin = source;
modelJSON.attributes[params.key] = attr;
modelJSON.attributes[params.key] = attr;
try {
fs.writeFileSync(modelFilePath, JSON.stringify(modelJSON, null, 2), 'utf8');
} catch (e) {
errors.push({
id: 'request.error.model.write',
params: {
filePath: modelFilePath
}
});
}
});
} catch (e) {
errors.push({
id: 'request.error.model.read',
params: {
filePath: modelFilePath
}
});
try {
fs.writeFileSync(pathToModel, JSON.stringify(modelJSON, null, 2), 'utf8');
} catch (e) {
errors.push({
id: 'request.error.model.write',
params: {
filePath: pathToModel
}
}
}
});
} catch (e) {
errors.push({
id: 'request.error.folder.read',
params: {
folderPath: modelsPath
});
}
});
}
});
} catch (e) {
errors.push({
id: 'request.error.folder.read',
params: {
folderPath: apiPath
}
});
}
};
// Update `./api` models.
update(structure.models);
Object.keys(structure.plugins).forEach(name => {
// Update `./plugins/${name}` models.
update(structure.plugins[name].models, name);
});
return errors;
},

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-email",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Email",
@ -27,7 +27,7 @@
"sendmail": "^1.2.0"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.7.2"
"strapi-helper-plugin": "3.0.0-alpha.7.3"
},
"author": {
"name": "A Strapi developer",

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-settings-manager",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Strapi plugin to manage settings.",
"strapi": {
"name": "Settings Manager",
@ -26,7 +26,7 @@
"devDependencies": {
"flag-icon-css": "^2.8.0",
"react-select": "^1.0.0-rc.5",
"strapi-helper-plugin": "3.0.0-alpha.7.2"
"strapi-helper-plugin": "3.0.0-alpha.7.3"
},
"author": {
"name": "Strapi team",

View File

@ -6,7 +6,7 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { findIndex, has, includes, isEmpty, map, toLower } from 'lodash';
import { findIndex, has, includes, isEmpty, map, toLower, upperFirst } from 'lodash';
import cn from 'classnames';
import PropTypes from 'prop-types';
@ -77,7 +77,7 @@ class InputSearch extends React.Component { // eslint-disable-line react/prefer-
return (
<div className={cn(styles.inputSearch, 'col-md-6')}>
<label htmlFor={this.props.name}>
<FormattedMessage id={this.props.label} values={this.props.labelValues} />
<FormattedMessage id={this.props.label} values={upperFirst(this.props.labelValues)} />
</label>
<div className={cn('input-group')}>
<span className={cn('input-group-addon', styles.addon)} />

View File

@ -38,7 +38,6 @@
label {
margin-bottom: 0;
font-weight: 500;
text-transform: capitalize;
}
input {

View File

@ -8,25 +8,35 @@
"username": {
"type": "string",
"minLength": 3,
"unique": true
"unique": true,
"configurable": false,
"required": true
},
"email": {
"type": "email",
"minLength": 6,
"unique": true
"unique": true,
"configurable": false,
"required": true
},
"provider": {
"type": "string"
"type": "string",
"configurable": false
},
"password": {
"type": "password",
"minLength": 6
"minLength": 6,
"configurable": false,
"required": true
},
"resetPasswordToken": {
"type": "string"
"type": "string",
"configurable": false
},
"role": {
"type": "integer"
"type": "integer",
"configurable": false
}
}
},
"connection": "default"
}

View File

@ -1,6 +1,6 @@
{
"name": "strapi-plugin-users-permissions",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "This is the description of the plugin.",
"strapi": {
"name": "Auth & Permissions",
@ -29,7 +29,7 @@
"uuid": "^3.1.0"
},
"devDependencies": {
"strapi-helper-plugin": "3.0.0-alpha.7.2"
"strapi-helper-plugin": "3.0.0-alpha.7.3"
},
"author": {
"name": "Strapi team",

View File

@ -50,7 +50,7 @@ module.exports = {
process.env.JWT_SECRET || _.get(strapi.plugins['users-permissions'], 'config.jwtSecret') || 'oursecret',
{},
function (err, user) {
if (err || !user || !user.id) {
if (err || !user || !_.get(user, 'id', '').toString()) {
return reject('Invalid token.');
}
resolve(user);

View File

@ -30,6 +30,13 @@ module.exports = {
values.role = '1';
}
// Use Content Manager business logic to handle relation.
if (strapi.plugins['content-manager']) {
return await strapi.plugins['content-manager'].services['contentmanager'].add({
model: 'user'
}, values, 'users-permissions');
}
return strapi.query('user', 'users-permissions').create(values);
},
@ -47,6 +54,11 @@ module.exports = {
values.password = await strapi.plugins['users-permissions'].services.user.hashPassword(values);
}
// Use Content Manager business logic to handle relation.
if (strapi.plugins['content-manager']) {
return await strapi.plugins['content-manager'].services['contentmanager'].edit(params, values, 'users-permissions');
}
return strapi.query('user', 'users-permissions').update(_.assign(params, values));
},
@ -97,6 +109,11 @@ module.exports = {
*/
remove: async params => {
// Use Content Manager business logic to handle relation.
if (strapi.plugins['content-manager']) {
await strapi.plugins['content-manager'].services['contentmanager'].delete(params, 'users-permissions');
}
return strapi.query('user', 'users-permissions').delete(params);
},

View File

@ -1,6 +1,6 @@
{
"name": "strapi-redis",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Redis hook for the Strapi framework",
"homepage": "http://strapi.io",
"keywords": [
@ -18,7 +18,7 @@
"ioredis": "^3.1.2",
"lodash": "^4.17.4",
"stack-trace": "0.0.10",
"strapi-utils": "3.0.0-alpha.7.2"
"strapi-utils": "3.0.0-alpha.7.3"
},
"strapi": {
"isHook": true

View File

@ -73,138 +73,143 @@ module.exports = {
*/
getNature: (association, key, models, currentModelName) => {
const types = {
current: '',
other: ''
};
try {
const types = {
current: '',
other: ''
};
if (_.isUndefined(models)) {
models = global['strapi'].models;
}
if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) {
const relatedAttribute = models[association.collection].attributes[association.via];
types.current = 'collection';
if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.hasOwnProperty('via')) {
types.other = 'collection';
} else if (relatedAttribute.hasOwnProperty('collection') && !relatedAttribute.hasOwnProperty('via')) {
types.other = 'collectionD';
} else if (relatedAttribute.hasOwnProperty('model')) {
types.other = 'model';
if (_.isUndefined(models)) {
models = association.plugin ? strapi.plugins[association.plugin].models : strapi.models;
}
} else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
types.current = 'modelD';
// We have to find if they are a model linked to this key
_.forIn(_.omit(models, currentModelName || ''), model => {
_.forIn(model.attributes, attribute => {
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection')) {
types.other = 'collection';
if (association.hasOwnProperty('via') && association.hasOwnProperty('collection')) {
const relatedAttribute = models[association.collection].attributes[association.via];
// Break loop
return false;
} else if (attribute.hasOwnProperty('model')) {
types.other = 'model';
types.current = 'collection';
// Break loop
return false;
}
});
});
} else if (association.hasOwnProperty('model')) {
types.current = 'model';
if (relatedAttribute.hasOwnProperty('collection') && relatedAttribute.hasOwnProperty('via')) {
types.other = 'collection';
} else if (relatedAttribute.hasOwnProperty('collection') && !relatedAttribute.hasOwnProperty('via')) {
types.other = 'collectionD';
} else if (relatedAttribute.hasOwnProperty('model')) {
types.other = 'model';
}
} else if (association.hasOwnProperty('via') && association.hasOwnProperty('model')) {
types.current = 'modelD';
// We have to find if they are a model linked to this key
_.forIn(models, model => {
_.forIn(model.attributes, attribute => {
if (attribute.hasOwnProperty('via') && attribute.via === key) {
if (attribute.hasOwnProperty('collection')) {
// We have to find if they are a model linked to this key
_.forIn(_.omit(models, currentModelName || ''), model => {
_.forIn(model.attributes, attribute => {
if (attribute.hasOwnProperty('via') && attribute.via === key && attribute.hasOwnProperty('collection')) {
types.other = 'collection';
// Break loop
return false;
} else if (attribute.hasOwnProperty('model')) {
types.other = 'modelD';
types.other = 'model';
// Break loop
return false;
}
}
});
});
});
} else if (association.hasOwnProperty('collection')) {
types.current = 'collectionD';
} else if (association.hasOwnProperty('model')) {
types.current = 'model';
// We have to find if they are a model linked to this key
_.forIn(models, model => {
_.forIn(model.attributes, attribute => {
if (attribute.hasOwnProperty('via') && attribute.via === key) {
if (attribute.hasOwnProperty('collection')) {
types.other = 'collection';
// We have to find if they are a model linked to this key
_.forIn(models, model => {
_.forIn(model.attributes, attribute => {
if (attribute.hasOwnProperty('via') && attribute.via === key) {
if (attribute.hasOwnProperty('collection')) {
types.other = 'collection';
// Break loop
return false;
} else if (attribute.hasOwnProperty('model')) {
types.other = 'modelD';
// Break loop
return false;
} else if (attribute.hasOwnProperty('model')) {
types.other = 'modelD';
// Break loop
return false;
// Break loop
return false;
}
}
}
});
});
});
}
} else if (association.hasOwnProperty('collection')) {
types.current = 'collectionD';
if (types.current === 'modelD' && types.other === 'model') {
return {
nature: 'oneToOne',
verbose: 'belongsTo'
};
} else if (types.current === 'model' && types.other === 'modelD') {
return {
nature: 'oneToOne',
verbose: 'hasOne'
};
} else if ((types.current === 'model' || types.current === 'modelD') && types.other === 'collection') {
return {
nature: 'manyToOne',
verbose: 'belongsTo'
};
} else if (types.current === 'modelD' && types.other === 'collection') {
return {
nature: 'oneToMany',
verbose: 'hasMany'
};
} else if (types.current === 'collection' && types.other === 'model') {
return {
nature: 'oneToMany',
verbose: 'hasMany'
};
} else if (types.current === 'collection' && types.other === 'collection') {
return {
nature: 'manyToMany',
verbose: 'belongsToMany'
};
} else if (types.current === 'collectionD' && types.other === 'collection' || types.current === 'collection' && types.other === 'collectionD') {
return {
nature: 'manyToMany',
verbose: 'belongsToMany'
};
} else if (types.current === 'collectionD' && types.other === '') {
return {
nature: 'manyWay',
verbose: 'belongsToMany'
};
} else if (types.current === 'model' && types.other === '') {
return {
nature: 'oneWay',
verbose: 'belongsTo'
};
}
// We have to find if they are a model linked to this key
_.forIn(models, model => {
_.forIn(model.attributes, attribute => {
if (attribute.hasOwnProperty('via') && attribute.via === key) {
if (attribute.hasOwnProperty('collection')) {
types.other = 'collection';
return undefined;
// Break loop
return false;
} else if (attribute.hasOwnProperty('model')) {
types.other = 'modelD';
// Break loop
return false;
}
}
});
});
}
if (types.current === 'modelD' && types.other === 'model') {
return {
nature: 'oneToOne',
verbose: 'belongsTo'
};
} else if (types.current === 'model' && types.other === 'modelD') {
return {
nature: 'oneToOne',
verbose: 'hasOne'
};
} else if ((types.current === 'model' || types.current === 'modelD') && types.other === 'collection') {
return {
nature: 'manyToOne',
verbose: 'belongsTo'
};
} else if (types.current === 'modelD' && types.other === 'collection') {
return {
nature: 'oneToMany',
verbose: 'hasMany'
};
} else if (types.current === 'collection' && types.other === 'model') {
return {
nature: 'oneToMany',
verbose: 'hasMany'
};
} else if (types.current === 'collection' && types.other === 'collection') {
return {
nature: 'manyToMany',
verbose: 'belongsToMany'
};
} else if (types.current === 'collectionD' && types.other === 'collection' || types.current === 'collection' && types.other === 'collectionD') {
return {
nature: 'manyToMany',
verbose: 'belongsToMany'
};
} else if (types.current === 'collectionD' && types.other === '') {
return {
nature: 'manyWay',
verbose: 'belongsToMany'
};
} else if (types.current === 'model' && types.other === '') {
return {
nature: 'oneWay',
verbose: 'belongsTo'
};
}
return undefined;
} catch (e) {
strapi.log.error(`Something went wrong in the model \`${_.upperFirst(currentModelName)}\` with the attribute \`${key}\``);
strapi.stop();
}
},
/**
@ -220,41 +225,48 @@ module.exports = {
*/
defineAssociations: function (model, definition, association, key) {
// Initialize associations object
if (definition.associations === undefined) {
definition.associations = [];
}
try {
// Initialize associations object
if (definition.associations === undefined) {
definition.associations = [];
}
// Exclude non-relational attribute
if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) {
return undefined;
}
// Exclude non-relational attribute
if (!association.hasOwnProperty('collection') && !association.hasOwnProperty('model')) {
return undefined;
}
// Get relation nature
const infos = this.getNature(association, key, undefined, model.toLowerCase());
const details = _.get(strapi.models, `${association.model || association.collection}.attributes.${association.via}`, {});
// Get relation nature
const infos = this.getNature(association, key, undefined, model.toLowerCase());
const details = _.get(strapi.models, `${association.model || association.collection}.attributes.${association.via}`, {});
// Build associations object
if (association.hasOwnProperty('collection')) {
definition.associations.push({
alias: key,
type: 'collection',
collection: association.collection,
via: association.via || undefined,
nature: infos.nature,
autoPopulate: _.get(association, 'autoPopulate', true),
dominant: details.dominant !== true
});
} else if (association.hasOwnProperty('model')) {
definition.associations.push({
alias: key,
type: 'model',
model: association.model,
via: association.via || undefined,
nature: infos.nature,
autoPopulate: _.get(association, 'autoPopulate', true),
dominant: details.dominant !== true
});
// Build associations object
if (association.hasOwnProperty('collection')) {
definition.associations.push({
alias: key,
type: 'collection',
collection: association.collection,
via: association.via || undefined,
nature: infos.nature,
autoPopulate: _.get(association, 'autoPopulate', true),
dominant: details.dominant !== true,
plugin: association.plugin || undefined,
});
} else if (association.hasOwnProperty('model')) {
definition.associations.push({
alias: key,
type: 'model',
model: association.model,
via: association.via || undefined,
nature: infos.nature,
autoPopulate: _.get(association, 'autoPopulate', true),
dominant: details.dominant !== true,
plugin: association.plugin || undefined,
});
}
} catch (e) {
strapi.log.error(`Something went wrong in the model \`${_.upperFirst(model)}\` with the attribute \`${key}\``);
strapi.stop();
}
},

View File

@ -1,6 +1,6 @@
{
"name": "strapi-utils",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "Shared utilities for the Strapi packages",
"homepage": "http://strapi.io",
"keywords": [

View File

@ -4,7 +4,7 @@
const path = require('path');
const glob = require('glob');
const utils = require('../utils');
const {merge, setWith, get, upperFirst, isString, isEmpty, isObject, pullAll, defaults, isPlainObject, forEach, assign, clone, cloneDeep} = require('lodash');
const {merge, setWith, get, upperFirst, isString, isEmpty, isObject, pullAll, defaults, isPlainObject, forEach, assign, clone, cloneDeep, camelCase} = require('lodash');
module.exports.nested = function() {
return Promise.all([
@ -112,13 +112,17 @@ module.exports.app = async function() {
this.models = Object.keys(this.api || []).reduce((acc, key) => {
for (let index in this.api[key].models) {
if (!this.api[key].models[index].globalId) {
this.api[key].models[index].globalId = upperFirst(index);
this.api[key].models[index].globalId = upperFirst(camelCase(index));
}
if (!this.api[key].models[index].connection) {
this.api[key].models[index].connection = this.config.currentEnvironment.database.defaultConnection;
}
if (!this.api[key].models[index].collectionName) {
this.api[key].models[index].collectionName = (`${index}`).toLowerCase();
}
acc[index] = this.api[key].models[index];
}
return acc;
@ -155,8 +159,12 @@ module.exports.app = async function() {
this.admin.models[key].identity = upperFirst(key);
}
if (!this.admin.models[key].identity) {
this.admin.models[key].identity = this.config.currentEnvironment.database.defaultConnection;
if (!this.admin.models[key].globalId) {
this.admin.models[key].globalId = upperFirst(camelCase(`admin-${key}`));
}
if (!this.admin.models[key].connection) {
this.admin.models[key].connection = this.config.currentEnvironment.database.defaultConnection;
}
acc[key] = this.admin.models[key];
@ -180,6 +188,18 @@ module.exports.app = async function() {
this.plugins[key].models[index].connection = this.config.currentEnvironment.database.defaultConnection;
}
if (!this.plugins[key].models[index].globalId) {
this.plugins[key].models[index].globalId = this.models[index] ?
upperFirst(camelCase(`${key}-${index}`)):
upperFirst(camelCase(`${index}`));
}
if (!this.plugins[key].models[index].collectionName) {
this.plugins[key].models[index].collectionName = this.models[index] ?
(`${key}_${index}`).toLowerCase():
(`${index}`).toLowerCase();
}
sum[index] = this.plugins[key].models[index];
return sum;

View File

@ -9,5 +9,9 @@ global.startedAt = Date.now();
*/
module.exports = function(global) {
return global.strapi = require('./Strapi'); // Strapi instance instanciated
try {
return global.strapi = require('./Strapi'); // Strapi instance instanciated
} catch (error) {
console.log(error);
}
}.call(this, global);

View File

@ -4,8 +4,7 @@
"origin": true,
"expose": [
"WWW-Authenticate",
"Server-Authorization",
"X-Forwarded-Host"
"Server-Authorization"
],
"maxAge": 31536000,
"credentials": true,

View File

@ -9,7 +9,7 @@ module.exports = async function() {
// Set if is admin destination for middleware application.
this.app.use(async (ctx, next) => {
if (ctx.request.header['origin'] === 'http://localhost:4000') {
ctx.request.header['x-forwarded-host'] = 'strapi';
ctx.request.header['x-forwarded-host'] = 'strapi';
}
ctx.request.admin = ctx.request.header['x-forwarded-host'] === 'strapi';

View File

@ -1,6 +1,6 @@
{
"name": "strapi",
"version": "3.0.0-alpha.7.2",
"version": "3.0.0-alpha.7.3",
"description": "An open source solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier.",
"homepage": "http://strapi.io",
"keywords": [
@ -55,14 +55,14 @@
"rimraf": "^2.6.2",
"semver": "^5.4.1",
"stack-trace": "0.0.10",
"strapi-generate": "3.0.0-alpha.7.2",
"strapi-generate-admin": "3.0.0-alpha.7.2",
"strapi-generate-api": "3.0.0-alpha.7.2",
"strapi-generate-new": "3.0.0-alpha.7.2",
"strapi-generate-plugin": "3.0.0-alpha.7.2",
"strapi-generate-policy": "3.0.0-alpha.7.2",
"strapi-generate-service": "3.0.0-alpha.7.2",
"strapi-utils": "3.0.0-alpha.7.2"
"strapi-generate": "3.0.0-alpha.7.3",
"strapi-generate-admin": "3.0.0-alpha.7.3",
"strapi-generate-api": "3.0.0-alpha.7.3",
"strapi-generate-new": "3.0.0-alpha.7.3",
"strapi-generate-plugin": "3.0.0-alpha.7.3",
"strapi-generate-policy": "3.0.0-alpha.7.3",
"strapi-generate-service": "3.0.0-alpha.7.3",
"strapi-utils": "3.0.0-alpha.7.3"
},
"author": {
"email": "hi@strapi.io",