Merge branch 'master' into fix/upload-url-relative

This commit is contained in:
Jim LAURIE 2020-01-02 14:28:11 +01:00 committed by GitHub
commit 1ac92c28d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 592 additions and 125 deletions

View File

@ -197,6 +197,7 @@ module.exports = {
'/3.0.0-beta.x/guides/process-manager',
'/3.0.0-beta.x/guides/jwt-validation',
'/3.0.0-beta.x/guides/api-token',
'/3.0.0-beta.x/guides/auth-request',
'/3.0.0-beta.x/guides/error-catching',
'/3.0.0-beta.x/guides/secure-your-app',
'/3.0.0-beta.x/guides/external-data',

View File

@ -52,10 +52,10 @@ As an **example** let's consider the following models:
### `Image With Description` Component
| Fields | Type | Description |
| :---------- | :----- | :------------------- |
| image | media | The image file |
| title | string | the image title |
| Fields | Type | Description |
| :---------- | :----- | :-------------------- |
| image | media | The image file |
| title | string | the image title |
| description | text | the image description |
:::

View File

@ -0,0 +1,172 @@
# Authenticated request
In this guide you will see how you can request the API as an authenticated user.
## Introduction
To show you many of the concepts on the [roles and permissions](../plugins/users-permissions.md#manage-roles-permissions) part, we will use many users and roles.
After that we will see the [authentication workflow](../plugins/users-permissions.md#authentication) to get a `JWT` and use it for an API request.
We will have one group of users that will be able to only fetch **Articles** and an other group that will be able to fetch, create and update **Articles**.
## Setup
### Create Content Type
Lets create a new Content Type **Article**
- Click on `Content Type Builder` in the left menu
- Then `+ Create new content-type`
- Fill `Display name` with `article`
- Create 2 fields
- **Text** short named `title`
- **Rich text** named `content`
- And save this new Content Type
Then create some **Articles** from the Content Manager.
### Create Roles & Permissions
We will create 2 new groups to manage available actions for different kind of users.
- Click on `Roles & Permissions` in the left menu
- Then `+ Add New Role`
- Fill `name` with `Author`
- Check `Select All` for the Application Article Content Type.
- And save the new role
And repeat the opperation for the `Reader` group and check `find`, `findOne` and `count`.
### Create users
Finally create **2 users** with the following data.
**User 1**
- **username**: author
- **email**: author@strapi.io
- **password**: strapi
- **role**: Author
**User 2**
- **username**: reader
- **email**: reader@strapi.io
- **password**: strapi
- **role**: Reader
## Login as a Reader
To login as a user your will have to follow the [login documentation](../plugins/users-permissions.md#login).
Here is the API route for the authentication `/auth/local`.
You have to request it in **POST**.
:::: tabs
::: tab axios
```js
import axios from 'axios';
const { data } = await axios.post('http://localhost:1337/auth/local', {
identifier: 'reader@strapi.io',
password: 'strapi',
});
console.log(data);
```
:::
::: tab Postman
If you are using **Postman** for example you will have to set the `body` as `raw` with the `JSON (application/json)` type.
```json
{
"identifier": "reader@strapi.io",
"password": "strapi"
}
```
:::
::::
The API response contains the **user's JWT** in the `jwt` key.
```json
{
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU",
"user": {
"id": 1,
"username": "reader",
...
}
}
```
You will have to store this `JWT` in your application, it's important because you will have to use it the next requests.
### Fetch articles
Let's fetch Articles you created.
To do so, you will have to fetch `/articles` route in **GET**.
```js
import axios from 'axios';
const { data } = await axios.get('http://localhost:1337/articles');
console.log(data);
```
Here you should receive a **403 error** because you are not allowed, as Public user, to access to the **articles**.
You should use the `JWT` in the request to say that you can access to this data as **Reader user**.
```js
import axios from 'axios';
const { data } = await axios.get('http://localhost:1337/articles', {
headers: {
Authorization:
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU',
},
});
console.log(data);
```
And tada you have access to the data.
### Create an Article
To do so, you will have to request the `/articles` route in **POST**.
```js
import axios from 'axios';
const {data} = await axios
.get('http://localhost:1337/articles', {
data: {
title: 'my article'
content: 'my super article content'
},
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTc2OTM4MTUwLCJleHAiOjE1Nzk1MzAxNTB9.UgsjjXkAZ-anD257BF7y1hbjuY3ogNceKfTAQtzDEsU'
}
});
console.log(data);
```
If you request this as a **Reader user**, you will receive a **403 error**. It's because the **Reader role** does not have access to the create function of the **Article** Content Type.
To fix that you will have to login with the **Author user** and use its `JWT` into the request to create an **Article**.
With that done, you will be able to create an **Article**.

View File

@ -491,20 +491,22 @@ sudo nano ecosystem.config.js
```js
module.exports = {
apps : [{
name: 'your-app-name',
cwd: '/home/ubuntu/my-strapi-project/my-project',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'production',
DATABASE_HOST: 'your-unique-url.rds.amazonaws.com', // database Endpoint under 'Connectivity & Security' tab
DATABASE_PORT: '5432',
DATABASE_NAME: 'strapi', // DB name under 'Configuration' tab
DATABASE_USERNAME: 'postgres', // default username
DATABASE_PASSWORD: 'Password',
apps: [
{
name: 'your-app-name',
cwd: '/home/ubuntu/my-strapi-project/my-project',
script: 'npm',
args: 'start',
env: {
NODE_ENV: 'production',
DATABASE_HOST: 'your-unique-url.rds.amazonaws.com', // database Endpoint under 'Connectivity & Security' tab
DATABASE_PORT: '5432',
DATABASE_NAME: 'strapi', // DB name under 'Configuration' tab
DATABASE_USERNAME: 'postgres', // default username
DATABASE_PASSWORD: 'Password',
},
},
}],
],
};
```

View File

@ -19,6 +19,10 @@ Starting from beta.18 the database packages have been changed to allow future ch
Update your package.json accordingly:
:::: tabs
::: tab bookshelf
**Before**
```json
@ -46,20 +50,68 @@ Update your package.json accordingly:
{
//...
"dependencies": {
"strapi": "3.0.0-beta.18",
"strapi-admin": "3.0.0-beta.18",
"strapi-connector-bookshelf": "3.0.0-beta.18",
"strapi-plugin-content-manager": "3.0.0-beta.18",
"strapi-plugin-content-type-builder": "3.0.0-beta.18",
"strapi-plugin-email": "3.0.0-beta.18",
"strapi-plugin-graphql": "3.0.0-beta.18",
"strapi-plugin-upload": "3.0.0-beta.18",
"strapi-plugin-users-permissions": "3.0.0-beta.18",
"strapi-utils": "3.0.0-beta.18"
"strapi": "3.0.0-beta.18.3",
"strapi-admin": "3.0.0-beta.18.3",
"strapi-connector-bookshelf": "3.0.0-beta.18.3",
"strapi-plugin-content-manager": "3.0.0-beta.18.3",
"strapi-plugin-content-type-builder": "3.0.0-beta.18.3",
"strapi-plugin-email": "3.0.0-beta.18.3",
"strapi-plugin-graphql": "3.0.0-beta.18.3",
"strapi-plugin-upload": "3.0.0-beta.18.3",
"strapi-plugin-users-permissions": "3.0.0-beta.18.3",
"strapi-utils": "3.0.0-beta.18.3"
}
}
```
:::
::: tab mongoose
**Before**
```json
{
//...
"dependencies": {
"strapi": "3.0.0-beta.17.4",
"strapi-admin": "3.0.0-beta.17.4",
"strapi-hook-mongoose": "3.0.0-beta.17.4", // rename to strapi-connector-mongoose
"strapi-plugin-content-manager": "3.0.0-beta.17.4",
"strapi-plugin-content-type-builder": "3.0.0-beta.17.4",
"strapi-plugin-email": "3.0.0-beta.17.4",
"strapi-plugin-graphql": "3.0.0-beta.17.4",
"strapi-plugin-upload": "3.0.0-beta.17.4",
"strapi-plugin-users-permissions": "3.0.0-beta.17.4",
"strapi-utils": "3.0.0-beta.17.4"
}
}
```
**After**
```json
{
//...
"dependencies": {
"strapi": "3.0.0-beta.18.3",
"strapi-admin": "3.0.0-beta.18.3",
"strapi-connector-mongoose": "3.0.0-beta.18.3",
"strapi-plugin-content-manager": "3.0.0-beta.18.3",
"strapi-plugin-content-type-builder": "3.0.0-beta.18.3",
"strapi-plugin-email": "3.0.0-beta.18.3",
"strapi-plugin-graphql": "3.0.0-beta.18.3",
"strapi-plugin-upload": "3.0.0-beta.18.3",
"strapi-plugin-users-permissions": "3.0.0-beta.18.3",
"strapi-utils": "3.0.0-beta.18.3"
}
}
```
:::
::::
Then run either `yarn install` or `npm install`.
## Database configuration
@ -68,6 +120,10 @@ Now that you have installed the new database package. You need to update your `d
You can now only use the connector name instead of the complete package name.
:::: tabs
::: tab bookshelf
**Before**
```json
@ -104,6 +160,91 @@ You can now only use the connector name instead of the complete package name.
}
```
:::
::: tab mongoose
**Before**
```json
{
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-mongoose",
"settings": {
//...
},
"options": {}
}
}
}
```
**After**
```json
{
"defaultConnection": "default",
"connections": {
"default": {
"connector": "mongoose",
"settings": {
//...
},
"options": {
//...
}
}
}
}
```
:::
::::
## Adding new root page files
We created new home pages when your go to your api url.
You will need to copy `index.html` and `production.html` into your `public` folder.
You can find those two files [here](https://github.com/strapi/strapi/tree/master/packages/strapi-generate-new/lib/resources/files/public).
## Updating `csp` options
The admin panel contains certain assets that use `data:img;base64` images. To allow rendering of those assets you can update the files `./config/environments/{env}/security.json` as follows:
**Before**
```json
{
"csp": {
"enabled": true,
"policy": [
{
"img-src": "'self' http:"
},
"block-all-mixed-content"
]
}
//....
}
```
**After**
```json
{
"csp": {
"enabled": true,
"policy": ["block-all-mixed-content"]
}
//....
}
```
If you need more fine control you can also simply add the `data:` option to the `img-src` option.
## `ctx.state.user`
Previously the ctx.state.user was populated with the user informations, its role and permissions. To avoid perfromance issues the role is the only populated relation on the user by default.
@ -112,11 +253,11 @@ Previously the ctx.state.user was populated with the user informations, its role
The file model has been updated. The `size` field is now a decimal number, allowing correct sorting behavior.
You will need to clear some database indexes if you are using either Mysql or PostgreSQL.
You will need to clear some database indexes if you are using either MySQL or PostgreSQL.
:::: tabs
::: tab Mysql
::: tab MySQL
Run the following statement in your database:
@ -298,7 +439,7 @@ RENAME COLUMN group_id to component_id;
:::
::: tab Mysql
::: tab MySQL
```sql
-- renaming the table
@ -342,7 +483,7 @@ RENAME TO components_new_table_name;
```
:::
::: tab Mysql
::: tab MySQL
```sql
-- renaming the table
@ -355,7 +496,7 @@ RENAME TABLE groups_old_table_name TO components_new_table_name;
**2. Change the `collectionName` of the component**
**Before**
`./api/components/category/component.json`
`./components/component.json`
```json
{
@ -365,7 +506,7 @@ RENAME TABLE groups_old_table_name TO components_new_table_name;
```
**After**
`./api/components/category/component.json`
`./components/component.json`
```json
{
@ -392,7 +533,6 @@ SET related_type = 'components_new_table_name'
WHERE related_type = 'groups_old_table_name';
```
#### Mongo
In `mongo` the relation between a content type and its components is held in an array of references. To know which component type it referes to, the array also contains a `kind` attribute containing the component Schema name.
@ -425,7 +565,7 @@ db.collection.renameCollection('groups_my_group', 'components_my_component');
**3. Change the `collectionName` of the component**
**Before**
`./api/components/category/component.json`
`./components/component.json`
```json
{
@ -435,7 +575,7 @@ db.collection.renameCollection('groups_my_group', 'components_my_component');
```
**After**
`./api/components/category/component.json`
`./components/component.json`
```json
{
@ -463,47 +603,6 @@ db.getCollection('contentTypeCollection').update(
);
```
## Adding new root page files
We created new home pages when your go to your api url.
You will need to copy `index.html` and `production.html` into your `public` folder.
You can find those two files [here](https://github.com/strapi/strapi/tree/master/packages/strapi-generate-new/lib/resources/files/public).
## Updating `csp` options
The admin panel contains certain assets that use `data:img;base64` images. To allow rendering of those assets you can update the files `./config/environments/{env}/security.json` as follows:
**Before**
```json
{
"csp": {
"enabled": true,
"policy": [
{
"img-src": "'self' http:"
},
"block-all-mixed-content"
]
}
//....
}
```
**After**
```json
{
"csp": {
"enabled": true,
"policy": ["block-all-mixed-content"]
}
//....
}
```
If you need more fine control you can also simply add the `data:` option to the `img-src` option.
## Rebuilding your administration panel
Now delete the `.cache` and `build` folders. Then run `yarn develop`.

View File

@ -111,3 +111,42 @@ If you want to create your own provider without publishing it on **npm** you can
```
- Finally, run `yarn install` or `npm install` to install your new custom provider.
## Trouble shooting
You received an `Auth.form.error.email.invalid` error even though the email is valid and exists in the database.
Here is the error response you get from the API.
```json
{
"statusCode": 400,
"error": "Bad Request",
"message": [
{
"messages": [
{
"id": "Auth.form.error.email.invalid"
}
]
}
]
}
```
This error is due to your IP connection. By default, Strapi uses the [`sendmail`](https://github.com/guileen/node-sendmail) package.
This package sends an email from the server it runs on. Depending on the network you are on, the connection to the SMTP server could fail.
Here is the `sendmail` error.
```
Error: SMTP code:550 msg:550-5.7.1 [87.88.179.13] The IP you're using to send mail is not authorized to
550-5.7.1 send email directly to our servers. Please use the SMTP relay at your
550-5.7.1 service provider instead. Learn more at
550 5.7.1 https://support.google.com/mail/?p=NotAuthorizedError 30si2132728pjz.75 - gsmtp
```
To fix it, I suggest you to use another email provider that uses third party to send emails.
When using a third party provider, you avoid having to setup a mail server on your server and get extra features such as email analytics.

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
const Wrapper = styled.div`
padding-top: 0.7rem;
position: absolute;
top: 60px;
top: 6rem;
right: 0;
bottom: 0;
left: 0;

View File

@ -137,7 +137,7 @@
"Auth.advanced.allow_register": "",
"Auth.form.button.forgot-password": "发送电子邮件",
"Auth.form.button.forgot-password.success": "再次发送",
"Auth.form.button.login": "登",
"Auth.form.button.login": "登",
"Auth.form.button.register": "准备开始",
"Auth.form.button.register-success": "再次发送",
"Auth.form.button.reset-password": "修改密码",
@ -176,6 +176,6 @@
"Auth.form.register.username.placeholder": "John Doe",
"Auth.header.register.description": "要完成安装并保护您的应用程序请通过输入必要的信息来创建第一个用户root管理员。",
"Auth.link.forgot-password": "忘记密码了吗?",
"Auth.link.ready": "准备好登",
"Auth.link.ready": "准备好登",
"components.Input.error.password.noMatch": "密码不匹配"
}

View File

@ -270,11 +270,16 @@ module.exports = {
: null
);
const reverseAssoc = model.associations.find(assoc => assoc.alias === obj.field);
const reverseAssoc = model.associations.find(
assoc => assoc.alias === obj.field
);
// Remove existing relationship because only one file
// can be related to this field.
if (reverseAssoc && reverseAssoc.nature === 'oneToManyMorph') {
if (
reverseAssoc &&
reverseAssoc.nature === 'oneToManyMorph'
) {
relationUpdates.push(
module.exports.removeRelationMorph
.call(

View File

@ -14,7 +14,7 @@ const DragWrapper = styled.div`
}
> div > div {
overflow-x: auto;
overflow-y: hidden;
overflow-y: scroll;
}
`;

View File

@ -343,6 +343,7 @@ const forms = {
},
],
[fields.divider],
[fields.private],
[fields.required],
[fields.unique],
];

View File

@ -82,6 +82,18 @@ const fields = {
},
validations: {},
},
private: {
autoFocus: false,
name: 'private',
type: 'checkbox',
label: {
id: getTrad('form.attribute.item.privateField'),
},
description: {
id: getTrad('form.attribute.item.privateField.description'),
},
validations: {},
},
unique: {
autoFocus: false,
name: 'unique',

View File

@ -77,6 +77,8 @@
"form.attribute.item.requiredField": "Required field",
"form.attribute.item.requiredField.description": "You won't be able to create an entry if this field is empty",
"form.attribute.item.settings.name": "Settings",
"form.attribute.item.privateField": "Private field",
"form.attribute.item.privateField.description": "This field will not show up in the API response",
"form.attribute.item.uniqueField": "Unique field",
"form.attribute.item.uniqueField.description": "You won't be able to create an entry if there is an existing entry with identical content",
"form.attribute.media.option.multiple": "Multiple media",

View File

@ -1,51 +1,151 @@
{
"attribute.boolean": "Boolean",
"attribute.boolean.description": "Да или нет, 1 или 0, Истина или Ложь",
"attribute.component": "Component",
"attribute.component.description": "Компонент - группа полей, доступных для повторения или повторного использования",
"attribute.date": "Date",
"attribute.date.description": "Элемент управления датой и временем",
"attribute.datetime": "Datetime",
"attribute.dynamiczone": "Dynamic zone",
"attribute.dynamiczone.description": "Компоненты с динамическим редактированием",
"attribute.email": "Email",
"attribute.email.description": "Поле электронной почты с проверкой формата",
"attribute.enumeration": "Enumeration",
"attribute.enumeration.description": "Перечень значений, выбирается одно",
"attribute.json": "JSON",
"attribute.json.description": "Данные в формате JSON",
"attribute.media": "Media",
"attribute.media.description": "Аудио- видео- и прочие медиафайлы",
"attribute.null": " ",
"attribute.number": "Number",
"attribute.number.description": "Числа (integer, float, decimal)",
"attribute.password": "Password",
"attribute.relation": "Связь",
"attribute.password.description": "Поле пароля с шифрованием",
"attribute.relation": "Relation",
"attribute.relation.description": "Ссылка на какой-либо тип контента",
"attribute.richtext": "Rich text",
"attribute.richtext.description": "Элемент управления для редактирования текста с форматированием",
"attribute.text": "Text",
"attribute.text.description": "Простой текст для заголовка или описания",
"attribute.time": "Time",
"attribute.uid": "Uuid",
"attribute.uid.description": "Уникальный идентификатор",
"button.attributes.add.another": "Ещё поле",
"button.component.add": "Добавить компонент",
"button.component.create": "Создать компонент",
"button.model.create": "Создать новый тип контента",
"components.componentSelect.no-component-available": "Вы уже добавили все имеющиеся группы полей",
"components.componentSelect.no-component-available.with-search": "Подходящих групп полей не найдено",
"components.componentSelect.value-component": "Выбрано компонентов - {number} (наберите для поиска)",
"components.componentSelect.value-components": "Компонентов выбрано - {number}",
"component.repeatable": "(повторяется)",
"configurations": "конфигурации",
"contentType.UID.description": "Идентификатор, используемый для генерации маршрутов и таблиц в API",
"contentType.collectionName.description": "Полезно, когда имя типа контента и таблицы различаются",
"contentType.collectionName.label": "Имя таблицы/коллекции",
"contentType.displayName.label": "Отображаемое имя",
"error.contentTypeName.reserved-name": "Это название зарезервировано и не может быть использовано в проекте",
"error.validation.minSupMax": "Не может выходить за ограничения",
"form.attribute.component.option.add": "Добавление компонента",
"form.attribute.component.option.create": "Создание нового компонента",
"form.attribute.component.option.create.description": "Компонент предоставляется в разных типах и группах и будет доступен отовсюду",
"form.attribute.component.option.repeatable": "Повторяющийся компонент",
"form.attribute.component.option.repeatable.description": "Применимо для множественных вхождений (массивов) ингредиентов, мета-тегов и т.д.",
"form.attribute.component.option.reuse-existing": "Использовать существующий компонент",
"form.attribute.component.option.reuse-existing.description": "Использовать повторно созданный ранее компонент, чтобы обеспечить согласованность данных в разных типах контента.",
"form.attribute.component.option.single": "Одиночный компонент",
"form.attribute.component.option.single.description": "Применимо для группировки полей, таких как полный адрес, основная информация и т.д.",
"form.attribute.item.customColumnName": "Названия столбцов",
"form.attribute.item.customColumnName.description": "Может быть полезно переименовать названия столбцов для более читаемых ответов API.",
"form.attribute.item.date.type.date": "дата",
"form.attribute.item.date.type.datetime": "дата/время",
"form.attribute.item.date.type.time": "время",
"form.attribute.item.defineRelation.fieldName": "Название поля",
"form.attribute.item.enumeration.graphql": "Название поля в GraphQL",
"form.attribute.item.enumeration.graphql.description": "Позволяет переопределить название поля в GraphQL, сгенерированное по умолчанию",
"form.attribute.item.enumeration.placeholder": "Например:\nmorning\nnoon\nevening",
"form.attribute.item.enumeration.rules": "Values (one line per value)",
"form.attribute.item.enumeration.placeholder": "Например:\nутро\nполдень\nвечер",
"form.attribute.item.enumeration.rules": "Значения (одна линия на значение)",
"form.attribute.item.maximum": "Максимальное значение",
"form.attribute.item.maximumLength": "Максимальная длина",
"form.attribute.item.minimum": "Минимальное значение",
"form.attribute.item.minimumLength": "Минимальная длина",
"form.attribute.item.number.type": "Числовой формат",
"form.attribute.item.number.type.decimal": "decimal (ex: 2.22)",
"form.attribute.item.number.type.float": "float (ex: 3.33333333)",
"form.attribute.item.number.type.integer": "integer (ex: 10)",
"form.attribute.item.number.type.biginteger": "Большое целое (ex: 123456789)",
"form.attribute.item.number.type.decimal": "Десятичное (ex: 2.22)",
"form.attribute.item.number.type.float": "С плавающей точкой (ex: 3.33333333)",
"form.attribute.item.number.type.integer": "Целое (ex: 10)",
"form.attribute.item.requiredField": "Обязательное поле",
"form.attribute.item.requiredField.description": "Вы не сможете создать запись, если это поле не заполнено",
"form.attribute.item.settings.name": "Настройки",
"form.attribute.item.uniqueField": "Уникальное поле",
"form.attribute.item.uniqueField.description": "Вы не сможете создать запись, если уже существует запись с таким значением",
"form.attribute.media.option.multiple": "Множественные медиа",
"form.attribute.media.option.multiple.description": "Применимо для слайдеров и каруселей",
"form.attribute.media.option.single": "Одиночное медиа",
"form.attribute.media.option.single.description": "Применимо для аватаров, картинок профиля и пр.",
"form.attribute.settings.default": "Стандартное значение",
"form.attribute.text.option.long-text": "Большой текст",
"form.attribute.text.option.long-text.description": "Применимо для описания, биографии... (не учествует в поиске)",
"form.attribute.text.option.short-text": "Короткий текст",
"form.attribute.text.option.short-text.description": "Применимо для названий, заголовков, ссылок... (участвует в поиске)",
"form.button.add-components-to-dynamiczone": "Добавить компоненты в зону",
"form.button.add-field": "Еще поле",
"form.button.add-first-field-to-created-component": "Добавить первое поле в компонент",
"form.button.add.field.to.component": "Добавить еще поле в компонент",
"form.button.add.field.to.contentType": "Добавить еще поле в тип контента",
"form.button.cancel": "Отменить",
"form.button.configure-component": "настроить компонент",
"form.button.configure-view": "Настроить отображение",
"form.button.continue": "Продолжить",
"form.button.delete": "Удалить",
"form.button.finish": "Завершить",
"form.button.save": "Сохранить",
"from": "from",
"menu.section.models.name.plural": "Типы Контента",
"menu.section.models.name.singular": "Тип Контента",
"form.button.select-component": "Выбрать компонент",
"from": "из",
"injected-components.content-manager.edit-settings-view.link.content-types": "Редактирование типа контента",
"injected-components.content-manager.edit-settings-view.link.components": "Редактирование компонента",
"menu.section.components.name.plural": "Компоненты",
"menu.section.components.name.singular": "Компонент",
"menu.section.models.name.plural": "Типы контента",
"menu.section.models.name.singular": "Тип контента",
"modalForm.attribute.form.base.name": "Имя атрибута",
"modalForm.attribute.form.base.name.description": "Пробелы в имени атрибута недопустимы",
"modalForm.attribute.text.type-selection": "Тип",
"modalForm.attributes.select-component": "Выбор компонента",
"modalForm.attributes.select-components": "Выбор компонентов",
"modalForm.component.header-create": "Создание компонента",
"modalForm.components.create-component.category.label": "Выберите категорию или введите имя новой",
"modalForm.components.icon.label": "Иконка",
"modalForm.contentType.header-create": "Создание типа контента",
"modalForm.editCategory.base.name.description": "Пробелы в имени категории недопустимы",
"modalForm.header-edit": "Редактирование {name}",
"modalForm.header.categories": "Категории",
"modalForm.sub-header.addComponentToDynamicZone": "Добавить компонент в динамическую зону",
"modalForm.sub-header.attribute.create": "Добавить новое поле типа {type}",
"modalForm.sub-header.attribute.create.step": "Добавить новый компонент ({step}/2)",
"modalForm.sub-header.attribute.edit": "Изменение {name}",
"modalForm.sub-header.chooseAttribute.component": "Выбрать имя поля компонента",
"modalForm.sub-header.chooseAttribute.contentType": "Выбрать имя поля типа контента",
"modelPage.attribute.relationWith": "Связь с",
"modelPage.contentHeader.emptyDescription.description": "Для этого Типа Контента нет описания",
"modelPage.contentHeader.emptyDescription.description": "Для этого типа контента нет описания",
"notification.info.creating.notSaved": "Пожалуйста, сохраните изменения перед созданием нового компонента типа контента ",
"plugin.description.long": "Моделируйте структуру данных вашего API. Создавайте новые поля и связи всего за минуту. Файлы в вашем проекте создаются и обновляются автоматически.",
"plugin.description.short": "Моделируйте структуру данных вашего API.",
"popUpForm.navContainer.advanced": "Расширенные настройки",
"popUpForm.navContainer.base": "Базовые настройки",
"popUpWarning.bodyMessage.contentType.delete": "Вы уверены, что хотите удалить этот Тип Контента?",
"popUpWarning.bodyMessage.cancel-modifications": "Вы уверены, что хотите отменить изменения?",
"popUpWarning.bodyMessage.cancel-modifications.with-components": "Вы уверены, что хотите отменить сделанные изменения? Некоторые компоненты были созданы или изменены...",
"popUpWarning.bodyMessage.category.delete": "Вы уверены, что хотите удалить категорию? Все входящие в нее компоненты будут также удалены.",
"popUpWarning.bodyMessage.component.delete": "Вы уверены, что хотите удалить этот компонент?",
"popUpWarning.bodyMessage.contentType.delete": "Вы уверены, что хотите удалить этот тип контента?",
"relation.attributeName.placeholder": "Пример: автор, категория, тег",
"relation.manyToMany": "имеет и принадлежит многим",
"relation.manyToOne": "имеет много",
"relation.manyWay": "имеет много",
"relation.oneToMany": "принадлежит многим",
"relation.oneToOne": "имеет один",
"relation.oneWay": "один принадлежит"
"relation.oneWay": "один принадлежит",
"table.attributes.title.plural": "Полей - {number}",
"table.attributes.title.singular": "Поле - {number}",
"prompt.unsaved": "Вы уверены, что хотите выйти? Все изменения будут потеряны."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "تم تحميل ملفك",
"notification.dropFiles.success": "{number} ملفات تم تحميلها",
"Upload.status.sizeLimit": "{file} أكبر من حجم الحد الذي تمت تهيئته",
"Upload.status.disabled" : "تم تعطيل تحميل الملف"
"Upload.status.disabled" : "تم تعطيل تحميل الملف",
"plugin.description.long": "إدارة ملفات الوسائط المتعددة.",
"plugin.description.short": "إدارة ملفات الوسائط المتعددة."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Deine Datei wurde hochgeladen",
"notification.dropFiles.success": "{number} Dateien wurden hochgeladen",
"Upload.status.sizeLimit": "{file} ist größer als die konfigurierte Begrenzungsgröße",
"Upload.status.disabled" : "Das Hochladen von Dateien ist deaktiviert"
"Upload.status.disabled" : "Das Hochladen von Dateien ist deaktiviert",
"plugin.description.long": "Multimedia-Dateiverwaltung.",
"plugin.description.short": "Multimedia-Dateiverwaltung."
}

View File

@ -27,5 +27,7 @@
"notification.config.success": "The settings has been updated",
"notification.delete.success": "The file has been deleted",
"notification.dropFile.success": "Your file has been uploaded",
"notification.dropFiles.success": "{number} files have been uploaded"
"notification.dropFiles.success": "{number} files have been uploaded",
"plugin.description.long": "Media file management.",
"plugin.description.short": "Media file management."
}

View File

@ -27,5 +27,7 @@
"notification.dropFiles.success": "{number} archivos han sido cargados",
"Upload.status.sizeLimit": "{file} es más grande que el tamaño límite configurado",
"Upload.status.disabled" : "La carga de archivos está deshabilitada",
"Upload.status.empty": "Los archivos están vacíos"
"Upload.status.empty": "Los archivos están vacíos",
"plugin.description.long": "Gestión de archivos multimedia.",
"plugin.description.short": "Gestión de archivos multimedia."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Votre fichier a été téléchargé",
"notification.dropFiles.success": "{number} fichiers ont été téléchargées",
"Upload.status.sizeLimit": "{file} est plus grand que la taille limite configurée",
"Upload.status.disabled" : "Le téléchargement de fichier est désactivé"
"Upload.status.disabled" : "Le téléchargement de fichier est désactivé",
"plugin.description.long": "Gestion de fichiers multimédia.",
"plugin.description.short": "Gestion de fichiers multimédia."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Il file è stato caricato",
"notification.dropFiles.success": "{number} file sono stati caricati",
"Upload.status.sizeLimit": "{file} è più grande della dimensione limite configurata",
"Upload.status.disabled" : "Il caricamento del file è disabilitato"
"Upload.status.disabled" : "Il caricamento del file è disabilitato",
"plugin.description.long": "Gestione dei file multimediali.",
"plugin.description.short": "Gestione dei file multimediali."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "ファイルがアップロードされました",
"notification.dropFiles.success": "{number}個のファイルがアップロードされました",
"Upload.status.sizeLimit": "{file}は設定された制限サイズよりも大きいです",
"Upload.status.disabled" : "ファイルのアップロードが無効になっています"
"Upload.status.disabled" : "ファイルのアップロードが無効になっています",
"plugin.description.long": "マルチメディアファイル管理.",
"plugin.description.short": "マルチメディアファイル管理."
}

View File

@ -27,5 +27,7 @@
"notification.config.success": "설정을 업데이트했습니다.",
"notification.delete.success": "파일을 삭제했습니다.",
"notification.dropFile.success": "파일을 업로드했습니다.",
"notification.dropFiles.success": "{number}개의 파일을 업로드 했습니다."
"notification.dropFiles.success": "{number}개의 파일을 업로드 했습니다.",
"plugin.description.long": "멀티미디어 파일 관리.",
"plugin.description.short": "멀티미디어 파일 관리."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Je bestand is geüpload",
"notification.dropFiles.success": "{number} bestanden zijn geüpload",
"Upload.status.sizeLimit": "{file} is groter dan de geconfigureerde limietgrootte",
"Upload.status.disabled" : "Bestand uploaden is uitgeschakeld"
"Upload.status.disabled" : "Bestand uploaden is uitgeschakeld",
"plugin.description.long": "Multimediabestandsbeheer.",
"plugin.description.short": "Multimediabestandsbeheer."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Plik został przesłany",
"notification.dropFiles.success": "{number} plików zostało przesłanych",
"Upload.status.sizeLimit": "{plik} jest większy niż skonfigurowany rozmiar limitu",
"Upload.status.disabled" : "Przesyłanie plików jest wyłączone"
"Upload.status.disabled" : "Przesyłanie plików jest wyłączone",
"plugin.description.long": "Zarządzanie plikami multimedialnymi.",
"plugin.description.short": "Zarządzanie plikami multimedialnymi."
}

View File

@ -24,5 +24,7 @@
"notification.config.success": "As configurações foram atualizadas",
"notification.delete.success": "O arquivo foi removido",
"notification.dropFile.success": "Seu arquivo foi enviado com sucesso",
"notification.dropFiles.success": "{number} arquivos foram enviados com sucesso"
"notification.dropFiles.success": "{number} arquivos foram enviados com sucesso",
"plugin.description.long": "Gerenciamento de arquivos multimídia.",
"plugin.description.short": "Gerenciamento de arquivos multimídia."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Seu arquivo foi transferido com sucesso",
"notification.dropFiles.success": "{number} arquivos foram transferidos com sucesso",
"Upload.status.sizeLimit": "{file} é maior que o tamanho limite configurado",
"Upload.status.disabled" : "O upload de arquivos está desativado"
"Upload.status.disabled" : "O upload de arquivos está desativado",
"plugin.description.long": "Gerenciamento de arquivos multimídia.",
"plugin.description.short": "Gerenciamento de arquivos multimídia."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Ваш файл загружен",
"notification.dropFiles.success": "Файлов загружено: {number}",
"Upload.status.sizeLimit": "{file} больше настроенного предельного размера",
"Upload.status.disabled" : "Загрузка файла отключена"
"Upload.status.disabled" : "Загрузка файла отключена",
"plugin.description.long": "Управление мультимедийными файлами.",
"plugin.description.short": "Управление мультимедийными файлами."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "Dosyanız yüklendi",
"notification.dropFiles.success": "{number} dosyalar yüklendi",
"Upload.status.sizeLimit": "{file} yapılandırılmış sınır boyutundan daha büyük",
"Upload.status.disabled" : "Dosya yükleme devre dışı"
"Upload.status.disabled" : "Dosya yükleme devre dışı",
"plugin.description.long": "Multimedya Dosya Yönetimi.",
"plugin.description.short": "Multimedya Dosya Yönetimi."
}

View File

@ -27,5 +27,7 @@
"notification.config.success": "Các cấu hình đã được cập nhật",
"notification.delete.success": "Tập tin đã được xoá",
"notification.dropFile.success": "Các tập tin của bạn đã được tải lên",
"notification.dropFiles.success": "{number} tập tin đã được tải lên"
"notification.dropFiles.success": "{number} tập tin đã được tải lên",
"plugin.description.long": "Quản lý tập tin đa phương tiện.",
"plugin.description.short": "Quản lý tập tin đa phương tiện."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "您的文件已上传",
"notification.dropFiles.success": "{number} 个文件已上传",
"Upload.status.sizeLimit": "{file}大于配置的限制大小",
"Upload.status.disabled" : "文件上传已禁用"
"Upload.status.disabled" : "文件上传已禁用",
"plugin.description.long": "多媒体档案管理.",
"plugin.description.short": "多媒体档案管理."
}

View File

@ -26,5 +26,7 @@
"notification.dropFile.success": "您的檔案已上傳",
"notification.dropFiles.success": "{number} 個檔案已上傳",
"Upload.status.sizeLimit": "{file}大於配置的限制大小",
"Upload.status.disabled" : "文件上傳已禁用"
"Upload.status.disabled" : "文件上傳已禁用",
"plugin.description.long": "多媒體檔案管理.",
"plugin.description.short": "多媒體檔案管理."
}

View File

@ -5,7 +5,7 @@
"strapi": {
"name": "Files Upload",
"icon": "cloud-upload-alt",
"description": "Description of upload plugin."
"description": "upload.plugin.description"
},
"scripts": {
"test": "echo \"no tests yet\""

View File

@ -1,4 +1,4 @@
# strapi-provider-email-sendmail
# strapi-provider-email-mailgun
## Resources

View File

@ -6,7 +6,7 @@
/* eslint-disable prefer-template */
// Public node modules.
const _ = require('lodash');
const isObject = require('lodash/isObject');
const mailgunFactory = require('mailgun-js');
/* eslint-disable no-unused-vars */
@ -47,21 +47,21 @@ module.exports = {
send: (options, cb) => {
return new Promise((resolve, reject) => {
// Default values.
options = _.isObject(options) ? options : {};
options.from = options.from || config.mailgun_default_from;
options.replyTo = options.replyTo || config.mailgun_default_replyto;
options.text = options.text || options.html;
options.html = options.html || options.text;
options = isObject(options) ? options : {};
let msg = {
from: options.from,
from: options.from || config.mailgun_default_from,
to: options.to,
subject: options.subject,
text: options.text,
html: options.html,
...(options.text && { text: options.text }),
...(options.html && { html: options.html }),
...(options.template && { template: options.template }),
...(options['h:X-Mailgun-Variables'] && {
'h:X-Mailgun-Variables': options['h:X-Mailgun-Variables'],
}),
...(options.attachment && { attachment: options.attachment }),
};
msg['h:Reply-To'] = options.replyTo;
msg['h:Reply-To'] = options.replyTo || config.mailgun_default_replyto;
mailgun.messages().send(msg, function(err) {
if (err) {

View File

@ -25,7 +25,7 @@ module.exports = ctx => {
if (fullPath.length <= 1 || fullPath[0] !== 'files') {
throw strapi.errors.badRequest(
`When using multipart/form-data you need to provide your files by prefixing them witht the 'files'.`
`When using multipart/form-data you need to provide your files by prefixing them with the 'files'.`
);
}