Merge branch 'database-setup' of github.com:strapi/strapi into database-setup

This commit is contained in:
Jim Laurie 2018-01-10 18:10:20 +01:00
commit 4e1fe20531
39 changed files with 544 additions and 599 deletions

View File

@ -82,4 +82,4 @@ For general help using Strapi, please refer to [the official Strapi documentatio
## License
[MIT License](LICENSE.md) Copyright (c) 2015-2017 [Strapi Solutions](http://strapi.io/).
[MIT License](LICENSE.md) Copyright (c) 2015-2018 [Strapi Solutions](http://strapi.io/).

View File

@ -29,8 +29,8 @@ Get to know more about Strapi and how it works.
**[Guides](configurations/configurations.md)**<br />
Get familiar with Strapi. Discover concrete examples on how to develop the features you need.
**[Plugins](plugins/development.md)**<br />
Understand how to install plugins and develop your owns.
**[Plugin Development](plugin-development/quick-start.md)**<br />
Understand how to develop your own plugin.
**[API Reference](api-reference/reference.md)**<br />
Learn about Strapi's API, the `strapi` object that is available in your backend.

View File

@ -30,12 +30,14 @@
* [Routing](guides/routing.md)
* [Services](guides/services.md)
### Plugins
* [Quick start](plugins/quick-start.md)
* [Development](plugins/development.md)
* [Helpers](plugins/utils.md)
* [UI Components](plugins/ui-components.md)
* [Advanced usage](plugins/advanced.md)
### Plugin Development
* [Quick start](plugin-development/quick-start.md)
* [Plugin Folders and Files Architecture](plugin-development/plugin-architecture.md)
* [Back-end Development](plugin-development/backend-development.md)
* [Front-end Development](plugin-development/frontend-development.md)
* [Front-end Use Cases](plugin-development/frontend-use-cases.md)
* [Front-end Helpers](plugin-development/utils.md)
* [Front-end UI Components](plugin-development/ui-components.md)
### Advanced Usage
* [Admin panel](advanced/customize-admin.md)

View File

@ -77,7 +77,7 @@ Returns an object of plugins available in the project. Each plugin object contai
## strapi.query
Returns a function that will returns the available queries for this model. This feature is only available inside the plugin's files (controllers, services, custom functions). For more details, see the [ORM queries section](../plugins/development.md#ORM queries).
Returns a function that will returns the available queries for this model. This feature is only available inside the plugin's files (controllers, services, custom functions). For more details, see the [ORM queries section](../plugin-development/backend-development.md#ORM queries).
## strapi.reload

View File

@ -170,7 +170,7 @@ strapi generate:plugin <name>
Example: `strapi generate:plugin user` will create the plugin at `./plugins/user`.
Please refer to the [plugins documentation](../plugins/development.md) to know more.
Please refer to the [plugin develoment documentation](../plugin-development/quick-start.md) to know more.
***
@ -198,7 +198,7 @@ options: [--dev]
> **Note: You have to restart the server to load the plugin into your project.**
Please refer to the [plugins documentation](../plugins/quick-start.md) to know more.
Please refer to the [plugins documentation](../plugin-development/quick-start.md) to know more.
***
@ -215,7 +215,7 @@ strapi uninstall <name>
Example: `strapi uninstall content-type-builder` will remove the folder at `./plugins/content-type-builder`.
Please refer to the [plugins documentation](../plugins/quick-start.md) to know more.
Please refer to the [plugins documentation](../plugin-development/quick-start.md) to know more.
***

View File

@ -200,7 +200,7 @@ Internationalization and localization (i18n) allows to adapt the project to diff
A plugin is like a sub-app fully independent. It has its own business logic with its dedicated models, controllers, services, middlewares or hooks. It can also contains an UI integrated into the admin panel to use it easily. It allows to develops or plugs features in a project in a short time span.
> Please refer to the [plugins documentation](../plugins/quick-start.md) for more informations.
> Please refer to the [plugins documentation](../plugin-development/quick-start.md) for more informations.
***
@ -208,7 +208,7 @@ A plugin is like a sub-app fully independent. It has its own business logic with
The admin panel uses [Bootstrap](http://getbootstrap.com/) to be styled on top of solid conventions and reusable CSS classes. Also, it uses [PostCSS](https://github.com/postcss/postcss) and [PostCSS SCSS](https://github.com/postcss/postcss-scss) to keep the code maintainable.
> Please refer to the [plugin development](../plugins/development.md#styles) for detailed informations.
> Please refer to the [plugin front-end development](../plugin-development/frontend-development.md#styling) for detailed informations.
***

View File

@ -53,7 +53,7 @@ These configurations are accessible through `strapi.config.backendURL` and `stra
## Language
As described in the [i18n documentation](../plugins/development.md#i18n), Strapi includes an internationalization system. This is especially useful to translate API messages (errors, etc.).
As described in the [i18n documentation](../plugin-development/frontend-development.md#i18n), Strapi includes an internationalization system. This is especially useful to translate API messages (errors, etc.).
**Path —** `./config/language.json`.
```json

View File

@ -0,0 +1,202 @@
# Back-end Development
This section explains how the 'back-end part' of your plugin works.
## Routes
The plugin API routes are defined in the `./plugins/**/config/routes.json` file.
> Please refer to [router documentation](../guides/routing.md) for informations.
**Route prefix**
Each route of a plugin is prefixed by the name of the plugin (eg: `/my-plugin/my-plugin-route`).
To disable the prefix, add the `prefix` attribute to each concerned route, like below:
```json
{
"method": "GET",
"path": "/my-plugin-route",
"handler": "MyPlugin.action",
"prefix": false
}
```
## CLI
The CLI can be used to generate files in the plugins folders.
Please refer to the [CLI documentation](../cli/CLI.md) for more informations.
## Controllers
Controllers contain functions executed according to the requested route.
Please refer to the [Controllers documentation](../guides/controllers.md) for more informations.
## Models
A plugin can have its own models.
### Table/Collection naming
Sometimes it happens that the plugins inject models that have the same name as yours. Let's take a quick example.
You already have `User` model defining in your `./api/user/models/User.settings.json` API. And you decide to install the `Users & Permissions` plugin. This plugin also contains a `User` model. To avoid the conflicts, the plugins' models are not globally exposed which means you cannot access to the plugin's model like this:
```js
module.exports = {
findUser: async function (params) {
// This `User` global variable will always make a reference the User model defining in your `./api/xxx/models/User.settings.json`.
return await User.find();
}
}
```
Also, the table/collection name won't be `users` because you already have a `User` model. That's why, the framework will automatically prefix the table/collection name for this model with the name of the plugin. Which means in our example, the table/collection name of the `User` model of our plugin `Users & Permissions` will be `users-permissions_users`. If you want to force the table/collection name of the plugin's model, you can add the `collectionName` attribute in your model.
Please refer to the [Models documentation](../guides/models.md) for more informations.
## Policies
### Global policies
A plugin can also use a globally exposed policy in the current Strapi project.
```json
{
"routes": [
{
"method": "GET",
"path": "/",
"handler": "MyPlugin.index",
"config": {
"policies": [
"global.isAuthenticated"
]
}
}
]
}
```
### Plugin policies
A plugin can have its own policies, such as adding security rules. For instance, if the plugin includes a policy named `isAuthenticated`, the syntax to use this policy would be:
```json
{
"routes": [
{
"method": "GET",
"path": "/",
"handler": "MyPlugin.index",
"config": {
"policies": [
"plugins.myPlugin.isAuthenticated"
]
}
}
]
}
```
Please refer to the [Policies documentation](../guides/policies.md) for more informations.
## ORM queries
Strapi supports multiple ORMs in order to let the users choose the database management system that suits their needs. Hence, each plugin must be compatible with at least one ORM. Each plugin contains a folder named `queries` in `./plugins/**/api/queries`. A folder must be created for each ORM (eg. `mongoose`) with a file named `mongoose.js` which exports the Mongoose ORM related queries.
The queries are accessible through the `strapi.query()` method, which automatically contains the queries according to the ORM used by the model.
### Example
Mongoose ORM queries definition:
**Path —** `./plugins/my-plugin/api/config/queries/mongoose/index.js`.
```js
module.exports = {
getUsers: async (params) => {
return User.find(params);
}
}
```
Bookshelf ORM queries definition:
**Path —** `./plugins/my-plugin/api/config/queries/bookshelf/index.js`.
```js
module.exports = {
getUsers: async (params) => {
return User.fetchAll(params);
}
}
```
Usage from the plugin:
**Path —** `./plugins/my-plugin/api/controllers/index.js`.
```js
module.exports = {
getUsers: async () => {
// Get parameters from the request
const { limit, sort } = ctx.request.query;
// Get the list of users using the plugins queries
const users = await strapi.query('User').getUsers({ limit, sort });
// Send the list of users as response
ctx.body = users;
}
}
```
### Advanced usage
Each function in the query file is bound with the ORM's model. It means that you can create generic query very easily. This feature is useful for CRUD such as we did in the [Content Manager plugin](https://github.com/strapi/strapi/tree/master/packages/strapi-plugin-content-manager/config/queries).
Mongoose ORM generic queries:
**Path —** `./plugins/my-plugin/api/config/queries/mongoose/index.js`.
```js
module.exports = {
getAll: async function (params) {
// this refers to the Mongoose model called in the query
// ex: strapi.query('User').getAll(), this will be equal to the User Mongoose model.
return this.find(params);
}
}
```
Bookshelf ORM generic queries:
**Path —** `./plugins/my-plugin/api/config/queries/bookshelf/index.js`.
```js
module.exports = {
getAll: async function (params) {
// this refers to the Bookshelf model called in the query
// ex: strapi.query('User').getAll(), this will be equal to the User Bookshelf model.
return this.fetchAll(params);
}
}
```
Usage from the plugin:
**Path —** `./plugins/my-plugin/api/controllers/index.js`.
```js
module.exports = {
getUsers: async () => {
// Get parameters from the request
const { limit, sort } = ctx.request.query;
// Get the list of users using the plugin's queries
const users = await strapi.query('User').getAll({ limit, sort });
// Send the list of users as response
ctx.body = users;
}
}
```
***

View File

@ -0,0 +1,142 @@
# Front-end Development
This section explains how to create your plugin interface in the admin panel. Refer to the Plugin Development [Quick Start Section](./quick-start.md) to start the project in development mode.
## Introduction
Strapi's admin panel and plugins system aim to be an easy and powerful way to create new features.
The admin panel is a [React](https://facebook.github.io/react/) application which can embed other React applications. These other React applications are the `admin` parts of each Strapi's plugins.
## Routing
The routing is based on the [React Router V4](https://reacttraining.com/react-router/web/guides/philosophy), due to it's implementation each route is declared in the `containers/App/index.js` file.
Also, we chose to use the [Switch Router](https://reacttraining.com/react-router/web/api/Switch) because it renders a route exclusively.
**Route declaration :**
Let's say that you want to create a route `/user` with params `/:id` associated with the container UserPage.
The declaration would be as followed :
**Path —** `plugins/my-plugin/admin/src/containers/App/index.js`.
```js
import React from 'react';
import UserPage from 'containers/UserPage';
// ...
class App extends React.Component {
// ...
render() {
return (
<div className={styles.myPlugin}>
<Switch>
<Route exact path="/plugins/my-plugin/user/:id" component={UserPage} />
</Switch>
</div>
);
}
}
// ...
```
See the [Front-end Use Cases](./frontend-use-cases.md#handle-user-navigation) for more informations.
## Data flow
Each plugin has its own data store, so it stays completely independent from the others.
Data flow is controlled thanks to Redux and redux-sagas.
## Styling
The [Bootstrap styles](http://getbootstrap.com/) are inherited by the plugins. However, each component has its own styles, so it possible to completely customize it.
**See the [plugin styles](../concepts/concepts.md#plugin-styles) for informations on its concept.**
To style a plugin component:
- Add a `styles.scss` file in the component directory
- Require it from the `index.js` file (`import styles from './styles.scss';`)
- Add some styles in the `styles.scss` file
```
.wrapper {
display: block;
background: red;
height: 100px;
width: 100px;
}
```
Use this style in the component: `<div className={styles.wrapper}></div>`.
Note: if you want to use several classes:
```js
import cn from 'classnames';
import styles from './styles.scss';
// ...
return (
<div className={cn(styles.wrapper, styles.otherClass)}>{this.props.children}</div>
);
// ...
```
## i18n
[React Intl](https://github.com/yahoo/react-intl) provides React components and an API to format dates, numbers, and strings, including pluralization and handling translations.
**Usage**
We recommend to set all your components text inside the translations folder.
The example below shows how to use i18n inside your plugin.
**Define all your ids with the associated message:**
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
```json
{
"notification.error.message": "An error occurred"
}
```
**Path —** `./plugins/my-plugin/admin/src/translations/fr.json`
```json
{
"notification.error.message": "Une erreur est survenue"
}
```
**Usage inside a component**
**Path —** `./plugins/my-plugin/admin/src/components/Foo/index.js`.
```js
import { FormattedMessage } from 'react-intl';
import SomeOtherComponent from 'components/SomeOtherComponent';
const Foo = (props) => (
<div className={styles.foo}>
<FormattedMessage id="my-plugin.notification.error.message" />
<SomeOtherComponent {...props} />
</div>
)
export default Foo;
```
See [the documentation](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage) for more extensive usage.
## Generators
You can use generators to create React components or containers for your plugin.
1. In your terminal go to your plugin folder `cd plugins/my-plugin`
2. Run `npm run generate` and choose the type of component your want to create

View File

@ -1,3 +1,7 @@
# Front-end Use Cases
This section gives use cases examples on front-end plugin development.
# Plugin advanced usage
This section contains advanced resources to develop plugins.
@ -321,8 +325,6 @@ Just by doing so, the `another-plugin` will add a `Button` which toggles the `lo
## Routeless container store injection
See the basic container's store injection [documentation](./development.md#using-redux-sagas).
If you have a container which can be a child of several other containers (i.e. it doesn't have a route); you'll have to inject it directly in the `./plugins/my-plugin/admin/src/containers/App/index.js` file as follows :
**Path —** `./plugins/my-plugin/admin/src/containers/App/index.js`.
@ -383,7 +385,7 @@ export default compose(
***
## Execute a logic before mounting
## Execute logic before mounting the plugin
You can execute a business logic before your plugin is being mounted.
@ -416,7 +418,7 @@ export default bootstrap;
***
## Prevent rendering
## Prevent plugin rendering
You can prevent your plugin from being rendered if some conditions aren't met.

View File

@ -0,0 +1,38 @@
# Plugin Folders and Files Architecture
The logic of a plugin is located at his root directory `./plugins/**`. The admin panel related parts of each plugin is contained in the `/admin` folder.
The folders and files structure is the following:
```
/plugin
└─── admin // Contains the plugin's front-end
| └─── build // Webpack build of the plugin
| └─── src // Source code directory
| └─── bootstrap.js // (Optional) Contains the logic to execute before rendering the plugin
| └─── components // Contains the list of React components used by the plugin
| └─── containers
| | └─── App // Container used by every others containers
| | └─── HomePage
| | └─── action.js // List of Redux actions used by the current container
| | └─── constants.js // List of actions constants
| | └─── index.js // React component of the current container
| | └─── reducer.js // Redux reducer used by the current container
| | └─── sagas.js // List of sagas functions
| | └─── selectors.js // List of selectors
| | └─── styles.scss // Style of the current container
| |
| └─── requirements.js // (Optional) Contains the logic to prevent a plugin from being rendered
| └─── translations // Contains the translations to make the plugin internationalized
| └─── en.json
| └─── fr.json
└─── config // Contains the configurations of the plugin
| └─── functions
| | └─── bootstrap.js // Asynchronous bootstrap function that runs before the app gets started
| └─── policies // Folder containing the plugin's policies
| └─── queries // Folder containing the plugin's models queries
| └─── routes.json // Contains the plugin's API routes
└─── controllers // Contains the plugin's API controllers
└─── middlewares // Contains the plugin's middlewares
└─── models // Contains the plugin's API models
└─── services // Contains the plugin's API services
```

View File

@ -0,0 +1,29 @@
# Quick start
To facilitate the development of a plugin, we drastically reduce the amount of commands necessary to install the entire development environment. Before getting started, you need to have Node.js (v8) and npm (v5) installed.
## Development Environment Setup
To setup the development environment please **follow the instructions below:**
1. [Fork the repository](https://github.com/strapi/strapi) to your own GitHub account.
2. Clone it to your computer `git clone git@github.com:strapi/strapi.git`.
3. Run `npm run setup` at the root of the directory.
> Note: If the installation failed, please remove the global packages related to Strapi. The command `npm ls strapi` will help you to find where your packages are installed globally.
## Plugin development Setup
Create a development project
1. Go to a folder on your computer `cd /path/to/my/folder`.
2. Create a new project `strapi new myDevelopmentProject --dev`.
To generate a new plugin **run the following commands:**
1. In your project folder `cd myDevelopmentProject && strapi generate:plugin my-plugin`.
2. Link the `strapi-helper-plugin` dependency in your project folder `cd pathToMyProject/myDevelopmentProject/plugins/my-plugin && npm link strapi-helper-plugin`.
3. Start the server in the admin folder `cd pathToMyProject/myDevelopmentProject/admin && npm start` and go to the following url [http://localhost:4000/admin](http://localhost:4000/admin).
4. In a new terminal window open at the root of your project launch your Strapi server `strapi start`.
Your are now ready to develop your own plugin and live-test your updates!

View File

@ -84,9 +84,9 @@ export default Foo;
## ExtendComponent
ExtendComponent allows a plugin to injectDesign into another one.
ExtendComponent allows a plugin to inject components into another one.
> Refer to the advanced plugin [documentation](./advanced.md#inject-design) to see how to use it.
> Refer to the use cases [documentation](./frontend-use-cases.md#inject-design) to see how to use it.
***

View File

@ -9,7 +9,7 @@ A request helper is available to handle all requests inside a plugin.
It takes three arguments:
- `requestUrl`: The url we want to fetch.
- `options`: Please refer to this [documentation](https://github.com/github/fetch).
- `true`: This third argument is optional. If true is passed the response will be sent only if the server has restarted check out the [example](#example-with-server-autoReload-watcher).
- `true`: This third argument is optional. If true is passed the response will be sent only if the server has restarted check out the [example](#example-with-server-autoreload-watcher).
### Usage

View File

@ -1,506 +0,0 @@
# Development
Any Strapi plugin can contain two parts: an [API](#plugin-api-development) and a [plugin admin interface](#plugin-admin-interface-development). This section explains how to change each of these two parts after plugin creation, or modify an existing plugin.
<!-- See the [strapi-generate](../cli/CLI.md#strapi-generateplugin) part to check the dedicated Strapi's command line. -->
***
## Back-end
This section explains how the 'backend part' of your plugin works.
Table of contents:
- [Folders and files structure](#folders-and-files-structure)
- [Routes](#routes)
- [CLI](#cli)
- [Controllers](#controllers)
- [Models](#models)
- [Policies](#policies)
- [ORM queries](#orm-queries)
### Folders and files structure
The logic of a plugin is located at his root directory `./plugins/**`. The folders and files structure is the following:
```
/plugin
└─── admin // Contains the plugin's front-end
└─── config // Contains the configurations of the plugin
| └─── routes.json // Contains the plugin's API routes
└─── controllers // Contains the plugin's API controllers
└─── models // Contains the plugin's API models
└─── services // Contains the plugin's API services
```
### Routes
The plugin API routes are defined in the `./plugins/**/config/routes.json` file.
> Please refer to [router documentation](../guides/routing.md) for informations.
**Route prefix**
Each route of a plugin is prefixed by the name of the plugin (eg: `/my-plugin/my-plugin-route`).
To disable the prefix, add the `prefix` attribute to each concerned route, like below:
```json
{
"method": "GET",
"path": "/my-plugin-route",
"handler": "MyPlugin.action",
"prefix": false
}
```
### CLI
The CLI can be used to generate files in the plugins folders.
Please refer to the [CLI documentation](../cli/CLI.md) for more informations.
### Controllers
Controllers contain functions executed according to the requested route.
Please refer to the [Controllers documentation](../guides/controllers.md) for more informations.
### Models
A plugin can have its own models.
##### Table/Collection naming
Sometimes it happens that the plugins inject models that have the same name as yours. Let's take a quick example.
You already have `User` model defining in your `./api/user/models/User.settings.json` API. And you decide to install the `Users & Permissions` plugin. This plugin also contains a `User` model. To avoid the conflicts, the plugins' models are not globally exposed which means you cannot access to the plugin's model like this:
```js
module.exports = {
findUser: async function (params) {
// This `User` global variable will always make a reference the User model defining in your `./api/xxx/models/User.settings.json`.
return await User.find();
}
}
```
Also, the table/collection name won't be `users` because you already have a `User` model. That's why, the framework will automatically prefix the table/collection name for this model with the name of the plugin. Which means in our example, the table/collection name of the `User` model of our plugin `Users & Permissions` will be `users-permissions_users`. If you want to force the table/collection name of the plugin's model, you can add the `collectionName` attribute in your model.
Please refer to the [Models documentation](../guides/models.md) for more informations.
### Policies
#### Global policies
A plugin can also use a globally exposed policy in the current Strapi project.
```json
{
"routes": [
{
"method": "GET",
"path": "/",
"handler": "MyPlugin.index",
"config": {
"policies": [
"global.isAuthenticated"
]
}
}
]
}
```
#### Plugin policies
A plugin can have its own policies, such as adding security rules. For instance, if the plugin includes a policy named `isAuthenticated`, the syntax to use this policy would be:
```json
{
"routes": [
{
"method": "GET",
"path": "/",
"handler": "MyPlugin.index",
"config": {
"policies": [
"plugins.myPlugin.isAuthenticated"
]
}
}
]
}
```
Please refer to the [Policies documentation](../guides/policies.md) for more informations.
### ORM queries
Strapi supports multiple ORMs in order to let the users choose the database management system that suits their needs. Hence, each plugin must be compatible with at least one ORM. Each plugin contains a folder named `queries` in `./plugins/**/api/queries`. A folder must be created for each ORM (eg. `mongoose`) with a file named `mongoose.js` which exports the Mongoose ORM related queries.
The queries are accessible through the `strapi.query()` method, which automatically contains the queries according to the ORM used by the model.
#### Example
Mongoose ORM queries definition:
**Path —** `./plugins/my-plugin/api/config/queries/mongoose/index.js`.
```js
module.exports = {
getUsers: async (params) => {
return User.find(params);
}
}
```
Bookshelf ORM queries definition:
**Path —** `./plugins/my-plugin/api/config/queries/bookshelf/index.js`.
```js
module.exports = {
getUsers: async (params) => {
return User.fetchAll(params);
}
}
```
Usage from the plugin:
**Path —** `./plugins/my-plugin/api/controllers/index.js`.
```js
module.exports = {
getUsers: async () => {
// Get parameters from the request
const { limit, sort } = ctx.request.query;
// Get the list of users using the plugins queries
const users = await strapi.query('User').getUsers({ limit, sort });
// Send the list of users as response
ctx.body = users;
}
}
```
#### Advanced usage
Each function in the query file is bound with the ORM's model. It means that you can create generic query very easily. This feature is useful for CRUD such as we did in the [Content Manager plugin](https://github.com/strapi/strapi/tree/master/packages/strapi-plugin-content-manager/config/queries).
Mongoose ORM generic queries:
**Path —** `./plugins/my-plugin/api/config/queries/mongoose/index.js`.
```js
module.exports = {
getAll: async function (params) {
// this refers to the Mongoose model called in the query
// ex: strapi.query('User').getAll(), this will be equal to the User Mongoose model.
return this.find(params);
}
}
```
Bookshelf ORM generic queries:
**Path —** `./plugins/my-plugin/api/config/queries/bookshelf/index.js`.
```js
module.exports = {
getAll: async function (params) {
// this refers to the Bookshelf model called in the query
// ex: strapi.query('User').getAll(), this will be equal to the User Bookshelf model.
return this.fetchAll(params);
}
}
```
Usage from the plugin:
**Path —** `./plugins/my-plugin/api/controllers/index.js`.
```js
module.exports = {
getUsers: async () => {
// Get parameters from the request
const { limit, sort } = ctx.request.query;
// Get the list of users using the plugin's queries
const users = await strapi.query('User').getAll({ limit, sort });
// Send the list of users as response
ctx.body = users;
}
}
```
***
## Front-end
This section explains how to create your plugin interface in the admin panel.
Table of contents:
- [Development mode](#start-the-project-in-development-mode)
- [Folders and files structure](#folders-and-file-structure)
- [Routing](#routing)
- [Using Redux/sagas](#using-redux-sagas)
- [i18n](#i18n)
- [Styling](#styles)
- [Data flow](#data-flow)
- [API Reference](#api-reference)
- [Tutorial](#tutorial)
### Introduction
Strapi's admin panel and plugins system aim to be an easy and powerful way to create new features.
The admin panel is a [React](https://facebook.github.io/react/) application which can embed other React applications. These other React applications are the `admin` parts of each Strapi's plugins.
### Start the project in development mode
To start the project in development mode, read the [Contributing Guide](https://github.com/strapi/strapi/blob/master/.github/CONTRIBUTING.md).
### Folders and files structure
The admin panel related parts of each plugin is contained in the `./plugins/my-plugin/admin` folder it has the following structure:
```
/admin
└─── build // Webpack build of the plugin
└─── src // Source code directory
| └─── bootstrap.js // (Optional) Contains the logic to execute before rendering the plugin
| └─── components // Contains the list of React components used by the plugin
| └─── containers
| | └─── App // Container used by every others containers
| | └─── HomePage
| | └─── action.js // List of Redux actions used by the current container
| | └─── constants.js // List of actions constants
| | └─── index.js // React component of the current container
| | └─── reducer.js // Redux reducer used by the current container
| | └─── sagas.js // List of sagas functions
| | └─── selectors.js // List of selectors
| | └─── styles.scss // Style of the current container
| |
| └─── requirements.js // (Optional) Contains the logic to prevent a plugin from being rendered
| └─── translations // Contains the translations to make the plugin internationalized
| └─── en.json
| └─── fr.json
└─── package.json // List of the necessary npm dependencies
```
### Routing
The routing is based on the [React Router V4](https://reacttraining.com/react-router/web/guides/philosophy), due to it's implementation each route is declared in the `containers/App/index.js` file.
Also, we chose to use the [Switch Router](https://reacttraining.com/react-router/web/api/Switch) because it renders a route exclusively.
**Route declaration :**
Let's say that you want to create a route `/user` with params `/:id` associated with the container UserPage.
The declaration would be as followed :
**Path —** `plugins/my-plugin/admin/src/containers/App/index.js`.
```js
import React from 'react';
import UserPage from 'containers/UserPage';
// ...
class App extends React.Component {
// ...
render() {
return (
<div className={styles.myPlugin}>
<Switch>
<Route exact path="/plugins/my-plugin/user/:id" component={UserPage} />
</Switch>
</div>
);
}
}
// ...
```
See the [advanced user navigation guide](./advanced.md#handle-user-navigation) for more informations.
### Using Redux sagas
Due to React Router V4 your container's store is not directly injected.
To inject your container's store if it's associated with a route you have to do it manually.
As an example, you created a FooPage container associated with the route `/plugins/my-plugin/bar`, and you want to use redux/action/reducer/sagas.
Your `plugins/my-plugin/admin/src/containers/App/index.js` file will look as follows :
```js
render() => (
<div className={styles.myPlugin}>
<Switch>
<Route exact path="/plugins/my-plugin/bar" component={FooPage} />
</Switch>
</div>
);
```
And the `plugins/my-plugin/admin/src/containers/FooPage/index.js` file will be :
```js
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { bindActionCreators, compose } from 'redux';
import PropTypes from 'prop-types';
// Utils to create your container store
import injectReducer from 'utils/injectReducer';
import injectSaga from 'utils/injectSaga';
import {
foo,
bar,
} from './actions';
import reducer from './reducer';
import saga from './sagas';
import { makeSelectFooPage } from './selectors';
// Styles
import styles from './styles.scss';
export class FooPage extends React.Component {
render() {
return (
<div className={styles.fooPage}>
Awesome container
</div>
);
}
}
FooPage.propTypes = {
fooPage: PropTypes.any,
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
foo,
bar,
},
dispatch
);
}
const mapStateToProps = createStructuredSelector({
fooPage: makeSelectFooPage(),
});
const withConnect = connect(mapStateToProps, mapDispatchToProps);
// This is where you create your container's store
// the key must correspond to your container name in camelCase
const withSagas = injectSaga({ key: 'fooPage', saga });
const withReducer = injectReducer({ key: 'fooPage', reducer });
export default compose(
withReducer,
withSagas,
withConnect,
)(FooPage);
```
Important: see the [advanced container store injection](./advanced.md#routeless-container-store-injection.md) for more informations about how to create your container's store.
### i18n
[React Intl](https://github.com/yahoo/react-intl) provides React components and an API to format dates, numbers, and strings, including pluralization and handling translations.
**Usage**
We recommend to set all your components text inside the translations folder.
The example below shows how to use i18n inside your plugin.
**Define all your ids with the associated message:**
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
```json
{
"notification.error.message": "An error occurred"
}
```
**Path —** `./plugins/my-plugin/admin/src/translations/fr.json`
```json
{
"notification.error.message": "Une erreur est survenue"
}
```
**Usage inside a component**
**Path —** `./plugins/my-plugin/admin/src/components/Foo/index.js`.
```js
import { FormattedMessage } from 'react-intl';
import SomeOtherComponent from 'components/SomeOtherComponent';
const Foo = (props) => (
<div className={styles.foo}>
<FormattedMessage id="my-plugin.notification.error.message" />
<SomeOtherComponent {...props} />
</div>
)
export default Foo;
```
See [the documentation](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage) for more extensive usage.
### Styles
The [Bootstrap styles](http://getbootstrap.com/) are inherited by the plugins. However, each component has its own styles, so it possible to completely customize it.
**See the [plugin styles](../concepts/concepts.md#plugin-styles) for informations on its concept.**
To style a plugin component:
- Add a `styles.scss` file in the component directory
- Require it from the `index.js` file (`import styles from './styles.scss';`)
- Add some styles in the `styles.scss` file
```
.wrapper {
display: block;
background: red;
height: 100px;
width: 100px;
}
```
Use this style from the component: `<div className={styles.wrapper}></div>`.
Note: if you want to use several classes:
```js
import cn from 'classnames';
// ...
return (
<div className={cn(styles.wrapper, styles.otherClass)}>{this.props.children}</div>
);
// ...
```
### Data flow
Each plugin has its own data store, so it stays completely independent from the others.
Data flow is controlled thanks to Redux and redux-sagas.
### API Reference
> Refer to the [plugin registration](../api-reference/reference.md#plugin-registration) for details.
### Tutorial
For more information, try the [Create your first Strapi plugin](http://strapi.io) tutorial.

View File

@ -1,31 +0,0 @@
# Quick start
You can install and uninstall any plugin you want.
## Plugin installation
Look at the [CLI documentation](../cli/CLI.html#strapi-install) to install plugin.
### Basic usage
Considering you want to install a plugin named `content-manager` you can run the following command:
```bash
strapi install content-manager
```
> Note: This implies that this plugin is published on the npm registry as `strapi-plugin-content-manager`.
***
## Plugin uninstallation
Look at the [CLI documentation](../cli/CLI.html#strapi-uninstall) to install plugin.
### Basic usage
This command will simply removes the plugin folder.
```bash
strapi uninstall content-manager
```
Please refer to the [CLI documentation](../cli/CLI.md) for more information.

View File

@ -0,0 +1 @@
<svg width="28" height="26" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M8.453 12.772c-1.54.047-2.799.656-3.778 1.824h-1.91c-.779 0-1.435-.192-1.967-.577C.266 13.634 0 13.071 0 12.33c0-3.354.59-5.032 1.768-5.032.057 0 .263.1.62.3.356.2.82.401 1.39.605.57.205 1.135.307 1.696.307.636 0 1.268-.11 1.896-.328a7.106 7.106 0 0 0-.072.94c0 1.322.385 2.538 1.155 3.65zm15.266 9.08c0 1.14-.347 2.04-1.04 2.701-.694.66-1.616.99-2.766.99H7.455c-1.15 0-2.072-.33-2.765-.99-.694-.66-1.04-1.56-1.04-2.701 0-.504.016-.995.049-1.475.033-.48.1-.998.2-1.554s.225-1.072.377-1.547c.152-.475.357-.938.613-1.39.257-.45.551-.836.884-1.154a3.718 3.718 0 0 1 1.219-.763 4.28 4.28 0 0 1 1.59-.285c.094 0 .298.102.612.307.314.204.66.432 1.04.684.38.252.89.48 1.526.684a6.264 6.264 0 0 0 1.924.307c.646 0 1.288-.103 1.925-.307.636-.204 1.145-.432 1.525-.684.38-.252.727-.48 1.04-.684.314-.205.518-.307.613-.307.58 0 1.11.095 1.59.285.48.19.886.445 1.218.763.333.318.628.703.884 1.155.257.45.461.914.613 1.39.152.474.278.99.378 1.546.1.556.166 1.074.2 1.554.033.48.05.971.05 1.475zm3.65-9.522c0 .741-.267 1.304-.799 1.69-.532.384-1.188.576-1.967.576h-1.91c-.979-1.168-2.238-1.777-3.777-1.824.77-1.112 1.154-2.328 1.154-3.65 0-.275-.024-.588-.071-.94.627.219 1.259.328 1.896.328.56 0 1.126-.102 1.696-.307a9.374 9.374 0 0 0 1.39-.605c.356-.2.563-.3.62-.3 1.178 0 1.767 1.678 1.767 5.032z" fill="#553D38"/><path d="M9.123 3.65a3.516 3.516 0 0 1-1.07 2.58 3.516 3.516 0 0 1-2.58 1.068 3.516 3.516 0 0 1-2.58-1.069 3.516 3.516 0 0 1-1.068-2.58c0-1.007.356-1.867 1.069-2.58A3.516 3.516 0 0 1 5.474 0C6.48 0 7.34.356 8.054 1.07a3.516 3.516 0 0 1 1.069 2.58zm10.035 5.473c0 1.51-.535 2.8-1.604 3.87-1.069 1.069-2.359 1.603-3.87 1.603-1.51 0-2.8-.534-3.87-1.603-1.069-1.07-1.603-2.36-1.603-3.87 0-1.511.534-2.801 1.603-3.87 1.07-1.07 2.36-1.604 3.87-1.604 1.511 0 2.801.535 3.87 1.604 1.07 1.069 1.604 2.359 1.604 3.87zm6.386-5.474a3.516 3.516 0 0 1-1.07 2.58 3.516 3.516 0 0 1-2.58 1.07 3.516 3.516 0 0 1-2.58-1.07 3.516 3.516 0 0 1-1.068-2.58c0-1.007.356-1.867 1.069-2.58A3.516 3.516 0 0 1 21.895 0c1.007 0 1.867.356 2.58 1.07a3.516 3.516 0 0 1 1.069 2.58z" fill="#EBBF8E"/><path d="M12.496 19.991h2.393v-.897c0-.33-.117-.612-.35-.846a1.153 1.153 0 0 0-.847-.35c-.33 0-.612.116-.846.35-.233.234-.35.516-.35.846v.897zm3.889.45v2.691c0 .125-.044.231-.131.318a.433.433 0 0 1-.318.131h-4.487a.433.433 0 0 1-.318-.13.433.433 0 0 1-.131-.319V20.44c0-.124.044-.23.13-.318a.433.433 0 0 1 .319-.13h.15v-.898c0-.573.205-1.066.616-1.477A2.015 2.015 0 0 1 13.692 17c.574 0 1.066.206 1.477.617.412.411.617.904.617 1.477v.897h.15c.125 0 .23.044.318.131.087.088.13.194.13.318z" fill="#FFF"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1 @@
<svg width="35" height="24" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M1.148 20.03c.567-7.141 2.974-7.675 7.222-1.604 6.372 9.107 7.533-.667 9.19-2.017" stroke="#FFD2B6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g fill-rule="nonzero"><path d="M21.313 16.48l1.081-1.082-2.792-2.792-1.081 1.081v1.271h1.52v1.521h1.272zm6.214-11.027c0-.174-.087-.26-.262-.26a.275.275 0 0 0-.202.082l-6.44 6.44a.275.275 0 0 0-.082.202c0 .174.087.261.261.261.08 0 .147-.028.202-.083l6.44-6.44a.275.275 0 0 0 .083-.202zm-.642-2.28l4.943 4.942L21.943 18H17v-4.943l9.885-9.885zM35 4.312c0 .42-.147.776-.44 1.07l-1.972 1.971-4.942-4.942 1.972-1.96A1.412 1.412 0 0 1 30.688 0c.419 0 .78.15 1.08.451l2.792 2.78c.293.31.44.67.44 1.082z" fill="#181D29"/><path d="M35 4.313c0 .42-.147.776-.44 1.07l-1.972 1.971-4.942-4.942 1.972-1.96A1.412 1.412 0 0 1 30.688 0c.419 0 .78.15 1.08.451l2.792 2.78c.293.31.44.67.44 1.082z" fill="#FF84B3"/></g></g></svg>

After

Width:  |  Height:  |  Size: 980 B

View File

@ -0,0 +1 @@
<svg width="22" height="22" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M8.604.607c.317.623.75 1.155 1.298 1.597.549.443 1.16.754 1.835.934l.012.873c.032 1.745-.494 3.166-1.579 4.264-1.084 1.098-2.5 1.647-4.247 1.647-1 0-1.885-.19-2.657-.571a4.852 4.852 0 0 1-1.858-1.567 7.325 7.325 0 0 1-1.055-2.25A9.927 9.927 0 0 1 0 2.832l.5.369c.276.205.528.387.755.547.228.16.468.309.72.448.251.14.438.21.56.21.333 0 .557-.152.67-.455a6.33 6.33 0 0 1 .701-1.383c.264-.381.547-.692.847-.934.3-.242.658-.436 1.073-.584A6.547 6.547 0 0 1 7.08.736c.422-.062.93-.105 1.523-.13z" id="a"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 12)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#EABE8D" fill-rule="nonzero" xlink:href="#a"/><path d="M10.18.264c4.086 6.335 2.315 9.584-5.314 9.747-7.629.163-6.185-1.797 4.331-5.88l.982-3.867z" fill="#DB1818" mask="url(#b)"/><path d="M10.564.16C11.422 6.26 8.9 9.507 2.998 9.904c-5.902.397-4.484-1.392 4.255-5.367L10.563.16z" fill="#F1D520" mask="url(#b)"/><path d="M10.196-1.556C9.168 5.281 6.272 8.728 1.507 8.786c-4.764.059-3.209-1.83 4.668-5.663l4.021-4.679z" fill="#32B167" mask="url(#b)"/><path d="M9.506-4.516C9.417 2.713 6.99 6.357 2.226 6.414-2.539 6.474-.784 4.262 7.49-.22l2.016-4.295z" fill="#1279EA" mask="url(#b)"/><path d="M9.449-4.392C8.72 1.871 5.974 5.032 1.209 5.09c-4.764.058-3.398-1.712 4.099-5.311l4.14-4.17z" fill="#4C2DCE" mask="url(#b)"/></g><path d="M19.88 0c.564 0 1.058.186 1.482.56.424.372.635.84.635 1.401 0 .505-.181 1.111-.544 1.817-2.679 5.045-4.555 8.06-5.628 9.047-.783.73-1.662 1.095-2.638 1.095-1.017 0-1.89-.37-2.62-1.113-.73-.742-1.096-1.622-1.096-2.64 0-1.027.371-1.877 1.113-2.551L18.306.65c.476-.433 1-.65 1.573-.65z" fill-rule="nonzero" fill="#553D38"/></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg width="54" height="34" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g fill-rule="nonzero"><path d="M11.392 30.3a1.797 1.797 0 0 0-.22-1.409 1.797 1.797 0 0 0-1.141-.857 1.797 1.797 0 0 0-1.41.22c-.449.27-.734.65-.857 1.142-.122.492-.049.962.22 1.41.27.449.65.734 1.142.857.491.122.961.049 1.41-.22.448-.27.734-.65.856-1.142zm21.224-7.353L8.464 37.459c-.874.525-1.812.663-2.813.413-.983-.245-1.756-.809-2.318-1.692L1.09 32.37c-.548-.86-.695-1.8-.44-2.82.249-1.002.822-1.772 1.72-2.311l24.117-14.492a14.885 14.885 0 0 0 2.02 5.728 14.885 14.885 0 0 0 4.108 4.472z" fill="#181D29"/><path d="M53.663 15.097c-.184.737-.65 1.684-1.401 2.842-1.52 2.31-3.587 3.978-6.2 5.003-2.615 1.024-5.254 1.204-7.918.54-3.497-.872-6.177-2.86-8.043-5.965-1.865-3.105-2.362-6.405-1.49-9.901.871-3.496 2.86-6.177 5.964-8.043 3.105-1.865 6.405-2.362 9.901-1.49 1.096.273 2.205.715 3.328 1.326 1.122.611 2.028 1.304 2.718 2.078.251.283.336.586.256.907-.08.321-.297.548-.651.68l-9.5 2.72-1.584 6.35 4.715 4.397c.109-.033.97-.305 2.582-.816 1.613-.511 3.084-.957 4.414-1.339 1.33-.38 2.08-.55 2.25-.508.283.071.481.22.595.45.113.229.135.485.064.769z" fill="#A1A1A1"/></g><path stroke="#C6C6C6" fill="#D8D8D8" d="M46.521 5.425l3.657 3.41-1.125 4.872-4.781 1.461-3.657-3.41 1.125-4.871z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -13,6 +13,10 @@ import { FormattedMessage } from 'react-intl';
import Ico from 'components/Ico';
import ListRow from 'components/ListRow';
import PopUpWarning from 'components/PopUpWarning';
import IconAuth from 'assets/icons/icon_auth-permissions.svg';
import IconCtb from 'assets/icons/icon_content-type-builder.svg';
import IconCm from 'assets/icons/icon_content-manager.svg';
import IconSettings from 'assets/icons/icon_settings-manager.svg';
import styles from './styles.scss';
@ -30,13 +34,35 @@ class Row extends React.Component {
this.props.onDeleteClick(e);
}
renderImg = () => {
switch (this.props.plugin.name) {
case 'Auth & Permissions':
return <img src={IconAuth} alt="logo" />;
case 'Content Manager':
return <img src={IconCm} alt="logo" />;
case 'Settings Manager':
return <img src={IconSettings} alt="logo" />;
case 'Content Type Builder':
return <img src={IconCtb} alt="logo" />;
default:
}
}
render() {
const pluginIcon = this.props.plugin.name !== 'Email' ? (
<div className={styles.frame} style={{ marginRight: '30px' }}>
<span className={styles.helper} />{this.renderImg()}
</div>
) : (
<div className={styles.icoContainer} style={{ marginRight: '30px' }}>
<i className={`fa fa-${this.props.plugin.icon}`} />
</div>
);
return (
<ListRow>
<div className={cn("col-md-11", styles.nameWrapper)}>
<div className={styles.icoContainer} style={{ marginRight: '30px' }}>
<i className={`fa fa-${this.props.plugin.icon}`} />
</div>
{pluginIcon}
<div className={styles.pluginContent}>
<span>{this.props.plugin.name} &nbsp;</span>
<FormattedMessage id={this.props.plugin.description} />

View File

@ -31,3 +31,23 @@
display: flex;
justify-content: flex-end;
}
.frame {
width: 70px;
height: 36px;
margin: auto 0;
text-align: center;
border: 1px solid rgba(28,93,231,0.1);
border-radius: 3px;
white-space: nowrap;
> img {
max-height: 36px;
vertical-align: baseline;
}
}
.helper {
display: inline-block;
height: 100%;
vertical-align: middle;
}

View File

@ -233,7 +233,11 @@ function parseTarget(target, scope, cb) {
// try requiring `strapi-generate-<module>` to get the core generator.
if (!subGenerator && !module.match(/^strapi-generate-/)) {
try {
subGenerator = require(path.resolve(process.mainModule.paths[1], 'strapi-generate-' + module));
if (process.mainModule.filename.indexOf('yarn') !== -1) {
subGenerator = require(path.resolve(process.mainModule.paths[2], 'strapi-generate-' + module));
} else {
subGenerator = require(path.resolve(process.mainModule.paths[1], 'strapi-generate-' + module));
}
} catch (e1) {
requireError = e1;
}

View File

@ -95,7 +95,9 @@ module.exports = strapi => {
charset: _.get(connection.settings, 'charset'),
schema: _.get(connection.settings, 'schema') || 'public',
port: _.get(connection.settings, 'port'),
socket: _.get(connection.settings, 'socketPath')
socket: _.get(connection.settings, 'socketPath'),
ssl: _.get(connection.settings, 'ssl') || false
},
debug: _.get(connection.options, 'debug') || false,
acquireConnectionTimeout: _.get(connection.options, 'acquireConnectionTimeout'),

View File

@ -44,13 +44,9 @@ module.exports = function (strapi) {
// Connect to mongo database
if (_.isEmpty(username) || _.isEmpty(password)) {
instance.connect(`mongodb://${host}:${port}/${database}`, {
useMongoClient: true
});
instance.connect(`mongodb://${host}:${port}/${database}`);
} else {
instance.connect(`mongodb://${username}:${password}@${host}:${port}/${database}`, {
useMongoClient: true
});
instance.connect(`mongodb://${username}:${password}@${host}:${port}/${database}`);
}
// Handle error
@ -251,7 +247,7 @@ module.exports = function (strapi) {
const FK = _.find(definition.associations, {alias: name});
const ref = details.plugin ? strapi.plugins[details.plugin].models[details.model].globalId : strapi.models[details.model].globalId;
if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne') {
if (FK && FK.nature !== 'oneToOne' && FK.nature !== 'manyToOne' && FK.nature !== 'oneWay') {
definition.loadedModel[name] = {
type: 'virtual',
ref,

View File

@ -5,11 +5,14 @@
*/
module.exports = mongoose => {
require('mongoose-double')(mongoose);
require('mongoose-float').loadType(mongoose);
const SchemaTypes = mongoose.Schema.Types;
SchemaTypes.Decimal.prototype.cast = function (value) {
return value.toString();
};
return {
convertType: mongooseType => {
switch (mongooseType.toLowerCase()) {
@ -22,9 +25,9 @@ module.exports = mongoose => {
case 'biginteger':
return 'Number';
case 'float':
return SchemaTypes.Float;
return 'Float';
case 'decimal':
return SchemaTypes.Double;
return 'Decimal';
case 'date':
case 'time':
case 'datetime':

View File

@ -16,8 +16,7 @@
"main": "./lib",
"dependencies": {
"lodash": "^4.17.4",
"mongoose": "^4.11.10",
"mongoose-double": "0.0.1",
"mongoose": "^5.0.0-rc1",
"mongoose-float": "^1.0.2",
"pluralize": "^6.0.0",
"strapi-utils": "3.0.0-alpha.7.3"

View File

@ -35,6 +35,7 @@ class EditFormRelations extends React.Component { // eslint-disable-line react/p
const relations = map(currentSchema.relations, (relation, i) => {
switch (relation.nature) {
case 'oneWay':
case 'oneToOne':
case 'manyToOne':
if (relation.dominant) {

View File

@ -159,7 +159,7 @@ export class Edit extends React.Component {
if (isObject(e.target.value) && e.target.value._isAMomentObject === true) {
formattedValue = moment(e.target.value, 'YYYY-MM-DD HH:mm:ss').format();
} else if (['float', 'integer', 'bigint'].indexOf(currentSchema.fields[e.target.name].type) !== -1) {
} else if (['float', 'integer', 'biginteger', 'decimal'].indexOf(currentSchema.fields[e.target.name].type) !== -1) {
formattedValue = toNumber(e.target.value);
}

View File

@ -77,6 +77,10 @@ module.exports = {
acc[current] = params.values[current];
} else {
switch (association.nature) {
case 'oneWay':
acc[current] = _.get(params.values[current], this.primaryKey, params.values[current]) || null;
break;
case 'oneToOne':
if (response[current] !== params.values[current]) {
const value = _.isNull(params.values[current]) ? response[current] : params.values;

View File

@ -58,6 +58,10 @@ module.exports = {
acc[current] = params.values[current];
} else {
switch (association.nature) {
case 'oneWay':
acc[current] = _.get(params.values[current], this.primaryKey, params.values[current]) || null;
break;
case 'oneToOne':
if (response[current] !== params.values[current]) {
const value = _.isNull(params.values[current]) ? response[current] : params.values;

View File

@ -70,7 +70,6 @@ module.exports = {
// Create an entry using `queries` system
ctx.body = await strapi.plugins['content-manager'].services['contentmanager'].add(ctx.params, ctx.request.body, source);
} catch(error) {
console.log(error);
ctx.badRequest(null, ctx.request.admin ? [{ messages: [{ id: error.message, field: error.field }] }] : error.message);
}
},

View File

@ -51,7 +51,7 @@ module.exports = {
params.target = relation.model || relation.collection;
params.key = relation.via;
params.nature = relation.nature;
params.targetColumnName = _.get(strapi.models[params.target].attributes[params.key], 'columnName', '');
params.targetColumnName = _.get((params.plugin ? strapi.plugins[params.plugin].models : strapi.models )[params.target].attributes[params.key], 'columnName', '');
}
attributes.push({
@ -106,7 +106,7 @@ module.exports = {
// Retrieve where is located the model.
// Note: The target is not found when we are creating a new API. That's why, we are returning the lowercased model.
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), model.toLowerCase()))[0] || model.toLowerCase();
.filter(x => _.includes(Object.keys(_.get((plugin ? strapi.plugins : strapi.api)[x], 'models', [])), model.toLowerCase()))[0] || model.toLowerCase();
// Retrieve the filename of the model.
const filename = fs.readdirSync(plugin ? path.join(strapi.config.appPath, 'plugins', target, 'models') : path.join(strapi.config.appPath, 'api', target, 'models'))
@ -123,7 +123,7 @@ module.exports = {
const attrs = {};
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), name))[0] || name.toLowerCase();
.filter(x => _.includes(Object.keys(_.get((plugin ? strapi.plugins : strapi.api)[x], 'models', [])), name))[0] || name.toLowerCase();
const model = (plugin ? _.get(strapi.plugins, [target, 'models', name]) : _.get(strapi.api, [target, 'models', name])) || {};
@ -209,7 +209,7 @@ module.exports = {
if (!_.isEmpty(relationsToDelete)) {
// Retrieve where is located the model.
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), name))[0];
.filter(x => _.includes(Object.keys(_.get((plugin ? strapi.plugins : strapi.api)[x], 'models', [])), name))[0];
// Retrieve the filename of the model.
const filename = fs.readdirSync(plugin ? path.join(strapi.config.appPath, 'plugins', target, 'models') : path.join(strapi.config.appPath, 'api', target, 'models'))
@ -280,7 +280,7 @@ module.exports = {
if (!_.isEmpty(relationsToCreate)) {
// Retrieve where is located the model.
const target = Object.keys((plugin ? strapi.plugins : strapi.api) || {})
.filter(x => _.includes(Object.keys((plugin ? strapi.plugins : strapi.api)[x].models), name))[0];
.filter(x => _.includes(Object.keys(_.get((plugin ? strapi.plugins : strapi.api)[x], 'models', [])), name))[0];
// Retrieve the filename of the model.
const filename = fs.readdirSync(plugin ? path.join(strapi.config.appPath, 'plugins', target, 'models') : path.join(strapi.config.appPath, 'api', target, 'models'))

View File

@ -6,7 +6,7 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { findIndex, has, includes, isEmpty, map, toLower, upperFirst } from 'lodash';
import { findIndex, has, includes, isEmpty, map, toLower } from 'lodash';
import cn from 'classnames';
import PropTypes from 'prop-types';
@ -77,7 +77,7 @@ class InputSearch extends React.Component { // eslint-disable-line react/prefer-
return (
<div className={cn(styles.inputSearch, 'col-md-6')}>
<label htmlFor={this.props.name}>
<FormattedMessage id={this.props.label} values={upperFirst(this.props.labelValues)} />
<FormattedMessage id={this.props.label} values={this.props.labelValues} />
</label>
<div className={cn('input-group')}>
<span className={cn('input-group-addon', styles.addon)} />

View File

@ -10,18 +10,19 @@ module.exports = async (ctx, next) => {
ctx.state.user = await strapi.plugins['users-permissions'].services.user.fetch(_.pick(tokenUser, ['_id', 'id']));
if (!ctx.state.user) {
ctx.unauthorized('This user doesn\'t exit.');
}
role = ctx.state.user.role;
if (role.toString() === '0') {
return await next();
}
} catch (err) {
return ctx.unauthorized(err);
}
if (!ctx.state.user) {
return ctx.unauthorized('This user doesn\'t exit.');
}
role = ctx.state.user.role;
if (role.toString() === '0') {
return await next();
}
}
const permission = _.get(strapi.plugins['users-permissions'].config, ['roles', role.toString(), 'permissions', route.plugin || 'application', 'controllers', route.controller, route.action]);

View File

@ -77,7 +77,11 @@ module.exports = function (plugin, cliArguments) {
fs.accessSync(path.join(pluginPath, '.gitignore'))
} catch (err) {
if (err.code === 'ENOENT') {
fs.copySync(path.resolve(__dirname, '..', 'node_modules', 'strapi-generate-plugin', 'templates', 'gitignore'), path.join(pluginPath, '.gitignore'));
if (process.mainModule.filename.indexOf('yarn') !== -1) {
fs.copySync(path.resolve(__dirname, '..', '..', 'strapi-generate-plugin', 'templates', 'gitignore'), path.join(pluginPath, '.gitignore'));
} else {
fs.copySync(path.resolve(__dirname, '..', 'node_modules', 'strapi-generate-plugin', 'templates', 'gitignore'), path.join(pluginPath, '.gitignore'));
}
}
}

View File

@ -78,14 +78,14 @@ module.exports = {
const aggregate = files.filter(
p =>
intersection(p.split('/').map(p => p.replace('.json', '')), ['environments', 'database', 'security', 'request', 'response', 'server']).length === 2 ||
p.indexOf('functions') !== -1 ||
((p.indexOf('functions') !== -1 ||
p.indexOf('policies') !== -1 ||
p.indexOf('locales') !== -1 ||
p.indexOf('hook') !== -1 ||
p.indexOf('middleware') !== -1 ||
p.indexOf('language') !== -1 ||
p.indexOf('queries') !== -1 ||
p.indexOf('layout') !== -1
p.indexOf('layout') !== -1) && p.indexOf('api') === -1)
);
const optional = difference(files, aggregate);

View File

@ -89,4 +89,4 @@
},
"preferGlobal": true,
"license": "MIT"
}
}