Merge branch 'master' into master

This commit is contained in:
Jim LAURIE 2019-12-23 11:04:29 +01:00 committed by GitHub
commit e5456da8eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1048 changed files with 32539 additions and 37194 deletions

View File

@ -90,12 +90,12 @@ The server (API) is available at http://localhost:1337
The built administration panel is available at http://localhost:1337/admin
**Start the administration panel server for development**
**Start the administration panel server for development**
```bash
cd strapi/packages/strapi-admin
yarn develop
```
```bash
cd strapi/packages/strapi-admin
yarn develop
```
The administration panel will be available at http://localhost:4000/admin

View File

@ -72,7 +72,7 @@ Complete installation requirements can be found in the documentation under <a hr
- Ubuntu 18.04/Debian 9.x
- CentOS/RHEL 8
- Mac O/S Mojave
- macOS Mojave
- Windows 10
- Docker - [Docker-Repo](https://github.com/strapi/strapi-docker)

View File

@ -196,11 +196,16 @@ module.exports = {
'/3.0.0-beta.x/guides/deployment',
'/3.0.0-beta.x/guides/process-manager',
'/3.0.0-beta.x/guides/jwt-validation',
'/3.0.0-beta.x/guides/api-token',
'/3.0.0-beta.x/guides/error-catching',
'/3.0.0-beta.x/guides/secure-your-app',
'/3.0.0-beta.x/guides/external-data',
'/3.0.0-beta.x/guides/custom-data-response',
'/3.0.0-beta.x/guides/custom-admin',
'/3.0.0-beta.x/guides/client',
'/3.0.0-beta.x/guides/draft',
'/3.0.0-beta.x/guides/slug',
'/3.0.0-beta.x/guides/send-email',
'/3.0.0-beta.x/guides/webhooks',
],
},

View File

@ -25,6 +25,25 @@ By default, the administration panel is exposed via [http://localhost:1337/admin
}
```
## Change the host
By default, the administration panel client host name is `localhost`. However, you can change this setting by updating the `admin` configuration:
**Path —** `./config/environment/**/server.json`.
```json
{
"host": "localhost",
"port": 1337,
"cron": {
"enabled": false
},
"admin": {
"host": "my-host"
}
}
```
The panel will be available through [http://localhost:1337/dashboard](http://localhost:1337/dashboard) with the configurations above.
## Development mode
@ -170,6 +189,25 @@ Add the following configuration:
export const SHOW_TUTORIALS = false;
```
### Changing the port
By default, the front-development server runs on the `8000` port. However, you can change this setting by updating the following configuration:
**Path —** `./config/environment/**/server.json`.
```json
{
"host": "localhost",
"port": 1337,
"cron": {
"enabled": false
},
"admin": {
"port": 3000
}
}
```
## Build
To build the administration, run the following command from the root directory of your project.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 KiB

After

Width:  |  Height:  |  Size: 887 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 KiB

After

Width:  |  Height:  |  Size: 896 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 956 KiB

After

Width:  |  Height:  |  Size: 894 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 KiB

After

Width:  |  Height:  |  Size: 935 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1022 KiB

After

Width:  |  Height:  |  Size: 1017 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1005 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 KiB

After

Width:  |  Height:  |  Size: 989 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 919 KiB

After

Width:  |  Height:  |  Size: 904 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 983 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 KiB

After

Width:  |  Height:  |  Size: 937 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1024 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 KiB

After

Width:  |  Height:  |  Size: 1005 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1013 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1024 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 960 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 984 KiB

After

Width:  |  Height:  |  Size: 913 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 KiB

After

Width:  |  Height:  |  Size: 1017 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1005 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1002 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1013 KiB

After

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 KiB

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 932 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 KiB

After

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 963 KiB

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 KiB

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 KiB

After

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -2,8 +2,6 @@
Strapi comes with a full featured Command Line Interface (CLI) which lets you scaffold and manage your project in seconds.
---
## strapi new
Create a new project.
@ -245,8 +243,6 @@ strapi generate:plugin <name>
Please refer to the [local plugins](../plugin-development/quick-start.md) section to know more.
---
## strapi install
Install a plugin in the project.
@ -264,8 +260,6 @@ strapi install <name>
Some plugins have admin panel integrations, your admin panel might have to be rebuilt. This can take some time.
:::
---
## strapi uninstall
Uninstall a plugin from the project.
@ -290,7 +284,13 @@ options [--delete-files]
Some plugins have admin panel integrations, your admin panel might have to be rebuilt. This can take some time.
:::
---
## strapi console
Start the server and let you eval commands into your application in real time.
```bash
strapi console
```
## strapi version
@ -300,8 +300,6 @@ Print the current globally installed Strapi version.
strapi version
```
---
## strapi help
List CLI commands.

View File

@ -57,7 +57,7 @@ These configurations are accessible through `strapi.config.backendURL` and `stra
The `./config/functions/` folder contains a set of JavaScript files in order to add dynamic and logic based configurations.
All functions that are expose in this folder or in your `./config` folder are accessible via `strapi.config.functions['fileName']();`
All functions that are exposed in this folder or in your `./config` folder are accessible via `strapi.config.functions['fileName']();`
### Bootstrap
@ -203,7 +203,7 @@ You can access the config of the current environment through `strapi.config.curr
- `defaultConnection` (string): Connection by default for models which are not related to a specific `connection`. Default value: `default`.
- `connections` List of all available connections.
- `default`
- `connector` (string): Connector used by the current connection. Will be `strapi-hook-bookshelf`.
- `connector` (string): Connector used by the current connection. Will be `bookshelf`.
- `settings` Useful for external session stores such as Redis.
- `client` (string): Database client to create the connection. `sqlite` or `postgres` or `mysql`.
- `host` (string): Database host name. Default value: `localhost`.
@ -234,7 +234,7 @@ You can access the config of the current environment through `strapi.config.curr
- `defaultConnection` (string): Connection by default for models which are not related to a specific `connection`. Default value: `default`.
- `connections` List of all available connections.
- `default`
- `connector` (string): Connector used by the current connection. Will be `strapi-hook-mongoose`.
- `connector` (string): Connector used by the current connection. Will be `mongoose`.
- `settings` Useful for external session stores such as Redis.
- `client` (string): Database client to create the connection. Will be `mongo`.
- `host` (string): Database host name. Default value: `localhost`.
@ -264,7 +264,7 @@ You can access the config of the current environment through `strapi.config.curr
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"connector": "bookshelf",
"settings": {
"client": "postgres",
"host": "localhost",
@ -291,7 +291,7 @@ You can access the config of the current environment through `strapi.config.curr
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"connector": "bookshelf",
"settings": {
"client": "mysql",
"host": "localhost",
@ -315,7 +315,7 @@ You can access the config of the current environment through `strapi.config.curr
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"connector": "bookshelf",
"settings": {
"client": "sqlite",
"filename": ".tmp/data.db"
@ -337,7 +337,7 @@ You can access the config of the current environment through `strapi.config.curr
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-mongoose",
"connector": "mongoose",
"settings": {
"client": "mongo",
"host": "localhost",
@ -496,7 +496,7 @@ In any JSON configurations files in your project, you can inject dynamic values
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-mongoose",
"connector": "mongoose",
"settings": {
"client": "mongo",
"uri": "${process.env.DATABASE_URI || ''}",

View File

@ -2,13 +2,15 @@
By default, your project's structure will look like this:
- `/.cache`: contains files used to build your admin panel.
- [`/admin`](../admin-panel/customization.md): contains your admin customization files.
- `/api`: contains the business logic of your project split in sub-folder per API.
- `**`
- `/config`: contains the API's configurations ([`routes`](./routing.md), [`policies`](./policies.md), etc).
- [`/controllers`](./controllers.md): contains the API's custom controllers.
- [`/models`](./models.md): contains the API's models.
- [`/services`](./services.md): contains the API's custom services.
- `/node_modules`: contains the npm's packages used by the project.
- `/build`: contains your admin panel UI build.
- [`/config`](./configurations.md)
- [`/environments`](./configurations.md#environments): contains the project's configurations per environment.
- `/**`
@ -27,11 +29,9 @@ By default, your project's structure will look like this:
- [`custom.json`](./configurations.md#custom): contains the custom configurations of the project.
- [`hook.json`](./configurations.md#hook): contains the hook settings of the project.
- [`middleware.json`](./configurations.md#middleware): contains the middleware settings of the project.
- [`/extensions`](./customization.md): contains the files to extend installed plugins.
- [`/hooks`](./hooks.md): contains the custom hooks of the project.
- [`/middlewares`](./middlewares.md): contains the custom middlewares of the project.
- [`/admin`](../admin-panel/customization.md): contains your admin customization files.
- [`/extensions`](./customization.md): contains the files to extend installed plugins.
- [`/plugins`](./plugins.md): contains your local plugins.
- [`/public`](./public-assets.md): contains the file accessible to the outside world.
- `/build`: contains your admin panel UI build.
- `/.cache`: contains files used to build your admin panel.
- `/node_modules`: contains the npm's packages used by the project.

View File

@ -1,6 +1,6 @@
# Hooks
The hooks are modules that add functionality to the core. They are loaded during the server boot. For example, if your project needs to work with a SQL database, you will have to add the hook `strapi-hook-bookshelf` to be able to connect your app with your database.
The hooks are modules that add functionality to the core. They are loaded during the server boot.
**File structure**
@ -89,25 +89,3 @@ To activate and configure your hook with custom options, you need to edit your `
}
}
```
## Dependencies
It happens that a hook has a dependency to another one. For example, the `strapi-hook-bookshelf` has a dependency to `strapi-hook-knex`. Without it, the `strapi-hook-bookshelf` can't work correctly. It also means that the `strapi-hook-knex` hook has to be loaded before.
To handle this case, you need to update the `package.json` at the root of your hook.
```json
{
"name": "strapi-hook-bookshelf",
"version": "x.x.x",
"description": "Bookshelf hook for the Strapi framework",
"dependencies": {
...
},
"strapi": {
"dependencies": [
"strapi-hook-knex"
]
}
}
```

View File

@ -118,8 +118,8 @@ The info key on the model-json states information about the model. This informat
The options key on the model-json states.
- `idAttribute`: This tells the model which attribute to expect as the unique identifier for each database row (typically an auto-incrementing primary key named 'id'). _Only valid for strapi-hook-bookshelf_
- `idAttributeType`: Data type of `idAttribute`, accepted list of value below. _Only valid for strapi-hook-bookshelf_
- `idAttribute`: This tells the model which attribute to expect as the unique identifier for each database row (typically an auto-incrementing primary key named 'id'). _Only valid for bookshelf_
- `idAttributeType`: Data type of `idAttribute`, accepted list of value below. _Only valid for bookshelf_
- `timestamps`: This tells the model which attributes to use for timestamps. Accepts either `boolean` or `Array` of strings where first element is create date and second element is update date. Default value when set to `true` for Bookshelf is `["created_at", "updated_at"]` and for MongoDB is `["createdAt", "updatedAt"]`.
- `uuid` : Boolean to enable UUID support on MySQL, you will need to set the `idAttributeType` to `uuid` as well and install the `bookshelf-uuid` package. To load the package you can see [this example](./configurations.md#bookshelf-mongoose).
@ -349,7 +349,7 @@ xhr.send(
::: tab "Many-to-Many" id="many-to-many"
One-to-Many relationships are usefull when an entry can be liked to multiple entries of an other Content Type. And an entry of the other Content Type can be linked to many entries.
Many-to-Many relationships are usefull when an entry can be liked to multiple entries of an other Content Type. And an entry of the other Content Type can be linked to many entries.
#### Example

View File

@ -53,7 +53,7 @@ module.exports = {
};
```
### SQL databases (strapi-hook-bookshelf)
### SQL databases (bookshelf)
If you are using a SQL database, calling `buildQuery` will return a [`Bookshelf Query`](https://bookshelfjs.org/api.html) on which you can call other functions (e.g `count`)

View File

@ -4,7 +4,7 @@ Strapi provides a utility function `strapi.query` to make database queries.
You can just call `strapi.query('modelName', 'pluginName')` to access the query API for any model.
These queries handle for you specific Strapi features like `groups`, `filters` and `search`.
These queries handle for you specific Strapi features like `components`, `dynamic zones`, `filters` and `search`.
## API Reference
@ -97,7 +97,20 @@ Creates an entry in the database and returns the entry.
```js
strapi.query('restaurant').create({
name: 'restaurant name',
// this is a group field. the order is persisted in db.
// this is a dynamiczone field. the order is persisted in db.
content: [
{
__component: 'blog.rich-text',
title: 'Some title',
subTitle: 'Some sub title',
},
{
__component: 'blog.quote',
quote: 'Some interesting quote',
author: 1,
},
],
// this is a component field. the order is persisted in db.
opening_hours: [
{
day_interval: 'Mon',
@ -134,6 +147,18 @@ strapi.query('restaurant').update(
{ id: 1 },
{
name: 'restaurant name',
content: [
{
__component: 'blog.rich-text',
title: 'Some title',
subTitle: 'Some sub title',
},
{
__component: 'blog.quote',
quote: 'Some interesting quote',
author: 1,
},
],
opening_hours: [
{
day_interval: 'Mon',
@ -154,15 +179,29 @@ strapi.query('restaurant').update(
);
```
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:
When updating an entry with its components or dynamiczones beware that if you send the components without any `id` the previous components will be deleted and replaced. You can update the components by sending their `id` with the rest of the fields:
**Update by id and update previous groups**
**Update by id and update previous components**
```js
strapi.query('restaurant').update(
{ id: 1 },
{
name: 'Mytitle',
content: [
{
__component: 'blog.rich-text',
id: 1,
title: 'Some title',
subTitle: 'Some sub title',
},
{
__component: 'blog.quote',
id: 1,
quote: 'Some interesting quote',
author: 1,
},
],
opening_hours: [
{
id: 2,

View File

@ -2,25 +2,36 @@
When you create a `Content Type` you will have a certain number of **REST API endpoints** available to interact with it.
As an **example** let's consider the `Restaurant` as a **Content Type** and `Openning_hours` as a **Group** for the next steps.
As an **example** let's consider the following models:
**Content Type**:
- `Restaurant`
**Components**:
- `Openning hours` (category: `restaurant`)
- `Title With Subtitle` (category: `content`)
- `Image With Description` (category: `content`)
:::: tabs
::: tab "Content Type"
::: tab "Content Types"
### `Restaurant` Content Type
| Fields | Type | Description | Options |
| :------------ | :----- | :------------------------------- | :----------- |
| name | string | Restaurant's title | |
| cover | media | Restaurant's cover image | |
| opening_hours | group | Restaurant's opening hours group | `repeatable` |
| Fields | Type | Description | Options |
| :------------ | :---------- | :----------------------------------- | :----------- |
| name | string | Restaurant's title | |
| cover | media | Restaurant's cover image | |
| content | dynamiczone | The restaurant profile content | |
| opening_hours | component | Restaurant's opening hours component | `repeatable` |
:::
::: tab Group
::: tab Components
### `Opening_hours` Group
### `Opening hours` Component
| Fields | Type | Description |
| :----------- | :----- | :------------------ |
@ -28,6 +39,25 @@ As an **example** let's consider the `Restaurant` as a **Content Type** and `Ope
| opening_hour | string | Meta's opening hour |
| closing_hour | string | Meta's closing hour |
---
### `Title With Subtitle` Component
| Fields | Type | Description |
| :------- | :----- | :------------ |
| title | string | The title |
| subTitle | string | the sub title |
---
### `Image With Description` Component
| Fields | Type | Description |
| :---------- | :----- | :------------------- |
| image | media | The image file |
| title | string | the image title |
| description | text | the image description |
:::
::::
@ -195,8 +225,35 @@ GET http://localhost:1337/restaurants
"cover": {
"id": 1,
"url": "/uploads/3d89ba92f762433bbb75bbbfd9c13974.png"
//...
},
"content": [
{
"__component": "content.title-with-subtitle",
"id": 1,
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"id": 1,
"image": {
"id": 1,
"name": "image.png",
"hash": "123456712DHZAUD81UDZQDAZ",
"sha256": "v",
"ext": ".png",
"mime": "image/png",
"size": 122.95,
"url": "http://localhost:1337/uploads/123456712DHZAUD81UDZQDAZ.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2019-12-09T00:00:00.000Z",
"updated_at": "2019-12-09T00:00:00.000Z"
},
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
"id": 1,
@ -204,7 +261,6 @@ GET http://localhost:1337/restaurants
"opening_hour": "7:30 PM",
"closing_hour": "10:00 PM"
}
//...
]
}
]
@ -229,8 +285,35 @@ GET http://localhost:1337/restaurants/1
"cover": {
"id": 1,
"url": "/uploads/3d89ba92f762433bbb75bbbfd9c13974.png"
//...
},
"content": [
{
"__component": "content.title-with-subtitle",
"id": 1,
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"id": 1,
"image": {
"id": 1,
"name": "image.png",
"hash": "123456712DHZAUD81UDZQDAZ",
"sha256": "v",
"ext": ".png",
"mime": "image/png",
"size": 122.95,
"url": "http://localhost:1337/uploads/123456712DHZAUD81UDZQDAZ.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2019-12-09T00:00:00.000Z",
"updated_at": "2019-12-09T00:00:00.000Z"
},
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
"id": 1,
@ -272,6 +355,19 @@ POST http://localhost:1337/restaurants
{
"title": "Restaurant 1",
"cover": 1,
"content": [
{
"__component": "content.title-with-subtitle",
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"image": 1, // user form data to upload the file or an id to reference an exisiting image
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
"day_interval": "Tue - Sat",
@ -291,8 +387,35 @@ POST http://localhost:1337/restaurants
"cover": {
"id": 1,
"url": "/uploads/3d89ba92f762433bbb75bbbfd9c13974.png"
//...
},
"content": [
{
"__component": "content.title-with-subtitle",
"id": 1,
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"id": 1,
"image": {
"id": 1,
"name": "image.png",
"hash": "123456712DHZAUD81UDZQDAZ",
"sha256": "v",
"ext": ".png",
"mime": "image/png",
"size": 122.95,
"url": "http://localhost:1337/uploads/123456712DHZAUD81UDZQDAZ.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2019-12-09T00:00:00.000Z",
"updated_at": "2019-12-09T00:00:00.000Z"
},
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
"id": 1,
@ -318,6 +441,21 @@ PUT http://localhost:1337/restaurants/1
```json
{
"title": "Restaurant 1",
"content": [
{
"__component": "content.title-with-subtitle",
// editing one of the previous item by passing its id
"id": 2,
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"image": 1, // user form data to upload the file or an id to reference an exisiting image
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
// adding a new item
@ -345,8 +483,35 @@ PUT http://localhost:1337/restaurants/1
"cover": {
"id": 1,
"url": "/uploads/3d89ba92f762433bbb75bbbfd9c13974.png"
//...
},
"content": [
{
"__component": "content.title-with-subtitle",
"id": 1,
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"id": 2,
"image": {
"id": 1,
"name": "image.png",
"hash": "123456712DHZAUD81UDZQDAZ",
"sha256": "v",
"ext": ".png",
"mime": "image/png",
"size": 122.95,
"url": "http://localhost:1337/uploads/123456712DHZAUD81UDZQDAZ.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2019-12-09T00:00:00.000Z",
"updated_at": "2019-12-09T00:00:00.000Z"
},
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
"id": 1,
@ -383,8 +548,35 @@ DELETE http://localhost:1337/restaurants/1
"cover": {
"id": 1,
"url": "/uploads/3d89ba92f762433bbb75bbbfd9c13974.png"
//...
},
"content": [
{
"__component": "content.title-with-subtitle",
"id": 1,
"title": "Restaurant 1 title",
"subTitle": "Cozy restaurant in the valley"
},
{
"__component": "content.image-with-description",
"id": 2,
"image": {
"id": 1,
"name": "image.png",
"hash": "123456712DHZAUD81UDZQDAZ",
"sha256": "v",
"ext": ".png",
"mime": "image/png",
"size": 122.95,
"url": "http://localhost:1337/uploads/123456712DHZAUD81UDZQDAZ.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2019-12-09T00:00:00.000Z",
"updated_at": "2019-12-09T00:00:00.000Z"
},
"title": "Amazing photography",
"description": "This is an amazing photography taken..."
}
],
"opening_hours": [
{
"id": 1,

View File

@ -212,9 +212,9 @@ You need to create a new **Content Type** for `Restaurants`.
1. Complete these steps to **Add a Restaurant Content Type**
- Click the `+ Add A Content Type` link (under existing **CONTENT TYPES**)
- Enter a **Name** for your new **Content Type** (call this `restaurant`), and you can write `Restaurant Listings` for the **Description**
- Click the `Done` button
- Click the `+ Create new content-type` link (under existing **CONTENT TYPES**)
- Enter a **Name** for your new **Content Type** (call this `restaurant`)
- Click the `Continue` button
![Singular Name Entries for Content Type](../assets/getting-started/tutorial/singular-name-entry.png 'Singular Name Entries or Content Type')
@ -226,11 +226,11 @@ The Content Type **Name** is always **singular**. For example, `restaurant` not
2. You are now at the **Field Selection** panel
You may add your first field, a **String** field for the **Restaurant** name.
You may add your first field, a **Text** field for the **Restaurant** name.
![Field Section Panel](../assets/getting-started/tutorial/field-selection-panel.png 'Field Selection Panel')
- Click on the `String` field
- Click on the `Text` field
- In the **Name** field, type `name`
![Restaurant Name Input Field](../assets/getting-started/tutorial/restaurant-name-input-field.png 'Restuarant Name Input Field')
@ -241,11 +241,11 @@ You may add your first field, a **String** field for the **Restaurant** name.
![Restaurant Name Advanced Settings](../assets/getting-started/tutorial/restaurant-name-advanced-settings.png 'Restuarant Name Advanced Settings')
- Click the `+ Add Another Field` button
- Click the `+ Add another field` button
You are now ready to add the second field, a **Rich Text** field for the **Restaurant** description.
![Field Section Panel](../assets/getting-started/tutorial/field-selection-panel.png 'Field Selection Panel')
![Field Section Panel](../assets/getting-started/tutorial/field-selection-panel-after-restaurant-name.png 'Field Selection Panel')
- Click the `Rich Text` field
@ -253,11 +253,11 @@ You are now ready to add the second field, a **Rich Text** field for the **Resta
![Restaurant Rich Text Field](../assets/getting-started/tutorial/restaurant-rich-text-field.png 'Restuarant Rich Text Field')
- Click the `+ Add Another Field` button
- Click the `+ Add another field` button
You are now ready to add the third field, a **Media** field for the **Restaurant** thumbnail image.
![Field Section Panel](../assets/getting-started/tutorial/field-selection-panel.png 'Field Selection Panel')
![Field Section Panel](../assets/getting-started/tutorial/field-selection-panel-after-restaurant-description.png 'Field Selection Panel')
- Click the `Media` field
@ -270,7 +270,7 @@ You are now ready to add the third field, a **Media** field for the **Restaurant
![Restaurant Rich Image Field Advanced Settings](../assets/getting-started/tutorial/restaurant-image-field-advanced-settings.png 'Restuarant Rich Image Field Advanced Settings')
- Click the `Done` button
- Click the `Finish` button
Your new Content Type called **Restaurant** is ready to be **Saved**.
@ -280,7 +280,7 @@ Your new Content Type called **Restaurant** is ready to be **Saved**.
- Wait for Strapi to restart
![Strapi Restart](../assets/getting-started/tutorial/strapi-restart.png 'Strapi Restart')
![Strapi Restart](../assets/getting-started/tutorial/restaurant-strapi-restart.png 'Strapi Restart')
After Strapi has restarted, you are ready to continue to create the `Category` **Content Type**.
@ -288,25 +288,24 @@ After Strapi has restarted, you are ready to continue to create the `Category` *
### The Category Content Type
The `Category` **Content Type** will have a **String** field named `category`, and a **Relation field** with a **Many to Many** relationship.
The `Category` **Content Type** will have a **Text** field named `category`, and a **Relation field** with a **Many to Many** relationship.
![Category Add Content Type](../assets/getting-started/tutorial/category-add-content-type.png 'Category Add Content Type')
1. Complete these steps to **add a Category Content Type**:
- Click the `+ Add A Content Type` link
- Click the `+ Create new content-type` link
- Enter a **Name** for your new **Content Type** (call this `category`)
- Enter `Restaurant Categories` for the **Description**
![Category Name Field](../assets/getting-started/tutorial/category-name-field.png 'Category Name Field')
- Click the `Done` button
- Click the `Continue` button
2. Now, you are ready to add fields to your **Category**:
![Category Fields](../assets/getting-started/tutorial/category-fields.png 'Category Fields')
- Click on the `String` field
- Click on the `Text` field
- In the **Name** field, type `name`
![Category Name Entry Field](../assets/getting-started/tutorial/category-name-entry-field.png 'Category Name Entry Fields')
@ -317,13 +316,13 @@ The `Category` **Content Type** will have a **String** field named `category`, a
![Category Advanced Settings](../assets/getting-started/tutorial/category-advanced-settings.png 'Category Advanced Settings')
- Click the `+ Add Another Field` button
- Click the `+ Add another field` button
You are now ready to add the second field, a **Relation** field for creating a **Many to Many** relationship between the **Category** and **Restaurant** Content Types.
- Click on the `Relation` field
![Category Add Field Panel](../assets/getting-started/tutorial/category-add-field-panel.png 'Category Add Field Panel')
![Category Add Field Panel](../assets/getting-started/tutorial/category-add-field-panel-after-category-name.png 'Category Add Field Panel')
This brings you to the **Add New Relation** screen.
@ -337,7 +336,7 @@ This brings you to the **Add New Relation** screen.
![Category Relation Many to Many](../assets/getting-started/tutorial/category-relation-many-to-many.png 'Category Relation Many to Many')
- Click the `Done` button
- Click the `Finish` button
![Category Save](../assets/getting-started/tutorial/category-save.png 'Category Save')
@ -345,30 +344,32 @@ This brings you to the **Add New Relation** screen.
- Wait for Strapi to restart
![Category Save Strapi Restart](../assets/getting-started/tutorial/category-save-strapi-restart.png 'Category Save Strapi Restart')
![Category Save Strapi Restart](../assets/getting-started/tutorial/categor-create-strapi-restart.png 'Category Save Strapi Restart')
After Strapi has restarted, you are ready to create a `Group and Repeatable Field` called **"Hours of Operations."**
After Strapi has restarted, you are ready to create a `Component` called **"Hours of Operations."**
## 5. Create a new Group, and Repeatable Field called, "Hours of Operation"
## 5. Create a new Component called, "Hours of Operation"
### The Hours of Operation Group
### The Hours of Operation Component
The `Restaurant` Content Type has a **Group** field named `Hours_of_operation`. This Group is **Repeatable** and for displaying the **Opening hours** and **Closing hours** of a **Restaurant**.
The `Restaurant` Content Type has a **Component** field named `Hours_of_operation`. This Component is **Repeatable** and for displaying the **Opening hours** and **Closing hours** of a **Restaurant**.
1. Complete these steps to **add a new Group**:
1. Complete these steps to **add a new Component**:
- Click the `+ Add A Group` link to add a new **Group**
- Enter a **Name** for your new **Group** (call this `hours_of_operation`), and you can write `Hours of Operation` for the **Description**
- Click the `+ Create new component` link to add a new **Component**
- Enter a **Name** for your new **Component** (call this `hours_of_operation`)
- Select the icon of your choice
- Create a new category for your **Component** (call it `hours`)
![Hours of Operation Add Group](../assets/getting-started/tutorial/hours-of-operation-add-group.png 'Hours of Operation Add Group')
![Hours of Operation Add Component](../assets/getting-started/tutorial/hours-of-operation-add-compo.png 'Hours of Operation Add Component')
- Click the `Done` button
- Click the `continue` button
2. Now, you are ready to add fields to your **Group**:
2. Now, you are ready to add fields to your **Component**:
![Hours of Operation Add Fields](../assets/getting-started/tutorial/hours-of-operation-add-fields.png 'Hours of Operation Add Fields')
- Click on the `String` field
- Click on the `Text` field
- In the **Name** field, type `day_interval`. This is to enter the **Day (or Days)** with **Hours of Operation**
![Hours of Operation Days](../assets/getting-started/tutorial/hours-of-operation-days.png 'Hours of Operation Days')
@ -378,29 +379,29 @@ The `Restaurant` Content Type has a **Group** field named `Hours_of_operation`.
![Hours of Operation Days Advanced Settings](../assets/getting-started/tutorial/hours-of-operation-days-advanced-settings.png 'Hours of Operation Days Advanced Settings')
- Click the `+ Add Another Field`
- Click the `+ Add another field`
You are now ready to add a second field, another **String** field for the **Opening Hours**.
You are now ready to add a second field, another **Text** field for the **Opening Hours**.
![Hours of Operation Opening Hours](../assets/getting-started/tutorial/hours-of-operation-opening-hours.png 'Hours of Operation Opening Hours')
- Click on the `String` field
- Click on the `Text` field
- In the **Name** field, type `opening_hours`
![Hours of Operation Opening Hours Name](../assets/getting-started/tutorial/hours-of-operation-opening-hours-name.png 'Hours of Operation Opening Hours Name')
- Click the `+ Add Another Field` button
- Click the `+ Add another field` button
You are now ready to add a third field, another **String** field for the **Closing Hours**.
You are now ready to add a third field, another **Text** field for the **Closing Hours**.
![Hours of Operation Closing Hours](../assets/getting-started/tutorial/hours-of-operation-closing-hours.png 'Hours of Operation Closing Hours')
- Click on the `String` field
- Click on the `Text` field
- In the **Name** field, type `closing_hours`
![Hours of Operation Closing Hours Name](../assets/getting-started/tutorial/hours-of-operation-closing-hours-name.png 'Hours of Operation Closing Hours Name')
- Click the `Done` button
- Click the `Finish` button
![Hours of Operation Save](../assets/getting-started/tutorial/hours-of-operation-save.png 'Hours of Operation Save')
@ -409,17 +410,17 @@ You are now ready to add a third field, another **String** field for the **Closi
![Hours of Operation Strapi Restart](../assets/getting-started/tutorial/hours-of-operation-strapi-restart.png 'Hours of Operation Strapi Restart')
After Strapi has restarted, you are ready to assign this **Hours_of_operation** group to the **Restaurant** Content Type.
After Strapi has restarted, you are ready to assign this **Hours_of_operation** Component to the **Restaurant** Content Type.
::: tip NOTE
It would be possible to assign the **Hours_of_operation** group to another **Content Type**, let's say, a **Cafe** Content Type. You have the option to reuse this group across your application.
It would be possible to assign the **Hours_of_operation** Component to another **Content Type**, let's say, a **Cafe** Content Type. You have the option to reuse this component across your application.
:::
3. Next, you need to assign the **Hours_of_operation** Group to the **Restaurant** Content Type.
3. Next, you need to assign the **Hours_of_operation** Component to the **Restaurant** Content Type.
To access the **Hours_of_operation** Group from within the **Restaurant** Content Type, you need to **edit** the **Restaurant** Content Type in the **Content Type Builder**.
To access the **Hours_of_operation** Component from within the **Restaurant** Content Type, you need to **edit** the **Restaurant** Content Type in the **Content Type Builder**.
- If needed, navigate back to the **Content Type Builder**
@ -429,50 +430,50 @@ To access the **Hours_of_operation** Group from within the **Restaurant** Conten
![Edit Restaurant Add Another Field](../assets/getting-started/tutorial/edit-restaurant-add-another-field.png 'Edit Restaurant Add Another Field')
- Click one of the `+ Add Another Field` buttons, to add the **Group**
- Click one of the `+ Add another field` button, to add the **Component**
![Edit Restaurant Group Field](../assets/getting-started/tutorial/edit-restaurant-group-field.png 'Edit Restaurant Group Field')
![Edit Restaurant Component Field](../assets/getting-started/tutorial/edit-restaurant-group-field.png 'Edit Restaurant Component Field')
- Click on the `Group` field
- Click on the `Component` field
- Select `Use an existing component` option
- Click on the `continue` button
- Ensure `hours_of_operation` is displayed in the **Select a group** dropdown
- Provide a **name** for this group in the **Restaurant** Content Type. E.g., `restaurant_hours`
- Check the `Repeatable field` box
![Restaurant Component Inputs](../assets/getting-started/tutorial/restaurant-group-inputs-use.png 'Restaurant Component Inputs')
![Restaurant Group Inputs](../assets/getting-started/tutorial/restaurant-group-inputs.png 'Restaurant Group Inputs')
- Ensure `hours_of_operation` is displayed in the **Select a component** dropdown
- Provide a **name** for this component in the **Restaurant** Content Type. E.g., `restaurant_hours`
- Select the `Repeatable component` option
![Restaurant Component Inputs](../assets/getting-started/tutorial/restaurant-group-inputs.png 'Restaurant Component Inputs')
- Click on the `ADVANCED SETTINGS` tab
- Check the `Required field` checkbox
![Restaurant Group Advanced Settings](../assets/getting-started/tutorial/restaurant-group-advanced-settings.png 'Restaurant Group Advanced Settings')
![Restaurant Component Advanced Settings](../assets/getting-started/tutorial/restaurant-group-advanced-settings.png 'Restaurant Component Advanced Settings')
- Click the `Done` button
- Click the `Finish` button
![Restaurant Group Save](../assets/getting-started/tutorial/restaurant-group-save.png 'Restaurant Group save')
![Restaurant Component Save](../assets/getting-started/tutorial/restaurant-group-save.png 'Restaurant Component save')
- Click the `Save` button
- Wait for Strapi to restart
![Restaurant Group Strapi Restart](../assets/getting-started/tutorial/restaurant-group-strapi-restart.png 'Restaurant Group Strapi Restart')
![Restaurant Component Strapi Restart](../assets/getting-started/tutorial/restaurant-group-strapi-restart.png 'Restaurant Component Strapi Restart')
After Strapi has restarted, you are ready to continue to the next section where you customize the user-interface of your **Restaurant** Content Type.
4. Next, you edit the **View Settings** for the new **Hoursofoperation Group** from within the **Content Manager**.
4. Next, you edit the **View Settings** for the new **Hoursofoperation Component** from within the **Content Manager**.
You can _drag and drop_ fields into a different layout, as well as, _rename the labels_ as two examples of how you can customize the user interface for your **Content Types**.
- Navigate to and click on the `Content Manager`, under **PLUGINS** in the left-hand menu
- Click on the `Configure the view`, button
![Content Manager](../assets/getting-started/tutorial/content-manager.png 'Content Manager')
![Content Manager](../assets/getting-started/tutorial/content-manager-restaurant.png 'Content Manager')
- Click on the `Groups(1)` tab
- Click on the `Set the component's layout`
![Content Manager Groups Tab](../assets/getting-started/tutorial/content-manager-groups-tab.png 'Content Manager Groups Tab')
- Click on `Hours_of_operation` to modify the **View Settings**
![Content Manager Hoursofoperation](../assets/getting-started/tutorial/content-manager-hoursofoperation.png 'Content Manager Hoursofoperation')
![Content Manager Components Tab](../assets/getting-started/tutorial/content-manager-restaurant-group.png 'Content Manager Components Tab')
- Rearrange the fields and make them more user friendly. Grab the `opening_hours` and slide it next to `closing_hours`
@ -593,8 +594,6 @@ By default, Strapi publishes all **Content Types** with restricted permissions.
- Scroll back to the top, and click the **Save** button
![Roles and Permissions Save](../assets/getting-started/tutorial/roles-and-permissions-save.png 'Roles And Permissions Save')
You have now opened the API and are ready to consume your content.
## 8. Consume the Content Type API

View File

@ -0,0 +1,89 @@
# API tokens
In this guide we will see how you can create an API token system to execute request as an authenticated user.
This feature is in our [roadmap](https://portal.productboard.com/strapi/1-public-roadmap/c/40-api-access-token-with-permissions).
This guide is a workaround to achieve this feature before we support it natively in strapi.
## Introduction
The goal is to be able to request API endpoints with a query parameter `token` that authenticates as a user. `eg. /restaurants?token=my-secret-token`.
To achieve this feature development, we will have to customize the `users-permissions` plugin. To do so we will use the [customization concept](../concepts/customization.md), this documentation will help you understand how to customize all your applications
## Create the Token Content Type
To manage your tokens, you will have to create a new Content Type named `token`.
- `string` attribute named `token`
- `relation` attribute **Token** (`user`) - **Token** has and belongs to one **User** - **User** (`token`)
Then add some users and create some token linked to these users.
## Setup the file to override
We now have to customize the function that verifies the `token` token. Strapi has an Authentication process that uses `JWT` tokens, we will reuse this function to customize the verification.
[Here is the function](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/config/policies/permissions.js) that manage the JWT validation.
To be able to customize it, you will have to create a new file in your application `./extensions/users-permissions/config/policies/permissions.js`.
Then copy the original function that is on GitHub and paste it in your new file.
When it's done, the Strapi application will use this function instead of the core one. We are ready to customize it.
## Add token validation logic
You will have to update the first lines of this function.
**Path —** `./extensions/users-permissions/config/policies/permissions.js`
```js
const _ = require('lodash');
module.exports = async (ctx, next) => {
let role;
// add the detection of `token` query parameter
if (
(ctx.request && ctx.request.header && ctx.request.header.authorization) ||
(ctx.request.query && ctx.request.query.token)
) {
try {
// init `id` and `isAdmin` outside of validation blocks
let id;
let isAdmin;
if (ctx.request.query && ctx.request.query.token) {
// find the token entry that match the token from the request
const [token] = await strapi.query('token').find({token: ctx.request.query.token});
if (!token) {
throw new Error(`Invalid token: This token doesn't exist`);
} else {
if (token.user && typeof token.token === 'string') {
id = token.user.id;
}
isAdmin = false;
}
delete ctx.request.query.token;
} else if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
// use the current system with JWT in the header
const decrypted = await strapi.plugins[
'users-permissions'
].services.jwt.getToken(ctx);
id = decrypted.id;
isAdmin = decrypted.isAdmin || false;
}
// this is the line that already exist in the code
if (id === undefined) {
throw new Error('Invalid token: Token did not contain required fields');
}
...
```
And tada! You can now create a token, link it to a user and use it in your URLs with `token` as query parameters.

View File

@ -0,0 +1,153 @@
# Setup a third party client
This guide will explain how to setup a connection with a third party client and use it everywhere in your code.
In our example we will use the GitHub Node.JS client [OctoKit REST.js](https://github.com/octokit/rest.js/).
This guide could also be used to setup an Axios client instance.
## Installation
First you will have to install the client package in your application by running one of the following command.
:::: tabs
::: tab yarn
`yarn add @octokit/rest`
:::
::: tab npm
`npm install @octokit/rest`
:::
::::
## Create a hook
To init the client, we will use the [hooks system](../concepts/hooks.md). Hooks let you add new features in your Strapi application.
Hooks are loaded one time, at the server start.
Lets create our GitHub hook.
**Path —** `./hooks/github/index.js`
```js
module.exports = strapi => {
return {
async initialize() {
console.log('my hook is loaded');
},
};
};
```
When the hook is created, we have to enable it to say to Strapi to use this hook.
**Path —** `./config/hook.json`
```json
{
...
"github": {
"enabled": true
}
}
```
Now you can start your application, you should see a log `my hook is loaded` in your terminal.
## Initialize the client
First lets update the config file to add your [GitHub token](https://github.com/settings/tokens).
By following the [documentation](https://octokit.github.io/rest.js/#authentication) you will also find the way to use GitHub applications.
**Path —** `./config/hook.json`
```json
{
...
"github": {
"enabled": true,
"token": "bf78d4fc3c1767019870476d6d7cc8961383d80f"
}
}
```
Now we have to load the GitHub client.
**Path —** `./hooks/github/index.js`
```js
const GitHubAPI = require('@octokit/rest');
module.exports = strapi => {
return {
async initialize() {
const { token } = strapi.config.hook.github;
strapi.services.github = new GitHubAPI({
userAgent: `${strapi.config.info.name} v${strapi.config.info.version}`,
auth: `token ${token}`,
});
},
};
};
```
And here it is.
You can now use `strapi.github` everywhere in your code to use the GitHub client.
To simply test if it works, lets update the `bootstrap.js` function to log your GitHub profile.
**Path —** `./config/functions/bootstrap.js`
```js
module.exports = async () => {
const data = await strapi.services.github.users.getAuthenticated();
console.log(data);
};
```
Restart your server and you should see your GitHub profile data.
## Configs by environment
You would probably want specific configurations for development and production environment.
To do so, we will update some configurations.
You have to move your `github` configs from `./config/hook.json` to `./config/environments/development.json`, then remove it from the `hook.json` file.
And in your GitHub hook, you will have to replace `strapi.config.hook.github` by `strapi.config.currentEnvironment.github` to access to the configs.
**Path —** `./config/environments/development.json`
```json
{
"github": {
"enabled": true,
"token": "806506ab855a94e8608148315eeb39a44c29aee1"
}
}
```
**Path —** `./hooks/github/inde.js`
```js
const GitHubAPI = require('@octokit/rest');
module.exports = strapi => {
return {
async initialize() {
const { token } = strapi.config.currentEnvironment.github;
strapi.services.github = new GitHubAPI({
userAgent: `${strapi.config.info.name} v${strapi.config.info.version}`,
auth: `token ${token}`,
});
},
};
};
```

View File

@ -172,7 +172,7 @@ MongoDB must already be running in the background.
::: tab yarn
```
yarn create strapi-app new my-project
yarn create strapi-app my-project
```
:::
@ -320,7 +320,7 @@ Replace the contents of `/database.json` with the following and replace **< pass
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-mongoose",
"connector": "mongoose",
"settings": {
"uri": "mongodb://paulbocuse:<password>@strapidatabase-shard-00-00-fxxx6c.mongodb.net:27017,strapidatabase-shard-00-01-fxxxc.mongodb.net:27017,strapidatabase-shard-00-02-fxxxc.mongodb.net:27017/test?ssl=true&replicaSet=strapidatabase-shard-0&authSource=admin&retryWrites=true&w=majority"
},

View File

@ -409,7 +409,7 @@ Copy/paste the following:
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"connector": "bookshelf",
"settings": {
"client": "postgres",
"host": "${process.env.DATABASE_HOST || '127.0.0.1'}",
@ -493,7 +493,7 @@ sudo nano ecosystem.config.js
module.exports = {
apps : [{
name: 'your-app-name',
cwd: '/home/ubuntu/my-strapi-project/my-project'
cwd: '/home/ubuntu/my-strapi-project/my-project',
script: 'npm',
args: 'start',
env: {
@ -922,7 +922,7 @@ In your code editor, you will need to edit a file called `database.json`. Replac
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"connector": "bookshelf",
"settings": {
"client": "postgres",
"host": "${process.env.DATABASE_HOST || '127.0.0.1'}",
@ -1433,7 +1433,7 @@ Replace the contents of `database.json` with the following:
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"connector": "bookshelf",
"settings": {
"client": "postgres",
"host": "${process.env.DATABASE_HOST}",
@ -1500,7 +1500,7 @@ Replace the contents of `database.json` with the following:
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-mongoose",
"connector": "mongoose",
"settings": {
"uri": "${process.env.DATABASE_URI}",
"database": "${process.env.DATABASE_NAME}"

View File

@ -0,0 +1,123 @@
# Draft system
This guide will explain how to create draft system. That will you manage draft, published, archive status.
## Introduction
What we want here is to fetch only data that have a `published` status.
But we don't want to use [parameters](../content-api/parameters.md) (eg. /articles?status=published) because you can easily fake the params.
To be able to do that, you have first to understand some concepts.
When you create a content type, it generates an API with the following list of [endpoints](../content-api/endpoint.md).
Each of these endpoint triggers a controller action. Here is the list of [controller actions](../concepts/controller.md) that exist by default when a content type is created.
If you check the controller file of your generated API `./api/{content-type}/controller/{Content-Type}.js`, you will see an empty file. It is because all the default logic is managed by Strapi. But you can override these actions with your own code.
And that is what we will do to filter to `published` status by default.
## Example
In our example we will use an Article content type. By default when you fetch articles, you will got all articles.
Let's consider you don't want to expose articles that are in `draft` or `archive` status.
To enforce this rule we will customize the action that fetchs all articles to just fetch `published` articles.
To follow the example your will have to create a content type `articles` and add the following field definition:
- `string` attribute named `title`
- `text` attribute named `content`
- `enumeration` attribute named `status` with `draft`, `published`, `archive`
Then add some data with different `status`.
## Override controller action
To customize the function that fetch all our articles we will have to override the `find` function.
First, to see the difference, let's request `GET /articles`. You will see all the data you created.
Now let's start the customization.
**Path —** `./api/article/controller/Article.js`
```js
module.exports = {
async find() {
return 'strapi';
},
};
```
After saving the new function, let's restart the `GET /articles` request. We will see `strapi` as response.
## Get the data back
We now know the function we have to update, but we just want to customize the returned article values.
In the [controller documentation](../concepts/controllers.html#extending-a-model-controller) you will find the default implementation of every actions. It will help you overwrite the fetch logic.
**Path —** `./api/article/controller/Article.js`
```js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async find(ctx) {
let entities;
if (ctx.query._q) {
entities = await strapi.services.article.search(ctx.query);
} else {
entities = await strapi.services.article.find(ctx.query);
}
return entities.map(entity =>
sanitizeEntity(entity, { model: strapi.models.article })
);
},
};
```
And now the data is back on `GET /articles`
## Apply our changes
Here we want force to fetch articles that have status equal to `published`.
The way to do that is to set `ctx.query.status` to `published`.
It will force the filter of the query.
**Path —** `./api/restaurant/controller/Restaurant.js`
```js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async find(ctx) {
let entities;
ctx.query = {
...ctx.query,
status: 'published'
};
if (ctx.query._q) {
entities = await strapi.services.article.search(ctx.query);
} else {
entities = await strapi.services.article.find(ctx.query);
}
return entities.map(entity =>
sanitizeEntity(entity, { model: strapi.models.article })
);
},
};
```
And tada! Draft and archived articles disapeared.
::: tip
This guide can be applied to any other controller action.
:::

View File

@ -0,0 +1,63 @@
# Secure your application
In this guide we will see how you can secure your Strapi application by using a third party provider.
::: tip
In this example we will use [Sqreen](https://sqreen.com).
:::
There [onboarding](https://my.sqreen.com/new-application#nodejs-agent) is really easy to follow and understand.
## Install Sqreen
Sqreen is an Application Security Management. That enable protections tailored to your stack, get unprecedented visibility into your security and scale it in production.
You will have to install Sqreen node_module in your application.
:::: tabs
::: tab yarn
`yarn add sqreen`
:::
::: tab npm
`npm install sqreen`
:::
::::
## Start your application programmaticaly
We will have to require the Sqreen node_module in the file we use to start Strapi.
To do so you will have to create a `server.js` file to be able to start our application by running `node server.js`.
**Path —** `./server.js`
```js
const strapi = require('strapi');
strapi().start();
```
Now you can run `node server.js` and it will start your application.
## Inject and configure Sqreen agent
By following their Node.js onboarding, we need to require the Sqreen node_module where the server is started.
Also, Sqreen has to be required just before Strapi to work!
*This is the reason why we have created a `server.js` file.*
To do so, you will have to update this file.
**Path —** `./server.js`
```js
require('sqreen');
const strapi = require('strapi');
strapi().start();
```
To let Strapi and Sqreen sync, you will have to create a `./sqreen.json` file with your credentials.
Then start your server with `node server.js` and we are done.

View File

@ -0,0 +1,127 @@
# Send email programmatically
In this guide we will see how to use the Email plugin to send email where you want in your app.
For this example we want to receive an email when a new article's comment is posted and if it contains bad words.
## Introduction
What we want here is to add some custom logic and call the email service when a `Comment` is created via the `POST /comments` endpoint.
To be able to do that, you have first to understand some concepts.
When you create a content type, it generates an API with the following list of [endpoints](../content-api/endpoint.md).
Each of these endpoint triggers a controller action. Here is the list of [controller actions](../concepts/controller.md) that exist by default when a content type is created.
If you check the controller file of your generated API `./api/{content-type}/controller/{Content-Type}.js`, you will see an empty file. It is because all the default logic is managed by Strapi. But you can override these actions with your own code.
And that is what we will do to add our custom code.
## Example
To keep the code example realy easy to follow, we will just have a `Comment` content type and omit the `Author` and `Article` relations.
So lets create a `Comment` content type with just one **Text** field named `content`.
When the content type is created, allow the create function for the Public role.
To check if bad words are in the comment we will use `bad-words` [node module](https://www.npmjs.com/package/bad-words). You will have to install it in your application.
## Override controller action
To customize the function that creates a comment we will have to override the `create` function.
First, to see the difference, let's request `POST /comment` with `that is fucking good!` for the `content` attribute.
You will see your comment is successfully created.
Now let's start the customization.
**Path —** `./api/comment/controller/Comment.js`
```js
module.exports = {
async create() {
return 'strapi';
},
};
```
After saving the new function, let's restart the `POST /comment` request. We will see `strapi` as response.
## Get the comment creation back
We now know the function we have to update. Let's get back to the original function.
In the [controller documentation](../concepts/controllers.html#extending-a-model-controller) you will find the default implementation of every actions. It will help you overwrite the create logic.
**Path —** `./api/comment/controller/Comment.js`
```js
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
module.exports = {
async create(ctx) {
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.services.comment.create(data, { files });
} else {
entity = await strapi.services.comment.create(ctx.request.body);
}
return sanitizeEntity(entity, { model: strapi.models.comment });
},
};
```
And now the comment creation is back.
## Apply our changes
We want to check if the content of the comment contains a bad words.
If it does, we want to send an email using the [Email plugin](../plugins/email.md)
**Path —** `./api/comment/controller/Comment.js`
```js
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
const Filter = require('bad-words');
const filter = new Filter();
module.exports = {
async create(ctx) {
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.services.comment.create(data, { files });
} else {
entity = await strapi.services.comment.create(ctx.request.body);
}
entry = sanitizeEntity(entity, { model: strapi.models.comment });
// check if the comment content contains a bad word
if (entry.content !== filter.clean(entry.content)) {
// send an email by using the email plugin
await strapi.plugins['email'].services.email.send({
to: 'paulbocuse@strapi.io',
from: 'admin@strapi.io'
subject: 'Comment posted that contains a bad words',
text: `
The comment #${entry.id} contain a bad words.
Comment:
${entry.content}
`,
});
}
return entry;
},
};
```
And tada, it works.

View File

@ -111,7 +111,7 @@ module.exports = {
#### Mongoose limitation
Until September 2018, `remove` lifecycle callback [was not supported by Mongoose](https://github.com/Automattic/mongoose/issues/3054). This has been added but `strapi-hook-mongoose` is not adapted yet to this update.
Until September 2018, `remove` lifecycle callback [was not supported by Mongoose](https://github.com/Automattic/mongoose/issues/3054). This has been added but `mongoose` is not adapted yet to this update.
So, to trigger an url on delete, please add `request.post(strapi.config.currentEnvironment.staticWebsiteBuildURL, entry);` in:

View File

@ -8,6 +8,7 @@ Read the [Migration guide from alpha.26 to beta](migration-guide-alpha.26-to-bet
- [Migration guide from beta.15 to beta.16](migration-guide-beta.15-to-beta.16.md)
- [Migration guide from beta.16+ to beta.17.4](migration-guide-beta.16-to-beta.17.4.md)
- [Migration guide from beta.17+ to beta.18](migration-guide-beta.17-to-beta.18.md)
## Alpha guides

View File

@ -189,7 +189,6 @@ build
## Migrating `config`
### Remove the `server.autoReload` key
You need to remove the `server.autoReload` key in `./config/environments/**/server.json`.

View File

@ -0,0 +1,513 @@
# Migration guide from beta.17.4 through beta.17.8 to beta.18
Upgrading your Strapi application to `v3.0.0-beta.18`.
**Make sure your server is not running until then end of the migration**
## Upgrading your dependencies
Start by upgrading your dependencies. Make sure to use exact versions.
::: danger
Starting from beta.18 the database packages have been changed to allow future changes.
- `strapi-hook-knex` has been removed and merged into the `bookshelf` database connector.
- `strapi-hook-bookshelf` is renamed `strapi-connector-bookshelf`.
- `strapi-hook-mongoose` is renamed `strapi-connector-mongoose`.
:::
Update your package.json accordingly:
**Before**
```json
{
//...
"dependencies": {
"strapi": "3.0.0-beta.17.4",
"strapi-admin": "3.0.0-beta.17.4",
"strapi-hook-bookshelf": "3.0.0-beta.17.4", // rename to strapi-connector-bookshelf
"strapi-hook-knex": "3.0.0-beta.17.4", // remove
"strapi-plugin-content-manager": "3.0.0-beta.17.4",
"strapi-plugin-content-type-builder": "3.0.0-beta.17.4",
"strapi-plugin-email": "3.0.0-beta.17.4",
"strapi-plugin-graphql": "3.0.0-beta.17.4",
"strapi-plugin-upload": "3.0.0-beta.17.4",
"strapi-plugin-users-permissions": "3.0.0-beta.17.4",
"strapi-utils": "3.0.0-beta.17.4"
}
}
```
**After**
```json
{
//...
"dependencies": {
"strapi": "3.0.0-beta.18",
"strapi-admin": "3.0.0-beta.18",
"strapi-connector-bookshelf": "3.0.0-beta.18",
"strapi-plugin-content-manager": "3.0.0-beta.18",
"strapi-plugin-content-type-builder": "3.0.0-beta.18",
"strapi-plugin-email": "3.0.0-beta.18",
"strapi-plugin-graphql": "3.0.0-beta.18",
"strapi-plugin-upload": "3.0.0-beta.18",
"strapi-plugin-users-permissions": "3.0.0-beta.18",
"strapi-utils": "3.0.0-beta.18"
}
}
```
Then run either `yarn install` or `npm install`.
## Database configuration
Now that you have installed the new database package. You need to update your `database.json` configuration files located in `./config/environments/{env}/database.json`.
You can now only use the connector name instead of the complete package name.
**Before**
```json
{
"defaultConnection": "default",
"connections": {
"default": {
"connector": "strapi-hook-bookshelf",
"settings": {
//...
},
"options": {}
}
}
}
```
**After**
```json
{
"defaultConnection": "default",
"connections": {
"default": {
"connector": "bookshelf",
"settings": {
//...
},
"options": {
//...
}
}
}
}
```
## `ctx.state.user`
Previously the ctx.state.user was populated with the user informations, its role and permissions. To avoid perfromance issues the role is the only populated relation on the user by default.
## File model
The file model has been updated. The `size` field is now a decimal number, allowing correct sorting behavior.
You will need to clear some database indexes if you are using either Mysql or PostgreSQL.
:::: tabs
::: tab Mysql
Run the following statement in your database:
`DROP INDEX SEARCH_UPLOAD_FILE ON upload_file;`
:::
::: tab PostgreSQL
Run the following statement in your database:
`DROP INDEX search_upload_file_size;`
:::
::::
## Date type changes
We introduced new types in the admin panel: `date`, `datetime` and `time`. Before all of those types where saved as `datetime`.
You will need to change the type of your fields from `date` to `datetime` if you don't want to migrate your data.
- To migrate yout old `date` to `datetime`, change the field type from `date` to `datetime`. NO data migration is required.
- To migrate your old `date` to new `date`, you will need to migrate yout data to be of the format: `YYYY-MM-DD`
- To migrate your old `date` to the new `time`, change the field type from `date` to `time`. You will also need to transform them to be of the format: `HH:mm:ss.SSS`
## Groups become Components
If you were using the groups feature you will need to apply some changes:
Start by renaming the `./groups` folder to `./components` in your project root folder.
Components now are placed into `categories`. To reflect this you must move your components inside `category` folders.
::: danger
Make sure to use `-` in your file names (Do not use spaces or underscores).
:::
### Example
**Before**
```
groups/
├── seo-metadata.json
└── image-text.json
```
**After**
```
components/
├── seo/
│ └── metadata.json
└── content/
└── image-text.json
```
Now that you have moved your component into categories. You need to update your content-types to reference them correctly.
**Before**
`./api/restaurant/models/Restaurant.settings.json`
```json
{
"connection": "default",
"collectionName": "restaurants",
"info": {
"name": "restaurant",
"description": ""
},
"options": {
"increments": true,
"timestamps": ["created_at", "updated_at"]
},
"attributes": {
"title": {
"type": "string"
},
"seo_metadatas": {
"type": "group",
"group": "seo-metadata",
"repeatable": true
},
"cover": {
"type": "group",
"group": "image-text"
}
}
}
```
**After**
`./api/restaurant/models/Restaurant.settings.json`
```json
{
"connection": "default",
"collectionName": "restaurants",
"info": {
"name": "restaurant",
"description": ""
},
"options": {
"increments": true,
"timestamps": ["created_at", "updated_at"]
},
"attributes": {
"title": {
"type": "string"
},
"seo_metadatas": {
"type": "component",
"component": "seo.metadata", // {category}.{name}
"repeatable": true
},
"cover": {
"type": "component",
"component": "content.image-text"
}
}
}
```
### Database migration of groups
::: warning
Make sure to do a database backup before your migration.
:::
Those migration are only necessary if you have data in production. Otherwise you should simply recreate your db.
To keep your preferences you can backup the `core_store` table data.
#### Bookshelf
Some database changes have occured:
- Component join tables have been renamed from `{content_type}_groups` to `{content_type}_components`.
- Component join tables column `group_type` is renamed to `component_type`.
- Component join tables column `group_id` is renamed to `component_id`.
**Migration queries**
Make sure to run those queries for the tables that exist in your database.
_`Queries for a Restaurant content type`_
:::: tabs
::: tab Sqlite
```sql
-- renaming the table
ALTER TABLE restaurants_groups
RENAME TO restaurants_components;
-- renaming the columns
ALTER TABLE restaurants_components
RENAME COLUMN group_type to component_type;
ALTER TABLE restaurants_components
RENAME COLUMN group_id to component_id;
```
:::
::: tab Postgres
```sql
-- renaming the table
ALTER TABLE restaurants_groups
RENAME TO restaurants_components;
-- renaming the columns
ALTER TABLE restaurants_components
RENAME COLUMN group_type to component_type;
ALTER TABLE restaurants_components
RENAME COLUMN group_id to component_id;
```
:::
::: tab Mysql
```sql
-- renaming the table
RENAME TABLE restaurants_groups TO restaurants_components;
-- renaming the columns
ALTER TABLE restaurants_components
RENAME COLUMN group_type to component_type;
ALTER TABLE restaurants_components
RENAME COLUMN group_id to component_id;
```
:::
::::
---
You might notice that you still have some tables with a name containing the `group` keyword. Those are the tables that contain the groups data.
If you want to rename them you have 3 steps to follow:
**1. Rename the table in your DB (you can use the table renaming query shown above).**
:::: tabs
::: tab Sqlite
```sql
ALTER TABLE groups_old_table_name
RENAME TO components_new_table_name;
```
:::
::: tab Postgres
```sql
ALTER TABLE groups_old_table_name
RENAME TO components_new_table_name;
```
:::
::: tab Mysql
```sql
-- renaming the table
RENAME TABLE groups_old_table_name TO components_new_table_name;
```
:::
::::
**2. Change the `collectionName` of the component**
**Before**
`./api/components/category/component.json`
```json
{
"collectionName": "groups_old_table_name"
//...
}
```
**After**
`./api/components/category/component.json`
```json
{
"collectionName": "components_new_table_name"
//....
}
```
**3. Update the `component_type` values in the join tables**
_Repeat this query for every join table where you are using this component._
```sql
UPDATE restaurant_components
SET component_type = 'components_new_table_name'
WHERE component_type = 'groups_old_table_name';
```
**4. If you store files in groups, update the `related_type` values**
```sql
UPDATE upload_file_morph
SET related_type = 'components_new_table_name'
WHERE related_type = 'groups_old_table_name';
```
#### Mongo
In `mongo` the relation between a content type and its components is held in an array of references. To know which component type it referes to, the array also contains a `kind` attribute containing the component Schema name.
**How to migrate**
**1. Get your new global ids**
The `kind` attribute references the Strapi `globalId` of a model. To get your component global ids run:
```sh
strapi console
```
```js
Object.keys(strapi.components).map(key => strapi.components[key].globalId);
//[
// 'ComponentCategoryMyComponent',
// 'ComponentCategoryMyOtherComponent',
//]
```
**2. Rename the component collections**
```js
// renaming a collection groups_my_group
db.collection.renameCollection('groups_my_group', 'components_my_component');
```
**3. Change the `collectionName` of the component**
**Before**
`./api/components/category/component.json`
```json
{
"collectionName": "groups_old_table_name"
//...
}
```
**After**
`./api/components/category/component.json`
```json
{
"collectionName": "components_new_table_name"
//....
}
```
**4. Rename the `kind` attributes**
To know the old `kind` name of a group here is the function that creates it:
```js
toGlobalId = name => upperFirst(camelCase(`group_${name}`));
// my-group => GroupMyGroup
```
**Query to update the kind for on filed in one contentType**:
```js
db.getCollection('contentTypeCollection').update(
{ 'componentField.kind': 'GroupsMyGroup' },
{ $set: { 'componentField.$.kind': 'ComponentCategoryMyComponent' } },
{ multi: true }
);
```
## Adding new root page files
We created new home pages when your go to your api url.
You will need to copy `index.html` and `production.html` into your `public` folder.
You can find those two files [here](https://github.com/strapi/strapi/tree/master/packages/strapi-generate-new/lib/resources/files/public).
## Updating `csp` options
The admin panel contains certain assets that use `data:img;base64` images. To allow rendering of those assets you can update the files `./config/environments/{env}/security.json` as follows:
**Before**
```json
{
"csp": {
"enabled": true,
"policy": [
{
"img-src": "'self' http:"
},
"block-all-mixed-content"
]
}
//....
}
```
**After**
```json
{
"csp": {
"enabled": true,
"policy": ["block-all-mixed-content"]
}
//....
}
```
If you need more fine control you can also simply add the `data:` option to the `img-src` option.
## Rebuilding your administration panel
Now delete the `.cache` and `build` folders. Then run `yarn develop`.
```
```

View File

@ -76,7 +76,7 @@ You have to send FormData in your request body
## Upload files related to an entry
To upload files that will be liked to an specific entry.
To upload files that will be linked to an specific entry.
### Request parameters
@ -213,8 +213,8 @@ And for your files, they have to be prefixed by `files`.
Example here with cover attribute `files.cover`.
::: tip
If you want to upload files for a group, you will have to specify the inidex of the item you wan to add the file.
Example `files.my_group_name[the_index].attribute_name`
If you want to upload files for a component, you will have to specify the inidex of the item you wan to add the file.
Example `files.my_component_name[the_index].attribute_name`
:::
::: warning

View File

@ -14,20 +14,35 @@
"comment": ""
},
"attributes": {
"full_name": {
"type": "string"
},
"postal_code": {
"type": "string"
"geolocation": {
"type": "json",
"required": true
},
"city": {
"type": "string",
"required": true
},
"postal_coder": {
"type": "string"
},
"geolocation": {
"type": "json"
"category": {
"model": "category"
},
"country": {
"model": "country"
"cover": {
"model": "file",
"via": "related",
"plugin": "upload",
"required": false
},
"images": {
"collection": "file",
"via": "related",
"plugin": "upload",
"required": false
},
"full_name": {
"type": "string",
"required": true
}
}
}
}

View File

@ -15,4 +15,4 @@
"type": "string"
}
}
}
}

View File

@ -16,7 +16,8 @@
"attributes": {
"name": {
"type": "string",
"required": true
"required": true,
"minLength": 3
},
"code": {
"type": "string",
@ -25,4 +26,4 @@
"minLength": 2
}
}
}
}

View File

@ -11,13 +11,13 @@
"comment": ""
},
"attributes": {
"author": {
"model": "user",
"plugin": "users-permissions"
"authore": {
"plugin": "users-permissions",
"model": "user"
},
"review": {
"model": "review",
"via": "likes"
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More