diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 937bb43b75..8c43ac13e1 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -129,6 +129,7 @@ module.exports = {
'/3.0.0-beta.x/guides/deployment',
'/3.0.0-beta.x/guides/jwt-validation',
'/3.0.0-beta.x/guides/error-catching',
+ '/3.0.0-beta.x/guides/external-data',
'/3.0.0-beta.x/guides/slug',
'/3.0.0-beta.x/guides/webhooks',
],
diff --git a/docs/3.0.0-beta.x/concepts/controllers.md b/docs/3.0.0-beta.x/concepts/controllers.md
index b31a37552e..089838aa2a 100644
--- a/docs/3.0.0-beta.x/concepts/controllers.md
+++ b/docs/3.0.0-beta.x/concepts/controllers.md
@@ -67,7 +67,9 @@ module.exports = {
entities = await strapi.services.restaurant.find(ctx.query);
}
- return entities.map(entity => sanitizeEntity(entity, { model }));
+ return entities.map(entity =>
+ sanitizeEntity(entity, { model: strapi.models.restaurant })
+ );
},
};
```
@@ -90,7 +92,7 @@ module.exports = {
async findOne(ctx) {
const entity = await strapi.services.restaurant.findOne(ctx.params);
- return sanitizeEntity(entity, { model });
+ return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
@@ -142,7 +144,7 @@ module.exports = {
} else {
entity = await strapi.services.restaurant.create(ctx.request.body);
}
- return sanitizeEntity(entity, { model });
+ return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
@@ -177,7 +179,7 @@ module.exports = {
);
}
- return sanitizeEntity(entity, { model });
+ return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
@@ -200,7 +202,7 @@ module.exports = {
async delete(ctx) {
const entity = await strapi.services.restaurant.delete(ctx.params);
- return sanitizeEntity(entity, { model });
+ return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
```
diff --git a/docs/3.0.0-beta.x/concepts/services.md b/docs/3.0.0-beta.x/concepts/services.md
index f7a5ed6985..1775336f1e 100644
--- a/docs/3.0.0-beta.x/concepts/services.md
+++ b/docs/3.0.0-beta.x/concepts/services.md
@@ -97,7 +97,7 @@ module.exports = {
if (files) {
// automatically uploads the files based on the entry and the model
- await this.uploadFiles(entry, files, { model });
+ await this.uploadFiles(entry, files, { model: strapi.models.restaurant });
return this.findOne({ id: entry.id });
}
@@ -125,7 +125,7 @@ module.exports = {
if (files) {
// automatically uploads the files based on the entry and the model
- await this.uploadFiles(entry, files, { model });
+ await this.uploadFiles(entry, files, { model: strapi.models.restaurant });
return this.findOne({ id: entry.id });
}
diff --git a/docs/3.0.0-beta.x/content-api/parameters.md b/docs/3.0.0-beta.x/content-api/parameters.md
index 4862006e71..11d39ad0c2 100644
--- a/docs/3.0.0-beta.x/content-api/parameters.md
+++ b/docs/3.0.0-beta.x/content-api/parameters.md
@@ -89,7 +89,7 @@ Sort according to a specific field.
- ASC: `GET /users?_sort=email:ASC`
- DESC: `GET /users?_sort=email:DESC`
-#### Sorting on multiple fileds
+#### Sorting on multiple fields
- `GET /users?_sort=email:asc,dateField:desc`
- `GET /users?_sort=email:DESC,username:ASC`
diff --git a/docs/3.0.0-beta.x/guides/external-data.md b/docs/3.0.0-beta.x/guides/external-data.md
new file mode 100644
index 0000000000..e3497c6bec
--- /dev/null
+++ b/docs/3.0.0-beta.x/guides/external-data.md
@@ -0,0 +1,96 @@
+# Fetching external data
+
+This guide explains how to fetch data from an external service to use it in your app.
+
+In this example we will see how to daily fetch Docker pull count to store the result in your database.
+
+## Content Type settings
+
+First, we need to create a Content Type, in this example we will call it `hit` with a `date` and `count` attribute.
+
+Your Content Type should look like this:
+
+**Path —** `./api/hit/models/Hit.settings.json`
+
+```json
+{
+ "connection": "default",
+ "collectionName": "hits",
+ "info": {
+ "name": "hit",
+ "description": ""
+ },
+ "options": {
+ "increments": true,
+ "timestamps": true,
+ "comment": ""
+ },
+ "attributes": {
+ "count": {
+ "type": "integer"
+ },
+ "date": {
+ "type": "date"
+ }
+ }
+}
+```
+
+## Fetch the data
+
+Now we will create a function that will be usable everywhere in your strapi application.
+
+**Path —** `./config/functions/docker.js`
+
+```js
+const axios = require('axios');
+
+module.exports = async () => {
+ const { data } = await axios.get(
+ 'https://hub.docker.com/v2/repositories/strapi/strapi/'
+ );
+
+ console.log(data);
+};
+```
+
+`data` contains all the data received from the Docker Hub API. What we want here is to add the `pull_count` value in your database.
+
+## Create a `hit` entry
+
+let's programmatically create the entry.
+
+**Path —** `./config/functions/docker.js`
+
+```js
+const axios = require('axios');
+
+module.exports = async () => {
+ const { data } = await axios.get(
+ 'https://hub.docker.com/v2/repositories/strapi/strapi/'
+ );
+
+ await strapi.query('hit').create({
+ date: new Date(),
+ count: data.pull_count,
+ });
+};
+```
+
+With this code, everytime this function is called it will fetch the docker repo's data and insert the current `pull_count` with the corresponding date in your Strapi database.
+
+## Call the function
+
+Here is how to call the function in your application `strapi.config.functions.docker()`
+
+So let's execute this function everyday at 2am. For this we will use a [CRON tasks](../concepts/configurations.md#cron-tasks).
+
+**Path —** `./config/functions/cron.js`
+
+```js
+module.exports = {
+ '0 2 * * *': () => {
+ strapi.config.functions.docker();
+ },
+};
+```
diff --git a/docs/3.0.0-beta.x/plugins/users-permissions.md b/docs/3.0.0-beta.x/plugins/users-permissions.md
index 84dd43e8e3..b13322bd11 100644
--- a/docs/3.0.0-beta.x/plugins/users-permissions.md
+++ b/docs/3.0.0-beta.x/plugins/users-permissions.md
@@ -209,17 +209,42 @@ axios
.post('http://localhost:1337/auth/reset-password', {
code: 'privateCode',
password: 'myNewPassword',
- passwordConfirmation: 'myNewPassword'
+ passwordConfirmation: 'myNewPassword',
})
.then(response => {
// Handle success.
- console.log('Your user\'s password has been changed.');
+ console.log("Your user's password has been changed.");
})
.catch(error => {
// Handle error.
console.log('An error occurred:', error);
});
-});
+```
+
+### Email validation
+
+This action send an email to a user with the link to confirm the user.
+
+#### Usage
+
+- email is the user email.
+
+```js
+import axios from 'axios';
+
+// Request API.
+axios
+ .post(`http://localhost:1337/auth/send-email-confirmation`, {
+ email: 'user@strapi.io',
+ })
+ .then(response => {
+ // Handle success.
+ console.log('Your user received an email');
+ })
+ .catch(error => {
+ // Handle error.
+ console.err('An error occured:', err);
+ });
```
## User object in Strapi context
diff --git a/packages/strapi-admin/admin/src/containers/App/tests/index.test.js b/packages/strapi-admin/admin/src/containers/App/tests/index.test.js
deleted file mode 100644
index 00797dda83..0000000000
--- a/packages/strapi-admin/admin/src/containers/App/tests/index.test.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { LoadingIndicatorPage } from 'strapi-helper-plugin';
-
-import { App } from '../../App';
-
-describe('', () => {
- it('should render the ', () => {
- const renderedComponent = shallow();
- expect(renderedComponent.find(LoadingIndicatorPage)).toHaveLength(1);
- });
-});
diff --git a/packages/strapi-generate-new/lib/create-customized-project.js b/packages/strapi-generate-new/lib/create-customized-project.js
index 37a36138f2..448e04fbbd 100644
--- a/packages/strapi-generate-new/lib/create-customized-project.js
+++ b/packages/strapi-generate-new/lib/create-customized-project.js
@@ -44,7 +44,7 @@ async function askDbInfosAndTest(scope) {
dependencies: clientDependencies({ scope, client }),
};
- await testDatabaseConnection({
+ return testDatabaseConnection({
scope,
configuration,
})
@@ -67,6 +67,7 @@ async function askDbInfosAndTest(scope) {
});
}
)
+ .then(() => configuration)
.catch(err => {
if (retries < MAX_RETRIES - 1) {
console.log();
@@ -88,8 +89,6 @@ async function askDbInfosAndTest(scope) {
`️⛔️ Could not connect to your database after ${MAX_RETRIES} tries. Try to check your database configuration an retry.`
);
});
-
- return configuration;
}
return loop();
diff --git a/packages/strapi-generate-new/lib/resources/json/database.json.js b/packages/strapi-generate-new/lib/resources/json/database.json.js
index 7e77fb10ad..b7a38e7c6b 100644
--- a/packages/strapi-generate-new/lib/resources/json/database.json.js
+++ b/packages/strapi-generate-new/lib/resources/json/database.json.js
@@ -3,24 +3,16 @@
module.exports = ({ connection, env }) => {
// Production/Staging Template
if (['production', 'staging'].includes(env)) {
- // All available settings (bookshelf and mongoose)
const settingsBase = {
client: connection.settings.client,
host: "${process.env.DATABASE_HOST || '127.0.0.1'}",
port: '${process.env.DATABASE_PORT || 27017}',
- srv: '${process.env.DATABASE_SRV || false}',
database: "${process.env.DATABASE_NAME || 'strapi'}",
username: "${process.env.DATABASE_USERNAME || ''}",
password: "${process.env.DATABASE_PASSWORD || ''}",
- ssl: '${process.env.DATABASE_SSL || false}',
};
- // All available options (bookshelf and mongoose)
- const optionsBase = {
- ssl: '${process.env.DATABASE_SSL || false}',
- authenticationDatabase:
- "${process.env.DATABASE_AUTHENTICATION_DATABASE || ''}",
- };
+ const optionsBase = {};
return {
defaultConnection: 'default',
diff --git a/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js b/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js
index 6558e1e2f4..4d5224ff96 100644
--- a/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js
+++ b/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js
@@ -6,7 +6,7 @@
/* eslint-disable react/require-default-props */
import React from 'react';
import PropTypes from 'prop-types';
-import { isEmpty, isObject, merge } from 'lodash';
+import { isEmpty, merge } from 'lodash';
// Design
import InputAddonWithErrors from '../InputAddonWithErrors';
@@ -22,7 +22,11 @@ import InputTextAreaWithErrors from '../InputTextAreaWithErrors';
import InputTextWithErrors from '../InputTextWithErrors';
import InputToggleWithErrors from '../InputToggleWithErrors';
-const DefaultInputError = ({ type }) =>
Your input type: {type} does not exist
;
+const DefaultInputError = ({ type }) => (
+
+ Your input type: {type} does not exist
+
+);
const inputs = {
addon: InputAddonWithErrors,
@@ -55,14 +59,14 @@ function InputsIndex(props) {
inputValue = props.value || [];
break;
case 'json':
- inputValue = isObject(props.value) ? props.value : null;
+ inputValue = props.value || null;
break;
default:
inputValue = props.value || '';
}
merge(inputs, props.customInputs);
-
+
const Input = inputs[type] ? inputs[type] : DefaultInputError;
return ;
@@ -78,10 +82,7 @@ InputsIndex.defaultProps = {
};
InputsIndex.propTypes = {
- addon: PropTypes.oneOfType([
- PropTypes.bool,
- PropTypes.string,
- ]),
+ addon: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
customInputs: PropTypes.object,
type: PropTypes.string.isRequired,
value: PropTypes.any,
diff --git a/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/index.js b/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/index.js
index 64da0dcb49..cf58c08923 100644
--- a/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/index.js
+++ b/packages/strapi-plugin-content-manager/admin/src/components/InputJSON/index.js
@@ -15,13 +15,12 @@ import 'codemirror/addon/selection/mark-selection';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/3024-night.css';
-import { isEmpty, isObject, trimStart } from 'lodash';
+import { isEmpty, trimStart } from 'lodash';
import jsonlint from './jsonlint';
import Wrapper from './components';
const WAIT = 600;
const stringify = JSON.stringify;
-const parse = JSON.parse;
const DEFAULT_THEME = '3024-night';
class InputJSON extends React.Component {
@@ -65,15 +64,14 @@ class InputJSON extends React.Component {
setInitValue = () => {
const { value } = this.props;
- if (isObject(value) && value !== null) {
- try {
- parse(stringify(value));
- this.setState({ hasInitValue: true });
+ try {
+ this.setState({ hasInitValue: true });
- return this.codeMirror.setValue(stringify(value, null, 2));
- } catch (err) {
- return this.setState({ error: true });
- }
+ if (value === null) return this.codeMirror.setValue('');
+
+ return this.codeMirror.setValue(stringify(value, null, 2));
+ } catch (err) {
+ return this.setState({ error: true });
}
};
@@ -125,10 +123,8 @@ class InputJSON extends React.Component {
const { name, onChange } = this.props;
let value = this.codeMirror.getValue();
- try {
- value = parse(value);
- } catch (err) {
- // Silent
+ if (value === '') {
+ value = null;
}
// Update the parent
diff --git a/packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/index.js b/packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/index.js
index e022848607..1d0a2cae27 100644
--- a/packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/index.js
+++ b/packages/strapi-plugin-content-manager/admin/src/components/InputJSONWithErrors/index.js
@@ -217,11 +217,7 @@ InputJSONWithErrors.propTypes = {
resetProps: PropTypes.bool,
tabIndex: PropTypes.string,
validations: PropTypes.object,
- value: PropTypes.oneOfType([
- PropTypes.array,
- PropTypes.object,
- PropTypes.bool,
- ]),
+ value: PropTypes.any,
};
export default InputJSONWithErrors;
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/formatData.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/formatData.js
index 9ed0af6ca4..f6904897d8 100644
--- a/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/formatData.js
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/formatData.js
@@ -19,7 +19,7 @@ export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
switch (attrType) {
case 'json':
- cleanedData = value;
+ cleanedData = JSON.parse(value);
break;
case 'date':
cleanedData =
diff --git a/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/schema.js b/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/schema.js
index 598298c225..aab240b62e 100644
--- a/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/schema.js
+++ b/packages/strapi-plugin-content-manager/admin/src/containers/EditView/utils/schema.js
@@ -1,12 +1,4 @@
-import {
- get,
- isBoolean,
- isNaN,
- isNumber,
- isNull,
- isArray,
- isObject,
-} from 'lodash';
+import { get, isBoolean, isNaN } from 'lodash';
import * as yup from 'yup';
import { translatedErrors as errorsTrads } from 'strapi-helper-plugin';
@@ -99,20 +91,13 @@ const createYupSchemaAttribute = (type, validations) => {
schema = yup
.mixed(errorsTrads.json)
.test('isJSON', errorsTrads.json, value => {
- try {
- if (
- isObject(value) ||
- isBoolean(value) ||
- isNumber(value) ||
- isArray(value) ||
- isNaN(value) ||
- isNull(value)
- ) {
- JSON.parse(JSON.stringify(value));
- return true;
- }
+ if (value === undefined) {
+ return true;
+ }
- return false;
+ try {
+ JSON.parse(value);
+ return true;
} catch (err) {
return false;
}
diff --git a/packages/strapi-plugin-graphql/services/Aggregator.js b/packages/strapi-plugin-graphql/services/Aggregator.js
index 6fffd05d0b..09a9139fa3 100644
--- a/packages/strapi-plugin-graphql/services/Aggregator.js
+++ b/packages/strapi-plugin-graphql/services/Aggregator.js
@@ -358,12 +358,12 @@ const formatConnectionAggregator = function(fields, model, modelName) {
if (opts._q) {
// allow search param
- return strapi.query(modelName).countSearch(opts);
+ return strapi.query(modelName, model.plugin).countSearch(opts);
}
- return strapi.query(modelName).count(opts);
+ return strapi.query(modelName, model.plugin).count(opts);
},
totalCount(obj, options, context) {
- return strapi.query(modelName).count({});
+ return strapi.query(modelName, model.plugin).count({});
},
},
};
diff --git a/packages/strapi-plugin-users-permissions/config/routes.json b/packages/strapi-plugin-users-permissions/config/routes.json
index 77204bb0f2..94b5c53a63 100644
--- a/packages/strapi-plugin-users-permissions/config/routes.json
+++ b/packages/strapi-plugin-users-permissions/config/routes.json
@@ -285,6 +285,20 @@
}
}
},
+ {
+ "method": "POST",
+ "path": "/auth/send-email-confirmation",
+ "handler": "Auth.sendEmailConfirmation",
+ "config": {
+ "policies": [],
+ "prefix": "",
+ "description": "Send a confirmation email to user",
+ "tag": {
+ "plugin": "users-permissions",
+ "name": "User"
+ }
+ }
+ },
{
"method": "GET",
"path": "/users",
diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js
index c3e56e5b55..655b3b9230 100644
--- a/packages/strapi-plugin-users-permissions/controllers/Auth.js
+++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js
@@ -597,4 +597,80 @@ module.exports = {
ctx.redirect(settings.email_confirmation_redirection || '/');
},
+
+ async sendEmailConfirmation(ctx) {
+ const pluginStore = await strapi.store({
+ environment: '',
+ type: 'plugin',
+ name: 'users-permissions',
+ });
+
+ const params = _.assign(ctx.request.body);
+
+ if (!params.email) {
+ return ctx.badRequest('missing.email');
+ }
+
+ const isEmail = emailRegExp.test(params.email);
+
+ if (isEmail) {
+ params.email = params.email.toLowerCase();
+ } else {
+ return ctx.badRequest('wrong.email');
+ }
+
+ const user = await strapi.query('user', 'users-permissions').findOne({
+ email: params.email
+ });
+
+ if (user.confirmed) {
+ return ctx.badRequest('already.confirmed');
+ }
+
+ if (user.blocked) {
+ return ctx.badRequest('blocked.user');
+ }
+
+ const jwt = strapi.plugins['users-permissions'].services.jwt.issue(
+ _.pick(user.toJSON ? user.toJSON() : user, ['id'])
+ );
+
+ const settings = await pluginStore.get({ key: 'email' }).then(storeEmail => {
+ try {
+ return storeEmail['email_confirmation'].options;
+ } catch (err) {
+ return {};
+ }
+ });
+
+ settings.message = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.message, {
+ URL: new URL('/auth/email-confirmation', strapi.config.url).toString(),
+ USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider']),
+ CODE: jwt
+ });
+
+ settings.object = await strapi.plugins['users-permissions'].services.userspermissions.template(settings.object, {
+ USER: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken', 'role', 'provider']),
+ });
+
+ try {
+ await strapi.plugins['email'].services.email.send({
+ to: (user.toJSON ? user.toJSON() : user).email,
+ from:
+ settings.from.email && settings.from.name
+ ? `"${settings.from.name}" <${settings.from.email}>`
+ : undefined,
+ replyTo: settings.response_email,
+ subject: settings.object,
+ text: settings.message,
+ html: settings.message
+ });
+ ctx.send({
+ email: (user.toJSON ? user.toJSON() : user).email,
+ sent: true
+ });
+ } catch (err) {
+ return ctx.badRequest(null, err);
+ }
+ },
};
diff --git a/packages/strapi-plugin-users-permissions/documentation/1.0.0/overrides/users-permissions-User.json b/packages/strapi-plugin-users-permissions/documentation/1.0.0/overrides/users-permissions-User.json
index c6d0821c09..3b55f96ae0 100644
--- a/packages/strapi-plugin-users-permissions/documentation/1.0.0/overrides/users-permissions-User.json
+++ b/packages/strapi-plugin-users-permissions/documentation/1.0.0/overrides/users-permissions-User.json
@@ -53,6 +53,47 @@
"security": []
}
},
+ "/auth/send-email-confirmation": {
+ "post": {
+ "security": [],
+ "externalDocs": {
+ "description": "Find out more in the strapi's documentation",
+ "url": "https://strapi.io/documentation/guides/authentication.html#usage"
+ },
+ "responses": {
+ "200": {
+ "description": "Successfully sent email",
+ "content": {
+ "application/json": {
+ "email": {
+ "type": "string"
+ },
+ "sent": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ },
+ "requestBody": {
+ "description": "",
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "required": ["email"],
+ "properties": {
+ "email": {
+ "type": "string",
+ "minLength": 6
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/users-permissions/search/{id}": {
"get": {
"summary": "Retrieve a list of users by searching for their username or email",
diff --git a/packages/strapi-plugin-users-permissions/services/UsersPermissions.js b/packages/strapi-plugin-users-permissions/services/UsersPermissions.js
index e630315fa4..52b2c17efa 100644
--- a/packages/strapi-plugin-users-permissions/services/UsersPermissions.js
+++ b/packages/strapi-plugin-users-permissions/services/UsersPermissions.js
@@ -389,7 +389,9 @@ module.exports = {
};
// Retrieve roles
- const roles = await strapi.query('role', 'users-permissions').find();
+ const roles = await strapi
+ .query('role', 'users-permissions')
+ .find({}, []);
// We have to know the difference to add or remove
// the permissions entries in the database.