Merge branch 'master' into fix/1317

This commit is contained in:
Jim LAURIE 2018-11-08 18:37:45 +01:00 committed by GitHub
commit 61dd06c742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 246 additions and 129 deletions

View File

@ -13,6 +13,7 @@ before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- git fetch -a
# - sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
# - npm cache clean --force
# - rm -rf node_modules/

File diff suppressed because one or more lines are too long

View File

@ -14,14 +14,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
import AdminPage from 'containers/AdminPage';
import NotFoundPage from 'containers/NotFoundPage';
import NotificationProvider from 'containers/NotificationProvider';
import AppLoader from 'containers/AppLoader';
import LoadingIndicatorPage from 'components/LoadingIndicatorPage';
import '../../styles/main.scss';
import styles from './styles.scss';
export class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
@ -29,12 +27,22 @@ export class App extends React.Component { // eslint-disable-line react/prefer-s
return (
<div>
<NotificationProvider />
<div className={styles.container}>
<Switch>
<Route path="/" component={AdminPage} />
<Route path="" component={NotFoundPage} />
</Switch>
</div>
<AppLoader>
{({ shouldLoad }) => {
if (shouldLoad) {
return <LoadingIndicatorPage />;
}
return (
<div className={styles.container}>
<Switch>
<Route path="/" component={AdminPage} />
<Route path="" component={NotFoundPage} />
</Switch>
</div>
);
}}
</AppLoader>
</div>
);
}

View File

@ -14,6 +14,11 @@ const selectPlugins = () => createSelector(
(appState) => appState.get('plugins')
);
const makeSelectApp = () => createSelector(
selectApp(),
appState => appState.toJS(),
);
const selectHasUserPlugin = () => createSelector(
selectApp(),
(appState) => appState.get('hasUserPlugin'),
@ -38,7 +43,7 @@ const makeSelectAppPlugins = () => createSelector(
selectApp(),
appState => appState.get('appPlugins').toJS(),
);
export default makeSelectApp;
export {
selectApp,
selectHasUserPlugin,

View File

@ -0,0 +1,34 @@
/**
*
* AppLoader
*
*/
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import makeSelectApp from 'containers/App/selectors';
class AppLoader extends React.Component {
shouldLoad = () => {
const { appPlugins, plugins: mountedPlugins } = this.props;
return appPlugins.length !== Object.keys(mountedPlugins).length;
}
render() {
const { children } = this.props;
return children({ shouldLoad: this.shouldLoad() });
}
}
AppLoader.propTypes = {
appPlugins: PropTypes.array.isRequired,
children: PropTypes.func.isRequired,
plugins: PropTypes.object.isRequired,
};
const mapStateToProps = makeSelectApp();
export default connect(mapStateToProps, null)(AppLoader);

View File

@ -291,6 +291,15 @@ module.exports = async cb => {
// Update the displayed fields
const updatedListDisplay = prevListDisplay.filter(obj => obj.name !== currentAttr.join());
// Retrieve the model's displayed fields for the `EditPage`
const fieldsPath = getEditDisplayFieldsPath(attrPath);
// Retrieve the previous settings
const prevEditDisplayFields = _.get(prevSchema.models, fieldsPath);
// Update the fields
const updatedEditDisplayFields = prevEditDisplayFields.filter(field => field !== currentAttr.join());
// Set the new layout
_.set(prevSchema.models, fieldsPath, updatedEditDisplayFields);
if (updatedListDisplay.length === 0) {
// Update it with the one from the generated schema
_.set(prevSchema.models, listDisplayPath, _.get(schema.models, listDisplayPath, []));

View File

@ -4,6 +4,7 @@ const fs = require('fs');
const _ = require('lodash');
const Service = require('../services/ContentTypeBuilder');
const { escapeNewlines } = require('../utils/helpers.js');
module.exports = {
getModels: async ctx => {
@ -47,11 +48,13 @@ module.exports = {
return ctx.badRequest(null, [{ messages: attributesErrors }]);
}
const _description = escapeNewlines(description, '\\n');
strapi.reload.isWatching = false;
await Service.appearance(formatedAttributes, name);
await Service.generateAPI(name, description, connection, collectionName, []);
await Service.generateAPI(name, _description, connection, collectionName, []);
const modelFilePath = await Service.getModelPath(name, plugin);
@ -103,12 +106,14 @@ module.exports = {
return ctx.badRequest(null, [{ messages: attributesErrors }]);
}
const _description = escapeNewlines(description);
let modelFilePath = Service.getModelPath(model, plugin);
strapi.reload.isWatching = false;
if (name !== model) {
await Service.generateAPI(name, description, connection, collectionName, []);
await Service.generateAPI(name, _description, connection, collectionName, []);
}
await Service.appearance(formatedAttributes, name, plugin);
@ -120,7 +125,7 @@ module.exports = {
modelJSON.collectionName = collectionName;
modelJSON.info = {
name,
description
description: _description
};
modelJSON.attributes = formatedAttributes;

View File

@ -61,10 +61,15 @@ const reorderList = (manager, list) => {
return List(flattenDeep(reordered));
};
const escapeNewlines = (content, placeholder = '\n') => {
return content.replace(/[\r\n]+/g, placeholder);
}
module.exports = {
createArrayOfLastEls,
createManager,
getElementsOnALine,
removeColsLine,
reorderList,
escapeNewlines
};

View File

@ -9,6 +9,7 @@
const _ = require('lodash');
const pluralize = require('pluralize');
const Schema = require('./Schema.js');
/* eslint-disable no-unused-vars */
module.exports = {
/**
@ -387,22 +388,6 @@ module.exports = {
};
},
/**
* Returns a list of fields that have type included in fieldTypes.
*/
getFieldsByTypes: (fields, typeCheck, returnType) => {
return _.reduce(
fields,
(acc, fieldType, fieldName) => {
if (typeCheck(fieldType)) {
acc[fieldName] = returnType(fieldType, fieldName);
}
return acc;
},
{},
);
},
/**
* Generate the connection type of each non-array field of the model
*

View File

@ -10,6 +10,7 @@ const _ = require('lodash');
const pluralize = require('pluralize');
const policyUtils = require('strapi-utils').policy;
const Query = require('./Query.js');
/* eslint-disable no-unused-vars */
module.exports = {
/**
@ -64,10 +65,7 @@ module.exports = {
const [name, action] = handler.split('.');
const controller = plugin
? _.get(
strapi.plugins,
`${plugin}.controllers.${_.toLower(name)}.${action}`,
)
? _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`)
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
if (!controller) {
@ -148,10 +146,7 @@ module.exports = {
const [name, action] = resolverOf.split('.');
const controller = plugin
? _.get(
strapi.plugins,
`${plugin}.controllers.${_.toLower(name)}.${action}`,
)
? _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`)
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
if (!controller) {

View File

@ -95,10 +95,7 @@ module.exports = {
const [name, action] = handler.split('.');
const controller = plugin
? _.get(
strapi.plugins,
`${plugin}.controllers.${_.toLower(name)}.${action}`,
)
? _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`)
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
if (!controller) {
@ -199,10 +196,7 @@ module.exports = {
const [name, action] = resolverOf.split('.');
const controller = plugin
? _.get(
strapi.plugins,
`${plugin}.controllers.${_.toLower(name)}.${action}`,
)
? _.get(strapi.plugins, `${plugin}.controllers.${_.toLower(name)}.${action}`)
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
if (!controller) {

View File

@ -65,11 +65,11 @@ module.exports = {
});
Object.assign(acc.resolver[globalId], {
createdAt: (obj, options, context) => {
createdAt: (obj) => {
// eslint-disable-line no-unused-vars
return obj.createdAt || obj.created_at;
},
updatedAt: (obj, options, context) => {
updatedAt: (obj) => {
// eslint-disable-line no-unused-vars
return obj.updatedAt || obj.updated_at;
},
@ -154,7 +154,7 @@ module.exports = {
Object.keys(queries).forEach(type => {
// The query cannot be built.
if (_.isError(queries[type])) {
console.error(queries[type]);
strapi.log.error(queries[type]);
strapi.stop();
}
@ -303,7 +303,7 @@ module.exports = {
case 'manyMorphToMany':
case 'manyToManyMorph':
return _.merge(acc.resolver[globalId], {
[association.alias]: async (obj, options, context) => {
[association.alias]: async (obj) => {
// eslint-disable-line no-unused-vars
const [withRelated, withoutRelated] = await Promise.all([
resolvers.fetch(
@ -362,7 +362,7 @@ module.exports = {
}
_.merge(acc.resolver[globalId], {
[association.alias]: async (obj, options, context) => {
[association.alias]: async (obj, options) => {
// eslint-disable-line no-unused-vars
// Construct parameters object to retrieve the correct related entries.
const params = {
@ -415,7 +415,8 @@ module.exports = {
}).where;
break;
// falls through
}
}
break;
default:
// Where.
queryOpts.query = strapi.utils.models.convertParams(name, {

View File

@ -135,22 +135,22 @@ module.exports = {
const shadowCRUD =
strapi.plugins.graphql.config.shadowCRUD !== false
? (() => {
// Exclude core models.
// Exclude core models.
const models = Object.keys(strapi.models).filter(
model => model !== 'core_store',
);
model => model !== 'core_store',
);
// Reproduce the same pattern for each plugin.
return Object.keys(strapi.plugins).reduce((acc, plugin) => {
const {
definition,
query,
mutation,
resolver,
} = Resolvers.shadowCRUD(
Object.keys(strapi.plugins[plugin].models),
plugin,
);
definition,
query,
mutation,
resolver,
} = Resolvers.shadowCRUD(
Object.keys(strapi.plugins[plugin].models),
plugin,
);
// We cannot put this in the merge because it's a string.
acc.definition += definition || '';
@ -303,7 +303,7 @@ module.exports = {
if (err && err.code === 'ENOENT') {
fs.mkdirSync(generatedFolder);
} else {
console.error(err);
strapi.log.error(err);
}
}

View File

@ -12,6 +12,7 @@ const graphql = require('graphql');
const GraphQLJSON = require('graphql-type-json');
const GraphQLDateTime = require('graphql-type-datetime');
const pluralize = require('pluralize');
/* eslint-disable no-unused-vars */
module.exports = {
/**
@ -212,7 +213,7 @@ module.exports = {
const inputName = `${_.capitalize(name)}Input`;
const payloadName = `${_.capitalize(name)}Payload`;
/* eslint-disable indent */
switch (type) {
case 'create':
return `
@ -238,5 +239,6 @@ module.exports = {
default:
// Nothing
}
/* eslint-enable indent */
},
};

View File

@ -21,6 +21,7 @@ import Button from 'components/Button';
import Input from 'components/InputsIndex';
// Utils
import auth from 'utils/auth';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
@ -40,47 +41,90 @@ import styles from './styles.scss';
export class AuthPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentDidMount() {
const params = this.props.location.search ? replace(this.props.location.search, '?code=', '') : this.props.match.params.id;
this.props.setForm(this.props.match.params.authType, params);
auth.clearAppStorage();
this.setForm();
}
componentWillReceiveProps(nextProps) {
if (this.props.match.params.authType !== nextProps.match.params.authType) {
const params = nextProps.location.search ? replace(nextProps.location.search, '?code=', '') : nextProps.match.params.id;
this.props.setForm(nextProps.match.params.authType, params);
this.props.hideLoginErrorsInput(false);
componentDidUpdate(prevProps) {
const {
hideLoginErrorsInput,
match: {
params : {
authType,
},
},
submitSuccess,
} = this.props;
if (authType !== prevProps.match.params.authType) {
this.setForm();
hideLoginErrorsInput(false);
}
if (nextProps.submitSuccess) {
switch (this.props.match.params.authType) {
if (submitSuccess) {
switch (authType) {
case 'login':
case 'reset-password':
this.props.history.push('/');
case 'reset-password':
// Check if we have token to handle redirection to login or admin.
// Done to prevent redirection to admin after reset password if user should
// not have access.
auth.getToken()
? this.redirect('/')
: this.redirect('/plugins/users-permissions/auth/login');
break;
case 'register':
this.props.history.push('/');
this.redirect('/');
// NOTE: prepare for comfirm email;
// this.props.history.push(`/plugins/users-permissions/auth/register-success/${this.props.modifiedData.email}`);
// this.redirect(`/plugins/users-permissions/auth/register-success/${this.props.modifiedData.email}`);
break;
default:
}
}
}
// Get form Errors shortcut.
getFormErrors = () => {
const { formErrors } = this.props;
return get(formErrors, ['0', 'errors', '0', 'id']);
}
setForm = () => {
const {
location: {
search,
},
match: {
params: {
authType,
id,
},
},
setForm,
} = this.props;
const params = search ? replace(search, '?code=', '') : id;
setForm(authType, params);
}
isAuthType = type => {
const { match: { params: { authType } } } = this.props;
return authType === type;
}
handleSubmit = (e) => {
const { modifiedData, setErrors, submit } = this.props;
e.preventDefault();
const formErrors = Object.keys(this.props.modifiedData).reduce((acc, key) => {
if (isEmpty(get(this.props.modifiedData, key)) && !isBoolean(get(this.props.modifiedData, key))) {
const formErrors = Object.keys(modifiedData).reduce((acc, key) => {
if (isEmpty(get(modifiedData, key)) && !isBoolean(get(modifiedData, key))) {
acc.push({ name: key, errors: [{ id: 'components.Input.error.validation.required' }] });
}
if (!isEmpty(get(this.props.modifiedData, 'password')) && !isEmpty(get(this.props.modifiedData, 'confirmPassword')) && findIndex(acc, ['name', 'confirmPassword']) === -1) {
if (this.props.modifiedData.password.length < 6) {
if (!isEmpty(get(modifiedData, 'password')) && !isEmpty(get(modifiedData, 'confirmPassword')) && findIndex(acc, ['name', 'confirmPassword']) === -1) {
if (modifiedData.password.length < 6) {
acc.push({ name: 'password', errors: [{ id: 'users-permissions.components.Input.error.password.length' }] });
}
if (get(this.props.modifiedData, 'password') !== get(this.props.modifiedData, 'confirmPassword')) {
if (get(modifiedData, 'password') !== get(modifiedData, 'confirmPassword')) {
acc.push({ name: 'confirmPassword', errors: [{ id: 'users-permissions.components.Input.error.password.noMatch' }] });
}
}
@ -88,25 +132,27 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
return acc;
}, []);
this.props.setErrors(formErrors);
setErrors(formErrors);
if (isEmpty(formErrors)) {
this.props.submit(this.context);
submit(this.context);
}
}
redirect = path => this.props.history.push(path);
renderButton = () => {
const { match: { params: { authType } }, submitSuccess } = this.props;
if (this.props.match.params.authType === 'login') {
if (this.isAuthType('login')) {
return (
<div className={cn('col-md-6', styles.loginButton)}>
<Button primary label="users-permissions.Auth.form.button.login" type="submit" />
</div>
);
}
const isEmailForgotSent = authType === 'forgot-password' && submitSuccess;
const label = isEmailForgotSent ? 'users-permissions.Auth.form.button.forgot-password.success' : `users-permissions.Auth.form.button.${this.props.match.params.authType}`;
const isEmailForgotSent = this.isAuthType('forgot-password') && submitSuccess;
const label = isEmailForgotSent ? 'users-permissions.Auth.form.button.forgot-password.success' : `users-permissions.Auth.form.button.${authType}`;
return (
<div className={cn('col-md-12', styles.buttonContainer)}>
@ -121,9 +167,10 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
);
}
renderLogo = () => this.isAuthType('register') && <div className={styles.logoContainer}><img src={LogoStrapi} alt="logo" /></div>;
renderLink = () => {
if (this.props.match.params.authType === 'login') {
if (this.isAuthType('login')) {
return (
<Link to="/plugins/users-permissions/auth/forgot-password">
<FormattedMessage id="users-permissions.Auth.link.forgot-password" />
@ -131,7 +178,7 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
);
}
if (this.props.match.params.authType === 'forgot-password' || this.props.match.params.authType === 'register-success') {
if (this.isAuthType('forgot-password') || this.isAuthType('register-success')) {
return (
<Link to="/plugins/users-permissions/auth/login">
<FormattedMessage id="users-permissions.Auth.link.ready" />
@ -143,33 +190,53 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
}
renderInputs = () => {
const { match: { params: { authType } } } = this.props;
const {
didCheckErrors,
formErrors,
match: {
params: {
authType,
},
},
modifiedData,
noErrorsDescription,
onChangeInput,
submitSuccess,
} = this.props;
const inputs = get(form, ['form', authType]);
return map(inputs, (input, key) => (
<Input
autoFocus={key === 0}
customBootstrapClass={get(input, 'customBootstrapClass')}
didCheckErrors={this.props.didCheckErrors}
errors={get(this.props.formErrors, [findIndex(this.props.formErrors, ['name', input.name]), 'errors'])}
key={get(input, 'name')}
label={authType === 'forgot-password' && this.props.submitSuccess? { id: 'users-permissions.Auth.form.forgot-password.email.label.success' } : get(input, 'label')}
name={get(input, 'name')}
onChange={this.props.onChangeInput}
placeholder={get(input, 'placeholder')}
type={get(input, 'type')}
validations={{ required: true }}
value={get(this.props.modifiedData, get(input, 'name'), get(input, 'value'))}
noErrorsDescription={this.props.noErrorsDescription}
/>
));
const isForgotEmailSent = this.isAuthType('forgot-password') && submitSuccess;
return map(inputs, (input, key) => {
const label =
isForgotEmailSent
? { id: 'users-permissions.Auth.form.forgot-password.email.label.success' }
: get(input, 'label');
return (
<Input
autoFocus={key === 0}
customBootstrapClass={get(input, 'customBootstrapClass')}
didCheckErrors={didCheckErrors}
errors={get(formErrors, [findIndex(formErrors, ['name', input.name]), 'errors'])}
key={get(input, 'name')}
label={label}
name={get(input, 'name')}
onChange={onChangeInput}
placeholder={get(input, 'placeholder')}
type={get(input, 'type')}
validations={{ required: true }}
value={get(modifiedData, get(input, 'name'), get(input, 'value'))}
noErrorsDescription={noErrorsDescription}
/>
);
});
}
render() {
const { match: { params: { authType } }, modifiedData, submitSuccess } = this.props;
let divStyle = authType === 'register' ? { marginTop: '3.2rem' } : { marginTop: '.9rem' };
const { modifiedData, noErrorsDescription, submitSuccess } = this.props;
let divStyle = this.isAuthType('register') ? { marginTop: '3.2rem' } : { marginTop: '.9rem' };
if (authType === 'forgot-password' && submitSuccess) {
if (this.isAuthType('forgot-password') && submitSuccess) {
divStyle = { marginTop: '.9rem', minHeight: '18.2rem' };
}
@ -177,33 +244,33 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
<div className={styles.authPage}>
<div className={styles.wrapper}>
<div className={styles.headerContainer}>
{this.props.match.params.authType === 'register' ? (
{this.isAuthType('register') ? (
<FormattedMessage id="users-permissions.Auth.form.header.register" />
) : (
<img src={LogoStrapi} alt="logo" />
)}
</div>
<div className={styles.headerDescription}>
{authType === 'register' && <FormattedMessage id="users-permissions.Auth.header.register.description" />}
{this.isAuthType('register') && <FormattedMessage id="users-permissions.Auth.header.register.description" />}
</div>
<div
className={cn(
styles.formContainer,
authType === 'forgot-password' && submitSuccess ? styles.borderedSuccess : styles.bordered,
this.isAuthType('forgot-password') && submitSuccess ? styles.borderedSuccess : styles.bordered,
)}
style={divStyle}
>
<form onSubmit={this.handleSubmit}>
<div className="container-fluid">
{this.props.noErrorsDescription && !isEmpty(get(this.props.formErrors, ['0', 'errors', '0', 'id']))? (
{noErrorsDescription && !isEmpty(this.getFormErrors())? (
<div className={styles.errorsContainer}>
<FormattedMessage id={get(this.props.formErrors, ['0', 'errors', '0', 'id'])} />
<FormattedMessage id={this.getFormErrors()} />
</div>
): ''}
<div className="row" style={{ textAlign: 'start' }}>
{!submitSuccess && this.renderInputs()}
{ authType === 'forgot-password' && submitSuccess && (
{ this.isAuthType('forgot-password') && submitSuccess && (
<div className={styles.forgotSuccess}>
<FormattedMessage id="users-permissions.Auth.form.forgot-password.email.label.success" />
<br />
@ -219,7 +286,7 @@ export class AuthPage extends React.Component { // eslint-disable-line react/pre
{this.renderLink()}
</div>
</div>
{authType === 'register' && <div className={styles.logoContainer}><img src={LogoStrapi} alt="logo" /></div>}
{this.renderLogo()}
</div>
);
}

View File

@ -1,16 +1,19 @@
import { get, includes, isArray, set, omit } from 'lodash';
import { call, fork, takeLatest, put, select } from 'redux-saga/effects';
import { get, includes, isArray, omit, set } from 'lodash';
import { call, fork, put, select, takeLatest } from 'redux-saga/effects';
import auth from 'utils/auth';
import request from 'utils/request';
import { makeSelectFormType, makeSelectModifiedData } from './selectors';
import { hideLoginErrorsInput, submitError, submitSucceeded } from './actions';
import { SUBMIT } from './constants';
import { makeSelectFormType, makeSelectModifiedData } from './selectors';
export function* submitForm(action) {
try {
const formType = yield select(makeSelectFormType());
const body = yield select(makeSelectModifiedData());
const formType = yield select(makeSelectFormType());
const isRegister = formType === 'register';
let requestURL;
switch (formType) {
@ -33,12 +36,13 @@ export function* submitForm(action) {
const response = yield call(request, requestURL, { method: 'POST', body: omit(body, 'news') });
if (response.jwt) {
if(get(response, 'user.role.name', '') === 'Administrator' || isRegister){
yield call(auth.setToken, response.jwt, body.rememberMe);
yield call(auth.setUserInfo, response.user, body.rememberMe);
}
if (formType === 'register') {
if (isRegister) {
action.context.updatePlugin('users-permissions', 'hasAdminUser', true);
if (body.news) {

View File

@ -199,7 +199,7 @@ module.exports = {
settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.object, {
USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider'])
});
try {
// Send an email to the user.
await strapi.plugins['email'].services.email.send({

View File

@ -91,6 +91,8 @@ module.exports = {
ctx.request.body.role = defaultRole._id || defaultRole.id;
}
ctx.request.body.provider = 'local';
try {
const data = await strapi.plugins['users-permissions'].services.user.add(ctx.request.body);
// Send 201 `created`