mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Merge branch 'master' into patch-1
This commit is contained in:
commit
4b921d8778
@ -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',
|
||||
],
|
||||
|
||||
@ -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 });
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@ -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`
|
||||
|
||||
96
docs/3.0.0-beta.x/guides/external-data.md
Normal file
96
docs/3.0.0-beta.x/guides/external-data.md
Normal file
@ -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();
|
||||
},
|
||||
};
|
||||
```
|
||||
@ -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
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { LoadingIndicatorPage } from 'strapi-helper-plugin';
|
||||
|
||||
import { App } from '../../App';
|
||||
|
||||
describe('<App />', () => {
|
||||
it('should render the <AppLoader />', () => {
|
||||
const renderedComponent = shallow(<App getDataSucceeded={jest.fn()} />);
|
||||
expect(renderedComponent.find(LoadingIndicatorPage)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -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();
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 }) => <div>Your input type: <b>{type}</b> does not exist</div>;
|
||||
const DefaultInputError = ({ type }) => (
|
||||
<div>
|
||||
Your input type: <b>{type}</b> does not exist
|
||||
</div>
|
||||
);
|
||||
|
||||
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 <Input {...props} value={inputValue} />;
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -19,7 +19,7 @@ export const cleanData = (retrievedData, ctLayout, groupLayouts) => {
|
||||
|
||||
switch (attrType) {
|
||||
case 'json':
|
||||
cleanedData = value;
|
||||
cleanedData = JSON.parse(value);
|
||||
break;
|
||||
case 'date':
|
||||
cleanedData =
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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({});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user