diff --git a/packages/strapi-generate-new/lib/before.js b/packages/strapi-generate-new/lib/before.js index 00be647da5..1f6381aac8 100755 --- a/packages/strapi-generate-new/lib/before.js +++ b/packages/strapi-generate-new/lib/before.js @@ -152,7 +152,7 @@ module.exports = (scope, cb) => { when: !hasDatabaseConfig, type: 'input', name: 'port', - message: 'Port (It will be ignored if you enable +srv):', + message: `Port${scope.client.database === 'mongo' ? ' (It will be ignored if you enable +srv)' : ''}:`, default: (answers) => { // eslint-disable-line no-unused-vars if (_.get(scope.database, 'port')) { return scope.database.port; @@ -186,7 +186,7 @@ module.exports = (scope, cb) => { when: !hasDatabaseConfig && scope.client.database === 'mongo', type: 'input', name: 'authenticationDatabase', - message: 'Authentication database:', + message: 'Authentication database (Maybe "admin" or blank):', default: _.get(scope.database, 'authenticationDatabase', undefined) }, { @@ -203,7 +203,7 @@ module.exports = (scope, cb) => { } scope.database.settings.host = answers.host; - scope.database.settings.srv = answers.srv; + scope.database.settings.srv = _.toString(answers.srv) === 'true'; scope.database.settings.port = answers.port; scope.database.settings.database = answers.database; scope.database.settings.username = answers.username; diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js b/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js index 8763ba4ae6..c118a3396d 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputFile/index.js @@ -8,7 +8,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, isArray, isObject } from 'lodash'; +import cn from 'classnames'; import ImgPreview from 'components/ImgPreview'; import InputFileDetails from 'components/InputFileDetails'; @@ -59,7 +60,8 @@ class InputFile extends React.Component { type: 'file', value, }; - + + this.inputFile.value = ''; this.setState({ isUploading: !this.state.isUploading }); this.props.onChange({ target }); } @@ -75,11 +77,12 @@ class InputFile extends React.Component { if (this.props.multiple) { value.splice(this.state.position, 1); } + // Update the parent's props const target = { name: this.props.name, type: 'file', - value, + value: Object.keys(value).length === 0 ? '' : value, }; this.props.onChange({ target }); @@ -98,6 +101,19 @@ class InputFile extends React.Component { this.setState({ position: newPosition }); } + isVisibleDetails = () => { + const {value} = this.props; + + if (!value || + (isArray(value) && value.length === 0) || + (isObject(value) && Object.keys(value).length === 0) + ) { + return false; + } + + return true; + } + render() { const { multiple, @@ -108,39 +124,43 @@ class InputFile extends React.Component { return (
- - +
+ {this.isVisibleDetails() && ( + + )} ); } @@ -150,9 +170,12 @@ InputFile.defaultProps = { multiple: false, setLabel: () => {}, value: [], + error: false, + }; InputFile.propTypes = { + error: PropTypes.bool, multiple: PropTypes.bool, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss index c6c81e3692..26799e7784 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputFile/styles.scss @@ -32,3 +32,7 @@ .copy { cursor: copy !important; } + +.inputFileControlForm { + padding: 0; +} \ No newline at end of file diff --git a/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js index 1167cb4b1b..ed08566151 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputFileWithErrors/index.js @@ -14,16 +14,27 @@ import Label from 'components/Label'; import InputDescription from 'components/InputDescription'; import InputFile from 'components/InputFile'; import InputSpacer from 'components/InputSpacer'; +import InputErrors from 'components/InputErrors'; +// Styles import styles from './styles.scss'; class InputFileWithErrors extends React.PureComponent { - state = { label: null, hasValue: false }; + state = { errors: [], label: null, hasValue: false }; componentDidMount() { + const { errors } = this.props; + let newState = Object.assign({}, this.state); + if (this.props.multiple && !isEmpty(this.props.value)) { - this.setState({ label: 1, hasValue: true }); + newState = Object.assign({}, newState, { label: 1, hasValue: true }); } + + if (!isEmpty(errors)) { + newState = Object.assign({}, newState, { errors }); + } + + this.setState(newState); } componentDidUpdate(prevProps) { @@ -32,6 +43,12 @@ class InputFileWithErrors extends React.PureComponent { } else if(isEmpty(this.props.value)) { this.updateState({ label: null }); } + // Check if errors have been updated during validations + if (nextProps.didCheckErrors !== this.props.didCheckErrors) { + // Remove from the state the errors that have already been set + const errors = isEmpty(nextProps.errors) ? [] : nextProps.errors; + this.setState({ errors }); + } } setLabel = (label) => { @@ -47,6 +64,9 @@ class InputFileWithErrors extends React.PureComponent { const { className, customBootstrapClass, + errorsClassName, + errorsStyle, + noErrorsDescription, inputDescription, inputDescriptionClassName, inputDescriptionStyle, @@ -83,6 +103,7 @@ class InputFileWithErrors extends React.PureComponent { )} + {spacer} ); @@ -100,8 +126,12 @@ class InputFileWithErrors extends React.PureComponent { } InputFileWithErrors.defaultProps = { + errors: [], + errorsClassName: '', + errorsStyle: {}, className: '', customBootstrapClass: 'col-md-6', + didCheckErrors: false, inputDescription: '', inputDescriptionClassName: '', inputDescriptionStyle: {}, @@ -109,6 +139,7 @@ InputFileWithErrors.defaultProps = { labelClassName: '', labelStyle: {}, multiple: false, + noErrorsDescription: false, style: {}, value: [], }; @@ -116,6 +147,10 @@ InputFileWithErrors.defaultProps = { InputFileWithErrors.propTypes = { className: PropTypes.string, customBootstrapClass: PropTypes.string, + didCheckErrors: PropTypes.bool, + errors: PropTypes.array, + errorsClassName: PropTypes.string, + errorsStyle: PropTypes.object, inputDescription: PropTypes.oneOfType([ PropTypes.string, PropTypes.func, @@ -138,6 +173,7 @@ InputFileWithErrors.propTypes = { labelStyle: PropTypes.object, multiple: PropTypes.bool, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onChange: PropTypes.func.isRequired, style: PropTypes.object, value: PropTypes.oneOfType([ diff --git a/packages/strapi-hook-bookshelf/lib/relations.js b/packages/strapi-hook-bookshelf/lib/relations.js index 09a6a26291..e22b8418d1 100644 --- a/packages/strapi-hook-bookshelf/lib/relations.js +++ b/packages/strapi-hook-bookshelf/lib/relations.js @@ -97,7 +97,7 @@ module.exports = { module.exports.findOne .call(model, { [model.primaryKey]: recordId }, [details.via]) .then(record => { - if (record && _.isObject(record[details.via])) { + if (record && _.isObject(record[details.via]) && record.id !== record[details.via][current]) { return module.exports.update.call(this, { id: getValuePrimaryKey(record[details.via], model.primaryKey), values: { diff --git a/packages/strapi-hook-mongoose/lib/index.js b/packages/strapi-hook-mongoose/lib/index.js index a0c3e5e9d4..2ad6a41574 100755 --- a/packages/strapi-hook-mongoose/lib/index.js +++ b/packages/strapi-hook-mongoose/lib/index.js @@ -51,9 +51,10 @@ module.exports = function (strapi) { initialize: cb => { _.forEach(_.pickBy(strapi.config.connections, {connector: 'strapi-hook-mongoose'}), (connection, connectionName) => { const instance = new Mongoose(); - const { uri, host, port, username, password, database } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); + const { uri, host, port, username, password, database, srv } = _.defaults(connection.settings, strapi.config.hook.settings.mongoose); const uriOptions = uri ? url.parse(uri, true).query : {}; const { authenticationDatabase, ssl, debug } = _.defaults(connection.options, uriOptions, strapi.config.hook.settings.mongoose); + const isSrv = srv === true || srv === 'true'; // Connect to mongo database const connectOptions = {}; @@ -73,10 +74,16 @@ module.exports = function (strapi) { connectOptions.ssl = ssl === true || ssl === 'true'; connectOptions.useNewUrlParser = true; + connectOptions.dbName = database; options.debug = debug === true || debug === 'true'; - instance.connect(uri || `mongodb://${host}:${port}/${database}`, connectOptions); + /* FIXME: for now, mongoose doesn't support srv auth except the way including user/pass in URI. + * https://github.com/Automattic/mongoose/issues/6881 */ + instance.connect(uri || + `mongodb${isSrv ? '+srv' : ''}://${username}:${password}@${host}${ !isSrv ? ':' + port : '' }/`, + connectOptions + ); for (let key in options) { instance.set(key, options[key]); diff --git a/packages/strapi-hook-mongoose/lib/relations.js b/packages/strapi-hook-mongoose/lib/relations.js index 1fbe976cac..0dcf8d35b7 100644 --- a/packages/strapi-hook-mongoose/lib/relations.js +++ b/packages/strapi-hook-mongoose/lib/relations.js @@ -50,7 +50,7 @@ module.exports = { .findOne({ [model.primaryKey]: value[current] }) .populate(details.via) .then(record => { - if (record && _.isObject(record[details.via])) { + if (record && _.isObject(record[details.via]) && record._id.toString() !== record[details.via][current].toString()) { return module.exports.update.call(this, { id: getValuePrimaryKey(record[details.via], model.primaryKey), values: { diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js index 32497c9870..cf2db9228e 100644 --- a/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js +++ b/packages/strapi-plugin-content-manager/admin/src/containers/ListPage/saga.js @@ -107,6 +107,7 @@ export function* dataDeleteAll({ entriesToDelete, model, source }) { yield put(deleteSeveralDataSuccess()); yield call(dataGet, { currentModel: model, source }); + strapi.notification.success('content-manager.success.record.delete'); } catch(err) { strapi.notification.error('content-manager.error.record.delete'); } diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json index af00853d4e..38d1808991 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/forms.json @@ -926,7 +926,10 @@ }, "name": "name", "type": "string", - "value": "" + "value": "", + "validations": { + "required": true + } }, { "label": { diff --git a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js index 2170bfec9b..6910be9dc8 100755 --- a/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js +++ b/packages/strapi-plugin-content-type-builder/services/ContentTypeBuilder.js @@ -178,7 +178,8 @@ module.exports = { if (params.plugin === 'upload' && relation.model || relation.collection === 'file') { params = { type: 'media', - multiple: params.collection ? true : false + multiple: params.collection ? true : false, + required: params.required }; } else { params = _.omit(params, ['collection', 'model', 'via']); @@ -288,7 +289,8 @@ module.exports = { attrs[attribute.name] = { [attribute.params.multiple ? 'collection' : 'model']: 'file', via, - plugin: 'upload' + plugin: 'upload', + required: attribute.params.required === true ? true : false }; } } else if (_.has(attribute, 'params.target')) { diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index 72ba075327..25f608040e 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -56,14 +56,14 @@ module.exports = { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.confirmed' }] }] : 'Your account email is not confirmed.'); } - if (user.blocked === true) { - return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.blocked' }] }] : 'Your account has been blocked by the administrator.'); - } - if (!user) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.invalid' }] }] : 'Identifier or password invalid.'); } + if (user.blocked === true) { + return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.blocked' }] }] : 'Your account has been blocked by the administrator.'); + } + if (user.role.type !== 'root' && ctx.request.admin) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.noAdminAccess' }] }] : `You're not an administrator.`); } @@ -285,7 +285,7 @@ module.exports = { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); } - if (user && user.provider !== params.provider && strapi.plugins['users-permissions'].config.advanced.unique_email) { + if (user && user.provider !== params.provider && settings.unique_email) { return ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: 'Auth.form.error.email.taken' }] }] : 'Email is already taken.'); }