mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	Merge branch 'develop' of github.com:strapi/strapi into ctm/tests
This commit is contained in:
		
						commit
						88a0241e38
					
				@ -4,7 +4,6 @@
 | 
				
			|||||||
  - [.admin](#strapi-admin)
 | 
					  - [.admin](#strapi-admin)
 | 
				
			||||||
  - [.api](#strapi-api)
 | 
					  - [.api](#strapi-api)
 | 
				
			||||||
  - [.app](#strapiapp)
 | 
					  - [.app](#strapiapp)
 | 
				
			||||||
  - [.bootstrap()](#strapi-bootstrap)
 | 
					 | 
				
			||||||
  - [.config](#strapi-config)
 | 
					  - [.config](#strapi-config)
 | 
				
			||||||
  - [.controllers](#strapi-controllers)
 | 
					  - [.controllers](#strapi-controllers)
 | 
				
			||||||
  - [.hook](#strapi-hook)
 | 
					  - [.hook](#strapi-hook)
 | 
				
			||||||
@ -24,7 +23,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## strapi.admin
 | 
					## strapi.admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This object contains the controllers, models, services and configurations contained in the `./admin` folder.
 | 
					This object contains the controllers, models, services and configurations contained in the `strapi-admin` package.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## strapi.api
 | 
					## strapi.api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -35,14 +34,6 @@ And by using `strapi.api[:name]` you can access the controllers, services, the m
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Returns the Koa instance.
 | 
					Returns the Koa instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## strapi.bootstrap
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Returns a `Promise`. When resolved, it means that the `./config/functions/bootstrap.js` has been executed. Otherwise, it throws an error.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
::: note
 | 
					 | 
				
			||||||
You can also access to the bootstrap function through `strapi.config.functions.boostrap`.
 | 
					 | 
				
			||||||
:::
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## strapi.config
 | 
					## strapi.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Returns an object that represents the configurations of the project. Every JavaScript or JSON file located in the `./config` folder will be parsed into the `strapi.config` object.
 | 
					Returns an object that represents the configurations of the project. Every JavaScript or JSON file located in the `./config` folder will be parsed into the `strapi.config` object.
 | 
				
			||||||
 | 
				
			|||||||
@ -80,7 +80,13 @@ module.exports = {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create(ctx) {
 | 
					  create(ctx) {
 | 
				
			||||||
    return strapi.services.product.create(ctx.request.body);
 | 
					    if (ctx.is('multipart')) {
 | 
				
			||||||
 | 
					      // Parses strapi's formData format
 | 
				
			||||||
 | 
					      const { data, files } = this.parseMultipartData(ctx);
 | 
				
			||||||
 | 
					      return service.create(data, { files });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return service.create(ctx.request.body);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@ -96,7 +102,13 @@ module.exports = {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update(ctx) {
 | 
					  update(ctx) {
 | 
				
			||||||
    return strapi.services.product.update(ctx.params, ctx.request.body);
 | 
					    if (ctx.is('multipart')) {
 | 
				
			||||||
 | 
					      // Parses strapi's formData format
 | 
				
			||||||
 | 
					      const { data, files } = this.parseMultipartData(ctx);
 | 
				
			||||||
 | 
					      return service.update(ctx.params, data, { files });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return service.update(ctx.params, ctx.request.body);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
				
			|||||||
@ -1,406 +1,287 @@
 | 
				
			|||||||
# Queries
 | 
					# Queries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Strapi provides a utility function `strapi.query` to make database queries ORM agnostic.
 | 
					Strapi provides a utility function `strapi.query` to make database queries.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Core Queries
 | 
					You can just call `strapi.query(modelName, pluginName)` to access the query API for any model.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In Strapi's [core services](./services.md#core-services) you can see we call a `strapi.query` function.
 | 
					These queries handle for you specific Strapi features like `groups`, `filters` and `search`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When customizing your model services you might want to implement some custom database queries.
 | 
					## API Reference
 | 
				
			||||||
To help you with that here is the current implementation of the queries for both `bookshelf` and `mongoose`.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can simply copy and paste the code in your custom services.
 | 
					### `findOne`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This method returns the first entry matching some basic params.
 | 
				
			||||||
 | 
					You can also pass a populate option to specify which relations you want to be populated.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find one by id**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').findOne({ id: 1 });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find one by title**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').findOne({ title: 'my title' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find one by title and creation_date**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi
 | 
				
			||||||
 | 
					  .query('post')
 | 
				
			||||||
 | 
					  .findOne({ title: 'my title', date: '2019-01-01T00:00:00Z' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find one by id and populate a relation**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').findOne({ id: 1 }, ['tag', 'tag.picture']);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `find`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This method returns a list of entries matching Strapi filters.
 | 
				
			||||||
 | 
					You can also pass a populate option to specify which relations you want to be populated.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find by id**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').find({ id: 1 });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find by in IN, with a limit**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').find({ _limit: 10, id_in: [1, 2] });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find by date orderBy title**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi
 | 
				
			||||||
 | 
					  .query('post')
 | 
				
			||||||
 | 
					  .find({ date_gt: '2019-01-01T00:00:00Z', _sort: 'title:desc' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Find by id not in and populate a relation. Skip the first ten results**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').find({ id_nin: [1], _start: 10 }, ['tag', 'tag.picture']);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `create`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Creates an entry in the database and returns the entry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').create({
 | 
				
			||||||
 | 
					  title: 'Mytitle',
 | 
				
			||||||
 | 
					  // this is a group field. the order is persisted in db.
 | 
				
			||||||
 | 
					  seo: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      metadata: 'description',
 | 
				
			||||||
 | 
					      value: 'My description',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      metadata: 'keywords',
 | 
				
			||||||
 | 
					      value: 'someKeyword,otherKeyword',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  // pass the id of a media to link it to the entry
 | 
				
			||||||
 | 
					  picture: 1,
 | 
				
			||||||
 | 
					  // automatically creates the relations when passing the ids in the field
 | 
				
			||||||
 | 
					  tags: [1, 2, 3],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `update`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updates an entry in the database and returns the entry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Update by id**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').update(
 | 
				
			||||||
 | 
					  { id: 1 },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: 'Mytitle',
 | 
				
			||||||
 | 
					    seo: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        metadata: 'description',
 | 
				
			||||||
 | 
					        value: 'My description',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        metadata: 'keywords',
 | 
				
			||||||
 | 
					        value: 'someKeyword,otherKeyword',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    // pass the id of a media to link it to the entry
 | 
				
			||||||
 | 
					    picture: 1,
 | 
				
			||||||
 | 
					    // automatically creates the relations when passing the ids in the field
 | 
				
			||||||
 | 
					    tags: [1, 2, 3],
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When updating an entry with its groups beware that if you send the groups without any `id` the previous groups will be deleted and replaced. You can update the groups by sending their `id` with the rest of the fields:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Update by id and update previous groups**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').update(
 | 
				
			||||||
 | 
					  { id: 1 },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: 'Mytitle',
 | 
				
			||||||
 | 
					    seo: [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 2
 | 
				
			||||||
 | 
					        metadata: 'keywords',
 | 
				
			||||||
 | 
					        value: 'someKeyword,otherKeyword',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 1
 | 
				
			||||||
 | 
					        metadata: 'description',
 | 
				
			||||||
 | 
					        value: 'My description',
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    // pass the id of a media to link it to the entry
 | 
				
			||||||
 | 
					    picture: 1,
 | 
				
			||||||
 | 
					    // automatically creates the relations when passing the ids in the field
 | 
				
			||||||
 | 
					    tags: [1, 2, 3],
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Partial update by title**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').update(
 | 
				
			||||||
 | 
					  { title: 'specific title' },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: 'Mytitle',
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `delete`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Deletes and entry and return its value before deletion.
 | 
				
			||||||
 | 
					You can delete multiple entries at once with the passed params.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Delete one by id**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').delete({ id: 1 });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Delete multiple by field**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').delete({ lang: 'en' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `count`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Returns the count of entries matching Strapi filters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Count by lang**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').count({ lang: 'en' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Count by title contains**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').count({ title_contains: 'food' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Count by date less than**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').count({ date_lt: '2019-08-01T00:00:00Z' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `search`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Returns entries based on a search on all fields allowing it. (this feature will return all entries on sqlite).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Search first ten starting at 20**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').search({ _q: 'my search query', _limit: 10, _start: 20 });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Search and sort**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi
 | 
				
			||||||
 | 
					  .query('post')
 | 
				
			||||||
 | 
					  .search({ _q: 'my search query', _limit: 100, _sort: 'date:desc' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### `countSearch`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Returns the total count of entries based on a search. (this feature will return all entries on sqlite).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query('post').countSearch({ _q: 'my search query' });
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Custom Queries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When you want to customize your services or create new ones you will have to build your queries with the underlying ORM models.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To access the underlying model:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					strapi.query(modelName, plugin).model;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then you can run any queries available on the model. You should refer to the specific ORM documentation for more details:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Bookshelf
 | 
					### Bookshelf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Documentation: [https://bookshelfjs.org/](https://bookshelfjs.org/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Example**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
const _ = require('lodash');
 | 
					strapi
 | 
				
			||||||
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
 | 
					  .query('post')
 | 
				
			||||||
 | 
					  .model.query(qb => {
 | 
				
			||||||
module.exports = ({ model }) => {
 | 
					    qb.where('id', 1);
 | 
				
			||||||
  return {
 | 
					  })
 | 
				
			||||||
    find(params, populate) {
 | 
					  .fetch();
 | 
				
			||||||
      const withRelated =
 | 
					 | 
				
			||||||
        populate ||
 | 
					 | 
				
			||||||
        model.associations
 | 
					 | 
				
			||||||
          .filter(ast => ast.autoPopulate !== false)
 | 
					 | 
				
			||||||
          .map(ast => ast.alias);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const filters = convertRestQueryParams(params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return model
 | 
					 | 
				
			||||||
        .query(buildQuery({ model, filters }))
 | 
					 | 
				
			||||||
        .fetchAll({ withRelated });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findOne(params, populate) {
 | 
					 | 
				
			||||||
      const withRelated =
 | 
					 | 
				
			||||||
        populate ||
 | 
					 | 
				
			||||||
        model.associations
 | 
					 | 
				
			||||||
          .filter(ast => ast.autoPopulate !== false)
 | 
					 | 
				
			||||||
          .map(ast => ast.alias);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return model
 | 
					 | 
				
			||||||
        .forge({
 | 
					 | 
				
			||||||
          [model.primaryKey]: params[model.primaryKey] || params.id,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .fetch({
 | 
					 | 
				
			||||||
          withRelated,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    count(params = {}) {
 | 
					 | 
				
			||||||
      const { where } = convertRestQueryParams(params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return model.query(buildQuery({ model, filters: { where } })).count();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async create(values) {
 | 
					 | 
				
			||||||
      const relations = _.pick(
 | 
					 | 
				
			||||||
        values,
 | 
					 | 
				
			||||||
        model.associations.map(ast => ast.alias)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      const data = _.omit(values, model.associations.map(ast => ast.alias));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Create entry with no-relational data.
 | 
					 | 
				
			||||||
      const entry = await model.forge(data).save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Create relational data and return the entry.
 | 
					 | 
				
			||||||
      return model.updateRelations({ id: entry.id, values: relations });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async update(params, values) {
 | 
					 | 
				
			||||||
      // Extract values related to relational data.
 | 
					 | 
				
			||||||
      const relations = _.pick(
 | 
					 | 
				
			||||||
        values,
 | 
					 | 
				
			||||||
        model.associations.map(ast => ast.alias)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      const data = _.omit(values, model.associations.map(ast => ast.alias));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Create entry with no-relational data.
 | 
					 | 
				
			||||||
      await model.forge(params).save(data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Create relational data and return the entry.
 | 
					 | 
				
			||||||
      return model.updateRelations(
 | 
					 | 
				
			||||||
        Object.assign(params, { values: relations })
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async delete(params) {
 | 
					 | 
				
			||||||
      params.values = {};
 | 
					 | 
				
			||||||
      model.associations.map(association => {
 | 
					 | 
				
			||||||
        switch (association.nature) {
 | 
					 | 
				
			||||||
          case 'oneWay':
 | 
					 | 
				
			||||||
          case 'oneToOne':
 | 
					 | 
				
			||||||
          case 'manyToOne':
 | 
					 | 
				
			||||||
          case 'oneToManyMorph':
 | 
					 | 
				
			||||||
            params.values[association.alias] = null;
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          case 'oneToMany':
 | 
					 | 
				
			||||||
          case 'manyToMany':
 | 
					 | 
				
			||||||
          case 'manyToManyMorph':
 | 
					 | 
				
			||||||
            params.values[association.alias] = [];
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          default:
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await model.updateRelations(params);
 | 
					 | 
				
			||||||
      return model.forge(params).destroy();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    search(params, populate) {
 | 
					 | 
				
			||||||
      // Convert `params` object to filters compatible with Bookshelf.
 | 
					 | 
				
			||||||
      const filters = strapi.utils.models.convertParams(model.globalId, params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Select field to populate.
 | 
					 | 
				
			||||||
      const withRelated =
 | 
					 | 
				
			||||||
        populate ||
 | 
					 | 
				
			||||||
        model.associations
 | 
					 | 
				
			||||||
          .filter(ast => ast.autoPopulate !== false)
 | 
					 | 
				
			||||||
          .map(ast => ast.alias);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return model
 | 
					 | 
				
			||||||
        .query(qb => {
 | 
					 | 
				
			||||||
          buildSearchQuery(qb, model, params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (filters.sort) {
 | 
					 | 
				
			||||||
            qb.orderBy(filters.sort.key, filters.sort.order);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (filters.start) {
 | 
					 | 
				
			||||||
            qb.offset(_.toNumber(filters.start));
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (filters.limit) {
 | 
					 | 
				
			||||||
            qb.limit(_.toNumber(filters.limit));
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .fetchAll({
 | 
					 | 
				
			||||||
          withRelated,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    countSearch(params) {
 | 
					 | 
				
			||||||
      return model.query(qb => buildSearchQuery(qb, model, params)).count();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * util to build search query
 | 
					 | 
				
			||||||
 * @param {*} qb
 | 
					 | 
				
			||||||
 * @param {*} model
 | 
					 | 
				
			||||||
 * @param {*} params
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const buildSearchQuery = (qb, model, params) => {
 | 
					 | 
				
			||||||
  const query = (params._q || '').replace(/[^a-zA-Z0-9.-\s]+/g, '');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const associations = model.associations.map(x => x.alias);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const searchText = Object.keys(model._attributes)
 | 
					 | 
				
			||||||
    .filter(
 | 
					 | 
				
			||||||
      attribute =>
 | 
					 | 
				
			||||||
        attribute !== model.primaryKey && !associations.includes(attribute)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .filter(attribute =>
 | 
					 | 
				
			||||||
      ['string', 'text'].includes(model._attributes[attribute].type)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const searchInt = Object.keys(model._attributes)
 | 
					 | 
				
			||||||
    .filter(
 | 
					 | 
				
			||||||
      attribute =>
 | 
					 | 
				
			||||||
        attribute !== model.primaryKey && !associations.includes(attribute)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .filter(attribute =>
 | 
					 | 
				
			||||||
      ['integer', 'decimal', 'float'].includes(
 | 
					 | 
				
			||||||
        model._attributes[attribute].type
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const searchBool = Object.keys(model._attributes)
 | 
					 | 
				
			||||||
    .filter(
 | 
					 | 
				
			||||||
      attribute =>
 | 
					 | 
				
			||||||
        attribute !== model.primaryKey && !associations.includes(attribute)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .filter(attribute =>
 | 
					 | 
				
			||||||
      ['boolean'].includes(model._attributes[attribute].type)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!_.isNaN(_.toNumber(query))) {
 | 
					 | 
				
			||||||
    searchInt.forEach(attribute => {
 | 
					 | 
				
			||||||
      qb.orWhereRaw(`${attribute} = ${_.toNumber(query)}`);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (query === 'true' || query === 'false') {
 | 
					 | 
				
			||||||
    searchBool.forEach(attribute => {
 | 
					 | 
				
			||||||
      qb.orWhereRaw(`${attribute} = ${_.toNumber(query === 'true')}`);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Search in columns with text using index.
 | 
					 | 
				
			||||||
  switch (model.client) {
 | 
					 | 
				
			||||||
    case 'mysql':
 | 
					 | 
				
			||||||
      qb.orWhereRaw(
 | 
					 | 
				
			||||||
        `MATCH(${searchText.join(',')}) AGAINST(? IN BOOLEAN MODE)`,
 | 
					 | 
				
			||||||
        `*${query}*`
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case 'pg': {
 | 
					 | 
				
			||||||
      const searchQuery = searchText.map(attribute =>
 | 
					 | 
				
			||||||
        _.toLower(attribute) === attribute
 | 
					 | 
				
			||||||
          ? `to_tsvector(${attribute})`
 | 
					 | 
				
			||||||
          : `to_tsvector('${attribute}')`
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      qb.orWhereRaw(`${searchQuery.join(' || ')} @@ to_tsquery(?)`, query);
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Mongoose
 | 
					### Mongoose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					Documentation: [https://mongoosejs.com/](https://mongoosejs.com/)
 | 
				
			||||||
const _ = require('lodash');
 | 
					 | 
				
			||||||
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = ({ model, strapi }) => {
 | 
					**Example**
 | 
				
			||||||
  const assocs = model.associations.map(ast => ast.alias);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const defaultPopulate = model.associations
 | 
					 | 
				
			||||||
    .filter(ast => ast.autoPopulate !== false)
 | 
					 | 
				
			||||||
    .map(ast => ast.alias);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    find(params, populate) {
 | 
					 | 
				
			||||||
      const populateOpt = populate || defaultPopulate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const filters = convertRestQueryParams(params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return buildQuery({
 | 
					 | 
				
			||||||
        model,
 | 
					 | 
				
			||||||
        filters,
 | 
					 | 
				
			||||||
        populate: populateOpt,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    findOne(params, populate) {
 | 
					 | 
				
			||||||
      const populateOpt = populate || defaultPopulate;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return model
 | 
					 | 
				
			||||||
        .findOne({
 | 
					 | 
				
			||||||
          [model.primaryKey]: params[model.primaryKey] || params.id,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .populate(populateOpt);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    count(params) {
 | 
					 | 
				
			||||||
      const filters = convertRestQueryParams(params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return buildQuery({
 | 
					 | 
				
			||||||
        model,
 | 
					 | 
				
			||||||
        filters: { where: filters.where },
 | 
					 | 
				
			||||||
      }).count();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async create(values) {
 | 
					 | 
				
			||||||
      // Extract values related to relational data.
 | 
					 | 
				
			||||||
      const relations = _.pick(values, assocs);
 | 
					 | 
				
			||||||
      const data = _.omit(values, assocs);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Create entry with no-relational data.
 | 
					 | 
				
			||||||
      const entry = await model.create(data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Create relational data and return the entry.
 | 
					 | 
				
			||||||
      return model.updateRelations({ _id: entry.id, values: relations });
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async update(params, values) {
 | 
					 | 
				
			||||||
      // Extract values related to relational data.
 | 
					 | 
				
			||||||
      const relations = _.pick(values, assocs);
 | 
					 | 
				
			||||||
      const data = _.omit(values, assocs);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Update entry with no-relational data.
 | 
					 | 
				
			||||||
      await model.updateOne(params, data, { multi: true });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Update relational data and return the entry.
 | 
					 | 
				
			||||||
      return model.updateRelations(
 | 
					 | 
				
			||||||
        Object.assign(params, { values: relations })
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async delete(params) {
 | 
					 | 
				
			||||||
      const data = await model
 | 
					 | 
				
			||||||
        .findOneAndRemove(params, {})
 | 
					 | 
				
			||||||
        .populate(defaultPopulate);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!data) {
 | 
					 | 
				
			||||||
        return data;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await Promise.all(
 | 
					 | 
				
			||||||
        model.associations.map(async association => {
 | 
					 | 
				
			||||||
          if (!association.via || !data._id || association.dominant) {
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          const search =
 | 
					 | 
				
			||||||
            _.endsWith(association.nature, 'One') ||
 | 
					 | 
				
			||||||
            association.nature === 'oneToMany'
 | 
					 | 
				
			||||||
              ? { [association.via]: data._id }
 | 
					 | 
				
			||||||
              : { [association.via]: { $in: [data._id] } };
 | 
					 | 
				
			||||||
          const update =
 | 
					 | 
				
			||||||
            _.endsWith(association.nature, 'One') ||
 | 
					 | 
				
			||||||
            association.nature === 'oneToMany'
 | 
					 | 
				
			||||||
              ? { [association.via]: null }
 | 
					 | 
				
			||||||
              : { $pull: { [association.via]: data._id } };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // Retrieve model.
 | 
					 | 
				
			||||||
          const model = association.plugin
 | 
					 | 
				
			||||||
            ? strapi.plugins[association.plugin].models[
 | 
					 | 
				
			||||||
                association.model || association.collection
 | 
					 | 
				
			||||||
              ]
 | 
					 | 
				
			||||||
            : strapi.models[association.model || association.collection];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return model.update(search, update, { multi: true });
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return data;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    search(params, populate) {
 | 
					 | 
				
			||||||
      // Convert `params` object to filters compatible with Mongo.
 | 
					 | 
				
			||||||
      const filters = strapi.utils.models.convertParams(model.globalId, params);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const $or = buildSearchOr(model, params._q);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return model
 | 
					 | 
				
			||||||
        .find({ $or })
 | 
					 | 
				
			||||||
        .sort(filters.sort)
 | 
					 | 
				
			||||||
        .skip(filters.start)
 | 
					 | 
				
			||||||
        .limit(filters.limit)
 | 
					 | 
				
			||||||
        .populate(populate || defaultPopulate);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    countSearch(params) {
 | 
					 | 
				
			||||||
      const $or = buildSearchOr(model, params._q);
 | 
					 | 
				
			||||||
      return model.find({ $or }).countDocuments();
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const buildSearchOr = (model, query) => {
 | 
					 | 
				
			||||||
  return Object.keys(model.attributes).reduce((acc, curr) => {
 | 
					 | 
				
			||||||
    switch (model.attributes[curr].type) {
 | 
					 | 
				
			||||||
      case 'integer':
 | 
					 | 
				
			||||||
      case 'float':
 | 
					 | 
				
			||||||
      case 'decimal':
 | 
					 | 
				
			||||||
        if (!_.isNaN(_.toNumber(query))) {
 | 
					 | 
				
			||||||
          return acc.concat({ [curr]: query });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return acc;
 | 
					 | 
				
			||||||
      case 'string':
 | 
					 | 
				
			||||||
      case 'text':
 | 
					 | 
				
			||||||
      case 'password':
 | 
					 | 
				
			||||||
        return acc.concat({ [curr]: { $regex: query, $options: 'i' } });
 | 
					 | 
				
			||||||
      case 'boolean':
 | 
					 | 
				
			||||||
        if (query === 'true' || query === 'false') {
 | 
					 | 
				
			||||||
          return acc.concat({ [curr]: query === 'true' });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return acc;
 | 
					 | 
				
			||||||
      default:
 | 
					 | 
				
			||||||
        return acc;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Understanding queries
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`strapi.query` will generate a queries object by passing a model to the factory function matching the model's ORM.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
In this example the User model from the Users and Permissions plugin is used.
 | 
					 | 
				
			||||||
By default the model is passed to `strapi/lib/core-api/queries/bookshelf.js` or `strapi/lib/core-api/queries/mongoose.js` depending on your connection configuration.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
const queries = strapi.query('users', 'users-permissions');
 | 
					strapi
 | 
				
			||||||
 | 
					  .query('post')
 | 
				
			||||||
// makes the bookshelf or mongoose queries available with a specific model binded to them
 | 
					  .model.find({
 | 
				
			||||||
queries.find();
 | 
					    { date: { $gte: '2019-01-01T00.00.00Z }
 | 
				
			||||||
```
 | 
					  });
 | 
				
			||||||
 | 
					 | 
				
			||||||
### Usage in plugins
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
To make plugins ORM agnostic, we create a queries function for every plugin that will either load the queries from the plugin's `config/queries` folder if it exists or use the default queries from the `core-api/queries` folder.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```js
 | 
					 | 
				
			||||||
// this will call the queries defined in the users-permissions plugin
 | 
					 | 
				
			||||||
// with the model user from the users-permissions plugin
 | 
					 | 
				
			||||||
strapi.plugins['users-permissions'].queries('user', 'users-permissions').find();
 | 
					 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
				
			|||||||
@ -74,8 +74,16 @@ module.exports = {
 | 
				
			|||||||
   * @return {Promise}
 | 
					   * @return {Promise}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create(values) {
 | 
					  async create(data, { files } = {}) {
 | 
				
			||||||
    return strapi.query(Product).create(values);
 | 
					    const entry = await strapi.query(model).create(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (files) {
 | 
				
			||||||
 | 
					      // automatically uploads the files based on the entry and the model
 | 
				
			||||||
 | 
					      await this.uploadFiles(entry, files, { model });
 | 
				
			||||||
 | 
					      return this.findOne({ id: entry.id });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return entry;
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@ -90,8 +98,16 @@ module.exports = {
 | 
				
			|||||||
   * @return {Promise}
 | 
					   * @return {Promise}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update(params, values) {
 | 
					  async update(params, data, { files } = {}) {
 | 
				
			||||||
    return strapi.query(Product).update(params, values);
 | 
					    const entry = await strapi.query(model).update(params, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (files) {
 | 
				
			||||||
 | 
					      // automatically uploads the files based on the entry and the model
 | 
				
			||||||
 | 
					      await this.uploadFiles(entry, files, { model });
 | 
				
			||||||
 | 
					      return this.findOne({ id: entry.id });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return entry;
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
				
			|||||||
@ -35,10 +35,10 @@
 | 
				
			|||||||
  "app.components.HomePage.createBlock.content.second": " — плагин, который поможет вам определить структуру ваших данных. Если вы новичок, мы настоятельно рекомендуем вам изучить наше ",
 | 
					  "app.components.HomePage.createBlock.content.second": " — плагин, который поможет вам определить структуру ваших данных. Если вы новичок, мы настоятельно рекомендуем вам изучить наше ",
 | 
				
			||||||
  "app.components.HomePage.createBlock.content.tutorial": " руководство.",
 | 
					  "app.components.HomePage.createBlock.content.tutorial": " руководство.",
 | 
				
			||||||
  "app.components.HomePage.cta": "ПОДПИСАТЬСЯ",
 | 
					  "app.components.HomePage.cta": "ПОДПИСАТЬСЯ",
 | 
				
			||||||
  "app.components.HomePage.newsLetter": "Подпишитесь на нашу рассылку, чтобы быть в курсе новостей Strapi",
 | 
					  "app.components.HomePage.newsLetter": "Подпишитесь на рассылку, чтобы быть в курсе новостей Strapi",
 | 
				
			||||||
  "app.components.HomePage.support": "ПОДДЕРЖИТЕ НАС",
 | 
					  "app.components.HomePage.support": "ПОДДЕРЖИТЕ НАС",
 | 
				
			||||||
  "app.components.HomePage.support.content": "Покупая футболку, вы помогаете нам продолжать работу над проектом и предоставлять вам наилучшее из возможных решений!",
 | 
					  "app.components.HomePage.support.content": "Покупая футболку, вы помогаете нам продолжать работу над проектом и предоставлять вам наилучшее из возможных решений!",
 | 
				
			||||||
  "app.components.HomePage.support.link": "ЗАКАЗАТЬ НАШУ ФУТБОЛКУ СЕЙЧАС",
 | 
					  "app.components.HomePage.support.link": "ЗАКАЗАТЬ ФУТБОЛКУ СЕЙЧАС",
 | 
				
			||||||
  "app.components.HomePage.welcome": "Добро пожаловать!",
 | 
					  "app.components.HomePage.welcome": "Добро пожаловать!",
 | 
				
			||||||
  "app.components.HomePage.welcome.again": "Добро пожаловать, ",
 | 
					  "app.components.HomePage.welcome.again": "Добро пожаловать, ",
 | 
				
			||||||
  "app.components.HomePage.welcomeBlock.content": "Мы рады, что вы присоединились к сообществу. Нам необходима обратная связь для развития проекта, поэтому не стесняйтесь писать нам в ",
 | 
					  "app.components.HomePage.welcomeBlock.content": "Мы рады, что вы присоединились к сообществу. Нам необходима обратная связь для развития проекта, поэтому не стесняйтесь писать нам в ",
 | 
				
			||||||
@ -52,6 +52,8 @@
 | 
				
			|||||||
  "app.components.InputFileDetails.originalName": "Первоначальное название:",
 | 
					  "app.components.InputFileDetails.originalName": "Первоначальное название:",
 | 
				
			||||||
  "app.components.InputFileDetails.remove": "Удалить этот файл",
 | 
					  "app.components.InputFileDetails.remove": "Удалить этот файл",
 | 
				
			||||||
  "app.components.InputFileDetails.size": "Размер:",
 | 
					  "app.components.InputFileDetails.size": "Размер:",
 | 
				
			||||||
 | 
					  "app.components.InstallPluginPage.Download.title": "Загрузка...",
 | 
				
			||||||
 | 
					  "app.components.InstallPluginPage.Download.description": "Для загрузки и установки плагина может потребоваться несколько секунд.",
 | 
				
			||||||
  "app.components.InstallPluginPage.InputSearch.label": " ",
 | 
					  "app.components.InstallPluginPage.InputSearch.label": " ",
 | 
				
			||||||
  "app.components.InstallPluginPage.InputSearch.placeholder": "Искать плагин... (ex: authentication)",
 | 
					  "app.components.InstallPluginPage.InputSearch.placeholder": "Искать плагин... (ex: authentication)",
 | 
				
			||||||
  "app.components.InstallPluginPage.description": "Расширяйте ваше приложение без усилий.",
 | 
					  "app.components.InstallPluginPage.description": "Расширяйте ваше приложение без усилий.",
 | 
				
			||||||
@ -65,7 +67,9 @@
 | 
				
			|||||||
  "app.components.InstallPluginPopup.navLink.faq": "faq",
 | 
					  "app.components.InstallPluginPopup.navLink.faq": "faq",
 | 
				
			||||||
  "app.components.InstallPluginPopup.navLink.screenshots": "Скриншоты",
 | 
					  "app.components.InstallPluginPopup.navLink.screenshots": "Скриншоты",
 | 
				
			||||||
  "app.components.InstallPluginPopup.noDescription": "Нет описания",
 | 
					  "app.components.InstallPluginPopup.noDescription": "Нет описания",
 | 
				
			||||||
  "app.components.LeftMenuFooter.poweredBy": "С гордостью предоставлено ",
 | 
					  "app.components.LeftMenuFooter.documentation": "Документация",
 | 
				
			||||||
 | 
					  "app.components.LeftMenuFooter.help": "Помощь",
 | 
				
			||||||
 | 
					  "app.components.LeftMenuFooter.poweredBy": "Работает на ",
 | 
				
			||||||
  "app.components.LeftMenuLinkContainer.configuration": "Настройки",
 | 
					  "app.components.LeftMenuLinkContainer.configuration": "Настройки",
 | 
				
			||||||
  "app.components.LeftMenuLinkContainer.general": "Общие",
 | 
					  "app.components.LeftMenuLinkContainer.general": "Общие",
 | 
				
			||||||
  "app.components.LeftMenuLinkContainer.installNewPlugin": "Магазин",
 | 
					  "app.components.LeftMenuLinkContainer.installNewPlugin": "Магазин",
 | 
				
			||||||
@ -75,26 +79,35 @@
 | 
				
			|||||||
  "app.components.ListPluginsPage.description": "Список установленных плагинов.",
 | 
					  "app.components.ListPluginsPage.description": "Список установленных плагинов.",
 | 
				
			||||||
  "app.components.ListPluginsPage.helmet.title": "Список плагинов",
 | 
					  "app.components.ListPluginsPage.helmet.title": "Список плагинов",
 | 
				
			||||||
  "app.components.ListPluginsPage.title": "Плагины",
 | 
					  "app.components.ListPluginsPage.title": "Плагины",
 | 
				
			||||||
 | 
					  "app.components.Logout.admin": "Управлять администраторами",
 | 
				
			||||||
  "app.components.Logout.profile": "Профиль",
 | 
					  "app.components.Logout.profile": "Профиль",
 | 
				
			||||||
  "app.components.Logout.logout": "Выйти",
 | 
					  "app.components.Logout.logout": "Выйти",
 | 
				
			||||||
  "app.components.NotFoundPage.back": "Вернуться на главную",
 | 
					  "app.components.NotFoundPage.back": "Вернуться на главную",
 | 
				
			||||||
  "app.components.NotFoundPage.description": "Не найдено",
 | 
					  "app.components.NotFoundPage.description": "Не найдено",
 | 
				
			||||||
  "app.components.Official": "Официальный",
 | 
					  "app.components.Official": "Официальный",
 | 
				
			||||||
 | 
					  "app.components.Onboarding.label.completed": "% завершено",
 | 
				
			||||||
 | 
					  "app.components.Onboarding.title": "Смотреть вводные видео",
 | 
				
			||||||
  "app.components.PluginCard.Button.label.download": "Скачать",
 | 
					  "app.components.PluginCard.Button.label.download": "Скачать",
 | 
				
			||||||
  "app.components.PluginCard.Button.label.install": "Уже установленно",
 | 
					  "app.components.PluginCard.Button.label.install": "Уже установлено",
 | 
				
			||||||
  "app.components.PluginCard.Button.label.support": "Поддержать нас",
 | 
					  "app.components.PluginCard.Button.label.support": "Поддержать нас",
 | 
				
			||||||
  "app.components.PluginCard.compatible": "Совместимо с вашим приложением",
 | 
					  "app.components.PluginCard.compatible": "Совместимо с вашим приложением",
 | 
				
			||||||
  "app.components.PluginCard.compatibleCommunity": "Совместимо с сообществом",
 | 
					  "app.components.PluginCard.compatibleCommunity": "Совместимо с сообществом",
 | 
				
			||||||
  "app.components.PluginCard.more-details": "Больше деталей",
 | 
					  "app.components.PluginCard.more-details": "Больше деталей",
 | 
				
			||||||
 | 
					  "app.components.PluginCard.PopUpWarning.install.impossible.autoReload.needed": "Функция автоматической перезагрузки должна быть отключена. Пожалуйста, запустите ваше приложение с помощью `yarn develop`.",
 | 
				
			||||||
 | 
					  "app.components.PluginCard.PopUpWarning.install.impossible.environment": "В целях безопасности плагин может быть загружен только в develop-окружении.",
 | 
				
			||||||
 | 
					  "app.components.PluginCard.PopUpWarning.install.impossible.confirm": "Я понимаю!",
 | 
				
			||||||
 | 
					  "app.components.PluginCard.PopUpWarning.install.impossible.title": "Загрузка невозможна",
 | 
				
			||||||
  "app.components.PluginCard.price.free": "Бесплатно",
 | 
					  "app.components.PluginCard.price.free": "Бесплатно",
 | 
				
			||||||
 | 
					  "app.components.PluginCard.settings": "Настройки",
 | 
				
			||||||
  "app.components.listPlugins.button": "Добавить новый плагин",
 | 
					  "app.components.listPlugins.button": "Добавить новый плагин",
 | 
				
			||||||
  "app.components.listPlugins.title.none": "Нет установленных плагинов",
 | 
					  "app.components.listPlugins.title.none": "Нет установленных плагинов",
 | 
				
			||||||
  "app.components.listPlugins.title.plural": "{number} плагинов установленно",
 | 
					  "app.components.listPlugins.title.plural": "{number} плагинов установлено",
 | 
				
			||||||
  "app.components.listPlugins.title.singular": "{number} плагин установлен",
 | 
					  "app.components.listPlugins.title.singular": "{number} плагин установлен",
 | 
				
			||||||
  "app.components.listPluginsPage.deletePlugin.error": "Возникла ошибка при установке плагина",
 | 
					  "app.components.listPluginsPage.deletePlugin.error": "Возникла ошибка при установке плагина",
 | 
				
			||||||
  "app.utils.SelectOption.defaultMessage": " ",
 | 
					  "app.utils.SelectOption.defaultMessage": " ",
 | 
				
			||||||
  "app.utils.defaultMessage": " ",
 | 
					  "app.utils.defaultMessage": " ",
 | 
				
			||||||
  "app.utils.placeholder.defaultMessage": " ",
 | 
					  "app.utils.placeholder.defaultMessage": " ",
 | 
				
			||||||
 | 
					  "components.AutoReloadBlocker.description": "Запустите Strapi с помощью одной из следующих команд:",
 | 
				
			||||||
  "components.AutoReloadBlocker.header": "Функционал перезапуска необходим для этого плагина.",
 | 
					  "components.AutoReloadBlocker.header": "Функционал перезапуска необходим для этого плагина.",
 | 
				
			||||||
  "components.ErrorBoundary.title": "Что-то пошло не так...",
 | 
					  "components.ErrorBoundary.title": "Что-то пошло не так...",
 | 
				
			||||||
  "components.Input.error.attribute.key.taken": "Это значение уже существует",
 | 
					  "components.Input.error.attribute.key.taken": "Это значение уже существует",
 | 
				
			||||||
@ -113,7 +126,9 @@
 | 
				
			|||||||
  "components.Input.error.validation.required": "Необходимое поле для заполнения.",
 | 
					  "components.Input.error.validation.required": "Необходимое поле для заполнения.",
 | 
				
			||||||
  "components.ListRow.empty": "Нет данных для отображения.",
 | 
					  "components.ListRow.empty": "Нет данных для отображения.",
 | 
				
			||||||
  "components.OverlayBlocker.description": "Вы воспользовались функционалом, который требует перезапуска сервера. Пожалуйста, подождите.",
 | 
					  "components.OverlayBlocker.description": "Вы воспользовались функционалом, который требует перезапуска сервера. Пожалуйста, подождите.",
 | 
				
			||||||
 | 
					  "components.OverlayBlocker.description.serverError": "Сервер должен был перезагрузиться, пожалуйста, проверьте ваши логи в терминале.",
 | 
				
			||||||
  "components.OverlayBlocker.title": "Ожидание перезапуска...",
 | 
					  "components.OverlayBlocker.title": "Ожидание перезапуска...",
 | 
				
			||||||
 | 
					  "components.OverlayBlocker.title.serverError": "Перезапуск занимает больше времени, чем ожидалось",
 | 
				
			||||||
  "components.PageFooter.select": "записей на странице",
 | 
					  "components.PageFooter.select": "записей на странице",
 | 
				
			||||||
  "components.ProductionBlocker.description": "Для безопасности мы должны заблокировать его для других вариантов.",
 | 
					  "components.ProductionBlocker.description": "Для безопасности мы должны заблокировать его для других вариантов.",
 | 
				
			||||||
  "components.ProductionBlocker.header": "Этот плагин доступен только на стадии разработки.",
 | 
					  "components.ProductionBlocker.header": "Этот плагин доступен только на стадии разработки.",
 | 
				
			||||||
@ -137,5 +152,6 @@
 | 
				
			|||||||
  "components.popUpWarning.title": "Пожалуйста, подтвердите",
 | 
					  "components.popUpWarning.title": "Пожалуйста, подтвердите",
 | 
				
			||||||
  "notification.error": "Произошла ошибка",
 | 
					  "notification.error": "Произошла ошибка",
 | 
				
			||||||
  "notification.error.layout": "Не удалось получить макет",
 | 
					  "notification.error.layout": "Не удалось получить макет",
 | 
				
			||||||
  "request.error.model.unknown": "Модель данных не существует"
 | 
					  "request.error.model.unknown": "Модель данных не существует",
 | 
				
			||||||
}
 | 
					  "app.utils.delete": "Удалить"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -154,6 +154,7 @@ function syncLayouts(configuration, schema) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const appendToEditLayout = (layout = [], keysToAppend, schema) => {
 | 
					const appendToEditLayout = (layout = [], keysToAppend, schema) => {
 | 
				
			||||||
 | 
					  if (keysToAppend.length === 0) return layout;
 | 
				
			||||||
  let currentRowIndex = Math.max(layout.length - 1, 0);
 | 
					  let currentRowIndex = Math.max(layout.length - 1, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // init currentRow if necessary
 | 
					  // init currentRow if necessary
 | 
				
			||||||
 | 
				
			|||||||
@ -195,27 +195,49 @@ module.exports = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      // Resolver can be a function. Be also a native resolver or a controller's action.
 | 
					      // Resolver can be a function. Be also a native resolver or a controller's action.
 | 
				
			||||||
      if (_.isFunction(resolver)) {
 | 
					      if (_.isFunction(resolver)) {
 | 
				
			||||||
        context.params = Query.convertToParams(
 | 
					        const normalizedName = _.toLower(name);
 | 
				
			||||||
          options.input.where || {},
 | 
					
 | 
				
			||||||
          (plugin ? strapi.plugins[plugin].models[name] : strapi.models[name])
 | 
					        let primaryKey;
 | 
				
			||||||
            .primaryKey
 | 
					
 | 
				
			||||||
        );
 | 
					        if (plugin) {
 | 
				
			||||||
        context.request.body = options.input.data || {};
 | 
					          primaryKey = strapi.plugins[plugin].models[normalizedName].primaryKey;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          primaryKey = strapi.models[normalizedName].primaryKey;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.input && options.input.where) {
 | 
				
			||||||
 | 
					          context.params = Query.convertToParams(
 | 
				
			||||||
 | 
					            options.input.where || {},
 | 
				
			||||||
 | 
					            primaryKey
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          context.params = {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (options.input && options.input.data) {
 | 
				
			||||||
 | 
					          context.request.body = options.input.data || {};
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          context.request.body = options;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (isController) {
 | 
					        if (isController) {
 | 
				
			||||||
          const values = await resolver.call(null, context);
 | 
					          const values = await resolver.call(null, context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (ctx.body) {
 | 
					          if (ctx.body) {
 | 
				
			||||||
            return {
 | 
					            return options.input
 | 
				
			||||||
              [pluralize.singular(name)]: ctx.body,
 | 
					              ? {
 | 
				
			||||||
            };
 | 
					                  [pluralize.singular(normalizedName)]: ctx.body,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              : ctx.body;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const body = values && values.toJSON ? values.toJSON() : values;
 | 
					          const body = values && values.toJSON ? values.toJSON() : values;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          return {
 | 
					          return options.input
 | 
				
			||||||
            [pluralize.singular(name)]: body,
 | 
					            ? {
 | 
				
			||||||
          };
 | 
					                [pluralize.singular(normalizedName)]: body,
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : body;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return resolver.call(null, obj, options, context);
 | 
					        return resolver.call(null, obj, options, context);
 | 
				
			||||||
 | 
				
			|||||||
@ -223,14 +223,19 @@ const schemaBuilder = {
 | 
				
			|||||||
            : {};
 | 
					            : {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          switch (type) {
 | 
					          switch (type) {
 | 
				
			||||||
            case 'Mutation':
 | 
					            case 'Mutation': {
 | 
				
			||||||
              // TODO: Verify this...
 | 
					              // TODO: Verify this...
 | 
				
			||||||
 | 
					              const [name, action] = acc[type][resolver].split('.');
 | 
				
			||||||
 | 
					              const normalizedName = _.toLower(name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              acc[type][resolver] = Mutation.composeMutationResolver(
 | 
					              acc[type][resolver] = Mutation.composeMutationResolver(
 | 
				
			||||||
                strapi.plugins.graphql.config._schema.graphql,
 | 
					                strapi.plugins.graphql.config._schema.graphql,
 | 
				
			||||||
                plugin,
 | 
					                plugin,
 | 
				
			||||||
                resolver
 | 
					                normalizedName,
 | 
				
			||||||
 | 
					                action
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
              break;
 | 
					              break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            case 'Query':
 | 
					            case 'Query':
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
              acc[type][resolver] = Query.composeQueryResolver(
 | 
					              acc[type][resolver] = Query.composeQueryResolver(
 | 
				
			||||||
 | 
				
			|||||||
@ -472,6 +472,9 @@ class Strapi extends EventEmitter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // custom queries made easy
 | 
					    // custom queries made easy
 | 
				
			||||||
    Object.assign(query, {
 | 
					    Object.assign(query, {
 | 
				
			||||||
 | 
					      get model() {
 | 
				
			||||||
 | 
					        return model;
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      custom(mapping) {
 | 
					      custom(mapping) {
 | 
				
			||||||
        if (typeof mapping === 'function') {
 | 
					        if (typeof mapping === 'function') {
 | 
				
			||||||
          return mapping.bind(query, { model, modelKey });
 | 
					          return mapping.bind(query, { model, modelKey });
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user