diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 03fb10ae3d..937bb43b75 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -124,8 +124,12 @@ module.exports = { collapsable: true, title: '📚 Guides', children: [ + '/3.0.0-beta.x/guides/update-version', '/3.0.0-beta.x/guides/databases', '/3.0.0-beta.x/guides/deployment', + '/3.0.0-beta.x/guides/jwt-validation', + '/3.0.0-beta.x/guides/error-catching', + '/3.0.0-beta.x/guides/slug', '/3.0.0-beta.x/guides/webhooks', ], }, diff --git a/docs/3.0.0-beta.x/admin-panel/customization.md b/docs/3.0.0-beta.x/admin-panel/customization.md index 4c5bc55022..0aa0ced84f 100644 --- a/docs/3.0.0-beta.x/admin-panel/customization.md +++ b/docs/3.0.0-beta.x/admin-panel/customization.md @@ -142,7 +142,7 @@ export default WysiwygWithErrors; The AdminUI package source can be easily found in `./node_modules/strapi-admin/src/`. -For example, to change the top-left displayed admin panel's color, `./node_modules/strapi-admin/admin/src/components/LeftMenuHeader/styles.scss` should be overriden by `./admin/src/components/LeftMenuHeader/styles.scss` with your own styles. +For example, to change the top-left displayed admin panel's color, copy the `./node_modules/strapi-admin/admin/src/components/LeftMenuHeader` folder to `./admin/src/components/LeftMenuHeader` and change the styles inside `./admin/src/components/LeftMenuHeader/Wrapper.js`. Thus, you are replacing the files that would normally be in `node_modules/strapi-admin/admin/src` and directing them to `admin/src/some/file/path`. diff --git a/docs/3.0.0-beta.x/assets/guides/slug/fields.png b/docs/3.0.0-beta.x/assets/guides/slug/fields.png new file mode 100644 index 0000000000..fc0b7c788b Binary files /dev/null and b/docs/3.0.0-beta.x/assets/guides/slug/fields.png differ diff --git a/docs/3.0.0-beta.x/assets/guides/slug/layout-after.png b/docs/3.0.0-beta.x/assets/guides/slug/layout-after.png new file mode 100644 index 0000000000..af5bd00313 Binary files /dev/null and b/docs/3.0.0-beta.x/assets/guides/slug/layout-after.png differ diff --git a/docs/3.0.0-beta.x/assets/guides/slug/layout-before.png b/docs/3.0.0-beta.x/assets/guides/slug/layout-before.png new file mode 100644 index 0000000000..60352779ef Binary files /dev/null and b/docs/3.0.0-beta.x/assets/guides/slug/layout-before.png differ diff --git a/docs/3.0.0-beta.x/assets/guides/slug/layout-config.png b/docs/3.0.0-beta.x/assets/guides/slug/layout-config.png new file mode 100644 index 0000000000..a4c5d87d99 Binary files /dev/null and b/docs/3.0.0-beta.x/assets/guides/slug/layout-config.png differ diff --git a/docs/3.0.0-beta.x/concepts/configurations.md b/docs/3.0.0-beta.x/concepts/configurations.md index 3864458b2c..9bae2d764b 100644 --- a/docs/3.0.0-beta.x/concepts/configurations.md +++ b/docs/3.0.0-beta.x/concepts/configurations.md @@ -98,6 +98,8 @@ module.exports = async () => { CRON tasks allow you to schedule jobs (arbitrary functions) for execution at specific dates, with optional recurrence rules. It only uses a single timer at any given time (rather than reevaluating upcoming jobs every second/minute). +This feature is powered by [`node-schedule`](https://www.npmjs.com/package/node-schedule) node modules. Check it for more information. + ::: note Make sure the `enabled` cron config is set to `true` in `./config/environments/**/server.json` file. ::: diff --git a/docs/3.0.0-beta.x/concepts/controllers.md b/docs/3.0.0-beta.x/concepts/controllers.md index 25f72f0acb..b31a37552e 100644 --- a/docs/3.0.0-beta.x/concepts/controllers.md +++ b/docs/3.0.0-beta.x/concepts/controllers.md @@ -50,6 +50,8 @@ const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); #### `find` ```js +const { sanitizeEntity } = require('strapi-utils'); + module.exports = { /** * Retrieve records. @@ -60,9 +62,9 @@ module.exports = { async find(ctx) { let entities; if (ctx.query._q) { - entities = await service.search(ctx.query); + entities = await strapi.services.restaurant.search(ctx.query); } else { - entities = await service.find(ctx.query); + entities = await strapi.services.restaurant.find(ctx.query); } return entities.map(entity => sanitizeEntity(entity, { model })); @@ -77,6 +79,8 @@ module.exports = { #### `findOne` ```js +const { sanitizeEntity } = require('strapi-utils'); + module.exports = { /** * Retrieve a record. @@ -85,7 +89,7 @@ module.exports = { */ async findOne(ctx) { - const entity = await service.findOne(ctx.params); + const entity = await strapi.services.restaurant.findOne(ctx.params); return sanitizeEntity(entity, { model }); }, }; @@ -107,9 +111,9 @@ module.exports = { count(ctx) { if (ctx.query._q) { - return service.countSearch(ctx.query); + return strapi.services.restaurant.countSearch(ctx.query); } - return service.count(ctx.query); + return strapi.services.restaurant.count(ctx.query); }, }; ``` @@ -121,6 +125,8 @@ module.exports = { #### `create` ```js +const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); + module.exports = { /** * Create a record. @@ -132,9 +138,9 @@ module.exports = { let entity; if (ctx.is('multipart')) { const { data, files } = parseMultipartData(ctx); - entity = await service.create(data, { files }); + entity = await strapi.services.restaurant.create(data, { files }); } else { - entity = await service.create(ctx.request.body); + entity = await strapi.services.restaurant.create(ctx.request.body); } return sanitizeEntity(entity, { model }); }, @@ -148,6 +154,8 @@ module.exports = { #### `update` ```js +const { parseMultipartData, sanitizeEntity } = require('strapi-utils'); + module.exports = { /** * Update a record. @@ -159,9 +167,14 @@ module.exports = { let entity; if (ctx.is('multipart')) { const { data, files } = parseMultipartData(ctx); - entity = await service.update(ctx.params, data, { files }); + entity = await strapi.services.restaurant.update(ctx.params, data, { + files, + }); } else { - entity = await service.update(ctx.params, ctx.request.body); + entity = await strapi.services.restaurant.update( + ctx.params, + ctx.request.body + ); } return sanitizeEntity(entity, { model }); @@ -176,6 +189,8 @@ module.exports = { #### `delete` ```js +const { sanitizeEntity } = require('strapi-utils'); + module.exports = { /** * delete a record. @@ -184,7 +199,7 @@ module.exports = { */ async delete(ctx) { - const entity = await service.delete(ctx.params); + const entity = await strapi.services.restaurant.delete(ctx.params); return sanitizeEntity(entity, { model }); }, }; diff --git a/docs/3.0.0-beta.x/concepts/models.md b/docs/3.0.0-beta.x/concepts/models.md index ac04814cc8..4402b99d0d 100644 --- a/docs/3.0.0-beta.x/concepts/models.md +++ b/docs/3.0.0-beta.x/concepts/models.md @@ -4,48 +4,50 @@ Models are a representation of the database's structure and lifecycle. They are split into two separate files. A JavaScript file that contains the lifecycle callbacks, and a JSON one that represents the data stored in the database and their format. The models also allow you to define the relationships between them. -**Path —** `./api/user/models/User.js`. +**Path —** `./api/restaurant/models/Restaurant.js`. ```js module.exports = { // Before saving a value. // Fired before an `insert` or `update` query. - beforeSave: next => { - // Use `this` to get your current object - next(); - }, + beforeSave: (model, attrs, options) => {}, // After saving a value. // Fired after an `insert` or `update` query. - afterSave: (doc, next) => { - next(); - }, + afterSave: (model, attrs, options) => {}, // ... and more }; ``` -**Path —** `./api/user/models/User.settings.json`. +**Path —** `./api/restaurant/models/Restaurant.settings.json`. ```json { "connection": "default", "info": { - "name": "user", - "description": "This represents the User Model" + "name": "restaurant", + "description": "This represents the Restaurant Model" }, "attributes": { - "firstname": { + "cover": { + "collection": "file", + "via": "related", + "plugin": "upload" + }, + "name": { + "default": "", "type": "string" }, - "lastname": { - "type": "string" + "description": { + "default": "", + "type": "text" } } } ``` -In this example, there is a `User` model which contains two attributes `firstname` and `lastname`. +In this example, there is a `Restaurant` model which contains two attributes `cover`, `name` and `description`. ### Where are the models defined? @@ -57,12 +59,12 @@ The models are defined in each `./api/**/models/` folder. Every JavaScript or JS If you are just starting out it is very convenient to generate some models with the Content Type Builder, directly in the admin interface. You can then review the generated model mappings on the code level. The UI takes over a lot of validation tasks and gives you a fast feeling for available features. ::: -Use the CLI, and run the following command `strapi generate:model user firstname:string lastname:string`. Read the [CLI documentation](../cli/CLI.md) for more information. +Use the CLI, and run the following command `strapi generate:model restaurant name:string description:text`. Read the [CLI documentation](../cli/CLI.md) for more information. -This will create two files located at `./api/user/models`: +This will create two files located at `./api/restaurant/models`: -- `User.settings.json`: contains the list of attributes and settings. The JSON format makes the file easily editable. -- `User.js`: imports `User.settings.json` and extends it with additional settings and lifecycle callbacks. +- `Restaurant.settings.json`: contains the list of attributes and settings. The JSON format makes the file easily editable. +- `Restaurant.js`: imports `Restaurant.settings.json` and extends it with additional settings and lifecycle callbacks. ::: note when you create a new API using the CLI (`strapi generate:api `), a model is automatically created. @@ -76,18 +78,18 @@ Additional settings can be set on models: - `collectionName` (string) - Collection's name (or table's name) in which the data should be stored. - `globalId` (string) -Global variable name for this model (case-sensitive). -**Path —** `User.settings.json`. +**Path —** `Restaurant.settings.json`. ```json { "connection": "mongo", - "collectionName": "Users_v1", - "globalId": "Users", + "collectionName": "Restaurants_v1", + "globalId": "Restaurants", "attributes": {} } ``` -In this example, the model `User` will be accessible through the `Users` global variable. The data will be stored in the `Users_v1` collection or table and the model will use the `mongo` connection defined in `./config/environments/**/database.json` +In this example, the model `Restaurant` will be accessible through the `Restaurants` global variable. The data will be stored in the `Restaurants_v1` collection or table and the model will use the `mongo` connection defined in `./config/environments/**/database.json` ::: note The `connection` value can be changed whenever you want, but you should be aware that there is no automatic data migration process. Also if the new connection doesn't use the same ORM you will have to rewrite your queries. @@ -101,12 +103,12 @@ The info key on the model-json states information about the model. This informat - `description`: The description of the model. - `mainField`: Determines which model-attribute is shown when displaying the model. -**Path —** `User.settings.json`. +**Path —** `Restaurant.settings.json`. ```json { "info": { - "name": "user", + "name": "restaurant", "description": "" } } @@ -137,6 +139,7 @@ The following types are currently available: - `string` - `text` +- `richtext` - `integer` - `biginteger` - `float` @@ -172,45 +175,24 @@ To improve the Developer eXperience when developing or using the administration ### Example -**Path —** `User.settings.json`. +**Path —** `Restaurant.settings.json`. ```json { - "connection": "default", - "info": { - "name": "user", - "description": "This represents the User Model", - "mainField": "email" - }, + ... "attributes": { - "firstname": { - "type": "string" - }, - "lastname": { - "type": "string" - }, - "email": { - "type": "email", - "required": true, + "title": { + "type": "string", + "min": 3, + "max": 99, "unique": true }, - "password": { - "type": "password", - "required": true, - "private": true + "description": { + "default": "My descrioption", + "type": "text", + "required": true }, - "about": { - "type": "description" - }, - "age": { - "type": "integer", - "min": 18, - "max": 99, - "index": true - }, - "birthday": { - "type": "date" - } + ... } } ``` @@ -243,21 +225,6 @@ A `pet` can be owned by someone (a `user`). } ``` -**Path —** `./api/pet/controllers/Pet.js`. - -```js -// Mongoose example -module.exports = { - findPetsWithOwners: async ctx => { - // Retrieve the list of pets with their owners. - const pets = Pet.find().populate('owner'); - - // Send the list of pets. - ctx.body = pets; - }, -}; -``` - **Example** ```js @@ -309,36 +276,6 @@ A `user` can have one `address`. And this address is only related to this `user` } ``` -**Path —** `./api/user/controllers/User.js`. - -```js -// Mongoose example -module.exports = { - findUsersWithAddresses: async ctx => { - // Retrieve the list of users with their addresses. - const users = User.find().populate('address'); - - // Send the list of users. - ctx.body = users; - }, -}; -``` - -**Path —** `./api/adress/controllers/Address.js`. - -```js -// Mongoose example -module.exports = { - findArticlesWithUsers: async ctx => { - // Retrieve the list of addresses with their users. - const articles = Address.find().populate('user'); - - // Send the list of addresses. - ctx.body = addresses; - }, -}; -``` - **Example** ```js @@ -390,36 +327,6 @@ A `user` can have many `articles`, and an `article` can be related to one `user` } ``` -**Path —** `./api/user/controllers/User.js`. - -```js -// Mongoose example -module.exports = { - findUsersWithArticles: async ctx => { - // Retrieve the list of users with their articles. - const users = User.find().populate('articles'); - - // Send the list of users. - ctx.body = users; - }, -}; -``` - -**Path —** `./api/article/controllers/Article.js`. - -```js -// Mongoose example -module.exports = { - findArticlesWithAuthors: async ctx => { - // Retrieve the list of articles with their authors. - const articles = Article.find().populate('author'); - - // Send the list of users. - ctx.body = articles; - }, -}; -``` - **Examples** ```js @@ -486,36 +393,6 @@ A `product` can be related to many `categories`, so a `category` can have many ` } ``` -**Path —** `./api/product/controllers/Product.js`. - -```js -// Mongoose example -module.exports = { - findProductsWithCategories: async ctx => { - // Retrieve the list of products. - const products = Product.find().populate('categories'); - - // Send the list of products. - ctx.body = products; - }, -}; -``` - -**Path —** `./api/category/controllers/Category.js`. - -```js -// Mongoose example -module.exports = { - findCategoriesWithProducts: async ctx => { - // Retrieve the list of categories. - const categories = Category.find().populate('products'); - - // Send the list of categories. - ctx.body = categories; - }, -}; -``` - **Example** ```js @@ -644,102 +521,6 @@ A `Image` model might belongs to many either `Article` models or a `Product` mod } ``` -**Path —** `./api/image/controllers/Image.js`. - -```js -// Mongoose example -module.exports = { - findFiles: async ctx => { - // Retrieve the list of images with the Article or Product entries related to them. - const images = Images.find().populate('related'); - - /* - [{ - "_id": "5a81b0fa8c063a53298a934a", - "url": "http://....", - "name": "john_doe_avatar.png", - "related": [{ - "_id": "5a81b0fa8c063a5393qj934a", - "title": "John Doe is awesome", - "description": "..." - }, { - "_id": "5a81jei389ns5abd75f79c", - "name": "A simple chair", - "description": "..." - }] - }] - */ - - // Send the list of files. - ctx.body = images; - }, -}; -``` - -**Path —** `./api/article/controllers/Article.js`. - -```js -// Mongoose example -module.exports = { - findArticlesWithAvatar: async ctx => { - // Retrieve the list of articles with the avatar (image). - const articles = Article.find().populate('avatar'); - - /* - [{ - "_id": "5a81b0fa8c063a5393qj934a", - "title": "John Doe is awesome", - "description": "...", - "avatar": { - "_id": "5a81b0fa8c063a53298a934a", - "url": "http://....", - "name": "john_doe_avatar.png" - } - }] - */ - - // Send the list of users. - ctx.body = articles; - }, -}; -``` - -**Path —** `./api/product/controllers/Product.js`. - -```js -// Mongoose example -module.exports = { - findProductWithPictures: async ctx => { - // Retrieve the list of products with the pictures (images). - const products = Product.find().populate('pictures'); - - /* - [{ - "_id": "5a81jei389ns5abd75f79c", - "name": "A simple chair", - "description": "...", - "pictures": [{ - "_id": "5a81b0fa8c063a53298a934a", - "url": "http://....", - "name": "chair_position_1.png" - }, { - "_id": "5a81d22bee1ad45abd75f79c", - "url": "http://....", - "name": "chair_position_2.png" - }, { - "_id": "5a81d232ee1ad45abd75f79e", - "url": "http://....", - "name": "chair_position_3.png" - }] - }] - */ - - // Send the list of users. - ctx.body = products; - }, -}; -``` - #### Database implementation If you're using MongoDB as a database, you don't need to do anything. Everything is natively handled by Strapi. However, to implement a polymorphic relationship with SQL databases, you need to create two tables. diff --git a/docs/3.0.0-beta.x/content-api/api-endpoints.md b/docs/3.0.0-beta.x/content-api/api-endpoints.md index 5257155559..4a098505f5 100644 --- a/docs/3.0.0-beta.x/content-api/api-endpoints.md +++ b/docs/3.0.0-beta.x/content-api/api-endpoints.md @@ -1,8 +1,12 @@ # API Endpoints -When you create a `Content Type` you will have a certain number of REST API endpoints available to interact with it. +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` Content Type for the next steps. +As an **example** let's consider the `Restaurant` as a **Content Type** and `Openning_hours` as a **Group** for the next steps. + +:::: tabs cache-lifetime="10" :options="{ useUrlFragment: false }" + +::: tab "Content Type" id="content-type" ### `Restaurant` Content Type @@ -12,6 +16,10 @@ As an example let's consider the `Restaurant` Content Type for the next steps. | cover | media | Restaurant's cover image | | | opening_hours | group | Restaurant's opening hours group | `repeatable` | +::: + +::: tab "Group" id="group" + ### `Opening_hours` Group | Fields | Type | Description | @@ -20,10 +28,14 @@ As an example let's consider the `Restaurant` Content Type for the next steps. | opening_hour | string | Meta's opening hour | | closing_hour | string | Meta's closing hour | ---- +::: + +:::: ## Endpoints +Here is the list of endpoints generated for each of your **Content Types** +