diff --git a/docs/docs/guides/e2e/01-app-template.md b/docs/docs/guides/e2e/01-app-template.md index 01e345cfa1..b7b1e1233a 100644 --- a/docs/docs/guides/e2e/01-app-template.md +++ b/docs/docs/guides/e2e/01-app-template.md @@ -15,9 +15,95 @@ Here you can read about what content schemas the test instance has & the API cus ## Content Schemas -:::note -There's no content yet! -::: +### Article + +```json +{ + // ... + "attributes": { + "title": { + "type": "string" + }, + "content": { + "type": "blocks" + }, + "authors": { + "type": "relation", + "relation": "manyToMany", + "target": "api::author.author", + "inversedBy": "articles" + } + } + // ... +} +``` + +### Author + +```json +{ + // ... + "attributes": { + "name": { + "type": "string" + }, + "profile": { + "allowedTypes": ["images", "files", "videos", "audios"], + "type": "media", + "multiple": false + }, + "articles": { + "type": "relation", + "relation": "manyToMany", + "target": "api::article.article", + "mappedBy": "authors" + } + } + // ... +} +``` + +### Homepage (Single Type) + +```json +{ + // ... + "attributes": { + "title": { + "type": "string" + }, + "content": { + "type": "blocks" + }, + "admin_user": { + "type": "relation", + "relation": "oneToOne", + "target": "admin::user" + } + } + // ... +} +``` + +### Upcoming Match (Single Type) + +```json +{ + // ... + "attributes": { + "title": { + "type": "string" + }, + "number_of_upcoming_matches": { + "type": "integer" + }, + "next_match": { + "type": "date" + } + } + // ... +} +``` ## API Customisations diff --git a/e2e/app-template/template/src/api/article/content-types/article/schema.json b/e2e/app-template/template/src/api/article/content-types/article/schema.json new file mode 100644 index 0000000000..b08a6174c4 --- /dev/null +++ b/e2e/app-template/template/src/api/article/content-types/article/schema.json @@ -0,0 +1,26 @@ +{ + "kind": "collectionType", + "collectionName": "articles", + "info": { + "singularName": "article", + "pluralName": "articles", + "displayName": "Article", + "description": "" + }, + "options": {}, + "pluginOptions": {}, + "attributes": { + "title": { + "type": "string" + }, + "content": { + "type": "blocks" + }, + "authors": { + "type": "relation", + "relation": "manyToMany", + "target": "api::author.author", + "inversedBy": "articles" + } + } +} diff --git a/e2e/app-template/template/src/api/testing/controllers/testing.js b/e2e/app-template/template/src/api/article/controllers/article.js similarity index 52% rename from e2e/app-template/template/src/api/testing/controllers/testing.js rename to e2e/app-template/template/src/api/article/controllers/article.js index 4127b8f5ba..c4bca44738 100644 --- a/e2e/app-template/template/src/api/testing/controllers/testing.js +++ b/e2e/app-template/template/src/api/article/controllers/article.js @@ -1,9 +1,9 @@ 'use strict'; /** - * testing controller + * article controller */ const { createCoreController } = require('@strapi/strapi').factories; -module.exports = createCoreController('api::testing.testing'); +module.exports = createCoreController('api::article.article'); diff --git a/e2e/app-template/template/src/api/article/documentation/1.0.0/article.json b/e2e/app-template/template/src/api/article/documentation/1.0.0/article.json new file mode 100644 index 0000000000..11021b2468 --- /dev/null +++ b/e2e/app-template/template/src/api/article/documentation/1.0.0/article.json @@ -0,0 +1,587 @@ +{ + "/articles": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleListResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Article"], + "parameters": [ + { + "name": "sort", + "in": "query", + "description": "Sort by attributes ascending (asc) or descending (desc)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pagination[withCount]", + "in": "query", + "description": "Return page/pageSize (default: true)", + "deprecated": false, + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "pagination[page]", + "in": "query", + "description": "Page number (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[pageSize]", + "in": "query", + "description": "Page size (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[start]", + "in": "query", + "description": "Offset value (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[limit]", + "in": "query", + "description": "Number of entities to return (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "fields", + "in": "query", + "description": "Fields to return (ex: title,author)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "populate", + "in": "query", + "description": "Relations to return", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "filters", + "in": "query", + "description": "Filters to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "object" + }, + "style": "deepObject" + }, + { + "name": "locale", + "in": "query", + "description": "Locale to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + } + ], + "operationId": "get/articles" + }, + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Article"], + "parameters": [], + "operationId": "post/articles", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleRequest" + } + } + } + } + } + }, + "/articles/{id}": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Article"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "get/articles/{id}" + }, + "put": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Article"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "put/articles/{id}", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleRequest" + } + } + } + } + }, + "delete": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Article"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "delete/articles/{id}" + } + }, + "/articles/{id}/localizations": { + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleLocalizationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Article"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "post/articles/{id}/localizations", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArticleLocalizationRequest" + } + } + } + } + } + } +} diff --git a/e2e/app-template/template/src/api/testing/routes/testing.js b/e2e/app-template/template/src/api/article/routes/article.js similarity index 54% rename from e2e/app-template/template/src/api/testing/routes/testing.js rename to e2e/app-template/template/src/api/article/routes/article.js index 18fa07c967..4d843face9 100644 --- a/e2e/app-template/template/src/api/testing/routes/testing.js +++ b/e2e/app-template/template/src/api/article/routes/article.js @@ -1,9 +1,9 @@ 'use strict'; /** - * testing router + * article router */ const { createCoreRouter } = require('@strapi/strapi').factories; -module.exports = createCoreRouter('api::testing.testing'); +module.exports = createCoreRouter('api::article.article'); diff --git a/e2e/app-template/template/src/api/testing/services/testing.js b/e2e/app-template/template/src/api/article/services/article.js similarity index 53% rename from e2e/app-template/template/src/api/testing/services/testing.js rename to e2e/app-template/template/src/api/article/services/article.js index 510389ad7c..b9890beb5d 100644 --- a/e2e/app-template/template/src/api/testing/services/testing.js +++ b/e2e/app-template/template/src/api/article/services/article.js @@ -1,9 +1,9 @@ 'use strict'; /** - * testing service + * article service */ const { createCoreService } = require('@strapi/strapi').factories; -module.exports = createCoreService('api::testing.testing'); +module.exports = createCoreService('api::article.article'); diff --git a/e2e/app-template/template/src/api/author/content-types/author/schema.json b/e2e/app-template/template/src/api/author/content-types/author/schema.json new file mode 100644 index 0000000000..e56835e55b --- /dev/null +++ b/e2e/app-template/template/src/api/author/content-types/author/schema.json @@ -0,0 +1,27 @@ +{ + "kind": "collectionType", + "collectionName": "authors", + "info": { + "singularName": "author", + "pluralName": "authors", + "displayName": "Author" + }, + "options": {}, + "pluginOptions": {}, + "attributes": { + "name": { + "type": "string" + }, + "profile": { + "allowedTypes": ["images", "files", "videos", "audios"], + "type": "media", + "multiple": false + }, + "articles": { + "type": "relation", + "relation": "manyToMany", + "target": "api::article.article", + "mappedBy": "authors" + } + } +} diff --git a/e2e/app-template/template/src/api/author/controllers/author.js b/e2e/app-template/template/src/api/author/controllers/author.js new file mode 100644 index 0000000000..93de2e1fa4 --- /dev/null +++ b/e2e/app-template/template/src/api/author/controllers/author.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * author controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::author.author'); diff --git a/e2e/app-template/template/src/api/author/documentation/1.0.0/author.json b/e2e/app-template/template/src/api/author/documentation/1.0.0/author.json new file mode 100644 index 0000000000..767d2cbbd8 --- /dev/null +++ b/e2e/app-template/template/src/api/author/documentation/1.0.0/author.json @@ -0,0 +1,587 @@ +{ + "/authors": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorListResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Author"], + "parameters": [ + { + "name": "sort", + "in": "query", + "description": "Sort by attributes ascending (asc) or descending (desc)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pagination[withCount]", + "in": "query", + "description": "Return page/pageSize (default: true)", + "deprecated": false, + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "pagination[page]", + "in": "query", + "description": "Page number (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[pageSize]", + "in": "query", + "description": "Page size (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[start]", + "in": "query", + "description": "Offset value (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[limit]", + "in": "query", + "description": "Number of entities to return (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "fields", + "in": "query", + "description": "Fields to return (ex: title,author)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "populate", + "in": "query", + "description": "Relations to return", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "filters", + "in": "query", + "description": "Filters to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "object" + }, + "style": "deepObject" + }, + { + "name": "locale", + "in": "query", + "description": "Locale to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + } + ], + "operationId": "get/authors" + }, + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Author"], + "parameters": [], + "operationId": "post/authors", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorRequest" + } + } + } + } + } + }, + "/authors/{id}": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Author"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "get/authors/{id}" + }, + "put": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Author"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "put/authors/{id}", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorRequest" + } + } + } + } + }, + "delete": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Author"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "delete/authors/{id}" + } + }, + "/authors/{id}/localizations": { + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorLocalizationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Author"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "", + "deprecated": false, + "required": true, + "schema": { + "type": "number" + } + } + ], + "operationId": "post/authors/{id}/localizations", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthorLocalizationRequest" + } + } + } + } + } + } +} diff --git a/e2e/app-template/template/src/api/author/routes/author.js b/e2e/app-template/template/src/api/author/routes/author.js new file mode 100644 index 0000000000..934f67b045 --- /dev/null +++ b/e2e/app-template/template/src/api/author/routes/author.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * author router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::author.author'); diff --git a/e2e/app-template/template/src/api/author/services/author.js b/e2e/app-template/template/src/api/author/services/author.js new file mode 100644 index 0000000000..61f296f30d --- /dev/null +++ b/e2e/app-template/template/src/api/author/services/author.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * author service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::author.author'); diff --git a/e2e/app-template/template/src/api/homepage/content-types/homepage/schema.json b/e2e/app-template/template/src/api/homepage/content-types/homepage/schema.json new file mode 100644 index 0000000000..87eb79ddfb --- /dev/null +++ b/e2e/app-template/template/src/api/homepage/content-types/homepage/schema.json @@ -0,0 +1,25 @@ +{ + "kind": "singleType", + "collectionName": "homepages", + "info": { + "singularName": "homepage", + "pluralName": "homepages", + "displayName": "Homepage", + "description": "" + }, + "options": {}, + "pluginOptions": {}, + "attributes": { + "title": { + "type": "string" + }, + "content": { + "type": "blocks" + }, + "admin_user": { + "type": "relation", + "relation": "oneToOne", + "target": "admin::user" + } + } +} diff --git a/e2e/app-template/template/src/api/homepage/controllers/homepage.js b/e2e/app-template/template/src/api/homepage/controllers/homepage.js new file mode 100644 index 0000000000..8deb30ebcf --- /dev/null +++ b/e2e/app-template/template/src/api/homepage/controllers/homepage.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * homepage controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::homepage.homepage'); diff --git a/e2e/app-template/template/src/api/homepage/documentation/1.0.0/homepage.json b/e2e/app-template/template/src/api/homepage/documentation/1.0.0/homepage.json new file mode 100644 index 0000000000..fb983a1ac8 --- /dev/null +++ b/e2e/app-template/template/src/api/homepage/documentation/1.0.0/homepage.json @@ -0,0 +1,397 @@ +{ + "/homepage": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HomepageResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Homepage"], + "parameters": [ + { + "name": "sort", + "in": "query", + "description": "Sort by attributes ascending (asc) or descending (desc)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pagination[withCount]", + "in": "query", + "description": "Return page/pageSize (default: true)", + "deprecated": false, + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "pagination[page]", + "in": "query", + "description": "Page number (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[pageSize]", + "in": "query", + "description": "Page size (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[start]", + "in": "query", + "description": "Offset value (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[limit]", + "in": "query", + "description": "Number of entities to return (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "fields", + "in": "query", + "description": "Fields to return (ex: title,author)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "populate", + "in": "query", + "description": "Relations to return", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "filters", + "in": "query", + "description": "Filters to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "object" + }, + "style": "deepObject" + }, + { + "name": "locale", + "in": "query", + "description": "Locale to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + } + ], + "operationId": "get/homepage" + }, + "put": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HomepageResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Homepage"], + "parameters": [], + "operationId": "put/homepage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HomepageRequest" + } + } + } + } + }, + "delete": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Homepage"], + "parameters": [], + "operationId": "delete/homepage" + } + }, + "/homepage/localizations": { + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HomepageLocalizationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Homepage"], + "parameters": [], + "operationId": "post/homepage/localizations", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HomepageLocalizationRequest" + } + } + } + } + } + } +} diff --git a/e2e/app-template/template/src/api/homepage/routes/homepage.js b/e2e/app-template/template/src/api/homepage/routes/homepage.js new file mode 100644 index 0000000000..40dc32a06a --- /dev/null +++ b/e2e/app-template/template/src/api/homepage/routes/homepage.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * homepage router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::homepage.homepage'); diff --git a/e2e/app-template/template/src/api/homepage/services/homepage.js b/e2e/app-template/template/src/api/homepage/services/homepage.js new file mode 100644 index 0000000000..86c6bd8316 --- /dev/null +++ b/e2e/app-template/template/src/api/homepage/services/homepage.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * homepage service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::homepage.homepage'); diff --git a/e2e/app-template/template/src/api/testing/content-types/testing/schema.json b/e2e/app-template/template/src/api/testing/content-types/testing/schema.json deleted file mode 100644 index 2ca26267f0..0000000000 --- a/e2e/app-template/template/src/api/testing/content-types/testing/schema.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "kind": "collectionType", - "collectionName": "testings", - "info": { - "singularName": "testing", - "pluralName": "testings", - "displayName": "testing" - }, - "options": {}, - "attributes": { - "title": { - "type": "string" - } - } -} diff --git a/e2e/app-template/template/src/api/upcoming-match/content-types/upcoming-match/schema.json b/e2e/app-template/template/src/api/upcoming-match/content-types/upcoming-match/schema.json new file mode 100644 index 0000000000..dd5b192ae2 --- /dev/null +++ b/e2e/app-template/template/src/api/upcoming-match/content-types/upcoming-match/schema.json @@ -0,0 +1,22 @@ +{ + "kind": "singleType", + "collectionName": "upcoming_matches", + "info": { + "singularName": "upcoming-match", + "pluralName": "upcoming-matches", + "displayName": "Upcoming Matches" + }, + "options": {}, + "pluginOptions": {}, + "attributes": { + "title": { + "type": "string" + }, + "number_of_upcoming_matches": { + "type": "integer" + }, + "next_match": { + "type": "date" + } + } +} diff --git a/e2e/app-template/template/src/api/upcoming-match/controllers/upcoming-match.js b/e2e/app-template/template/src/api/upcoming-match/controllers/upcoming-match.js new file mode 100644 index 0000000000..ec179ae35a --- /dev/null +++ b/e2e/app-template/template/src/api/upcoming-match/controllers/upcoming-match.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * upcoming-match controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::upcoming-match.upcoming-match'); diff --git a/e2e/app-template/template/src/api/upcoming-match/documentation/1.0.0/upcoming-match.json b/e2e/app-template/template/src/api/upcoming-match/documentation/1.0.0/upcoming-match.json new file mode 100644 index 0000000000..19b6d6efe9 --- /dev/null +++ b/e2e/app-template/template/src/api/upcoming-match/documentation/1.0.0/upcoming-match.json @@ -0,0 +1,397 @@ +{ + "/upcoming-match": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpcomingMatchResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Upcoming-match"], + "parameters": [ + { + "name": "sort", + "in": "query", + "description": "Sort by attributes ascending (asc) or descending (desc)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pagination[withCount]", + "in": "query", + "description": "Return page/pageSize (default: true)", + "deprecated": false, + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "pagination[page]", + "in": "query", + "description": "Page number (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[pageSize]", + "in": "query", + "description": "Page size (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[start]", + "in": "query", + "description": "Offset value (default: 0)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "pagination[limit]", + "in": "query", + "description": "Number of entities to return (default: 25)", + "deprecated": false, + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "fields", + "in": "query", + "description": "Fields to return (ex: title,author)", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "populate", + "in": "query", + "description": "Relations to return", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "filters", + "in": "query", + "description": "Filters to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "object" + }, + "style": "deepObject" + }, + { + "name": "locale", + "in": "query", + "description": "Locale to apply", + "deprecated": false, + "required": false, + "schema": { + "type": "string" + } + } + ], + "operationId": "get/upcoming-match" + }, + "put": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpcomingMatchResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Upcoming-match"], + "parameters": [], + "operationId": "put/upcoming-match", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpcomingMatchRequest" + } + } + } + } + }, + "delete": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int64" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Upcoming-match"], + "parameters": [], + "operationId": "delete/upcoming-match" + } + }, + "/upcoming-match/localizations": { + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpcomingMatchLocalizationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "tags": ["Upcoming-match"], + "parameters": [], + "operationId": "post/upcoming-match/localizations", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpcomingMatchLocalizationRequest" + } + } + } + } + } + } +} diff --git a/e2e/app-template/template/src/api/upcoming-match/routes/upcoming-match.js b/e2e/app-template/template/src/api/upcoming-match/routes/upcoming-match.js new file mode 100644 index 0000000000..e45d1578e6 --- /dev/null +++ b/e2e/app-template/template/src/api/upcoming-match/routes/upcoming-match.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * upcoming-match router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::upcoming-match.upcoming-match'); diff --git a/e2e/app-template/template/src/api/upcoming-match/services/upcoming-match.js b/e2e/app-template/template/src/api/upcoming-match/services/upcoming-match.js new file mode 100644 index 0000000000..3ae29ec19b --- /dev/null +++ b/e2e/app-template/template/src/api/upcoming-match/services/upcoming-match.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * upcoming-match service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::upcoming-match.upcoming-match'); diff --git a/e2e/constants.js b/e2e/constants.js index 08d53f8c2c..42f5159c83 100644 --- a/e2e/constants.js +++ b/e2e/constants.js @@ -1,6 +1,18 @@ const { CUSTOM_TRANSFER_TOKEN_ACCESS_KEY } = require('./app-template/template/src/constants'); -const ALLOWED_CONTENT_TYPES = ['admin::user', 'admin::role', 'admin::permission']; +const ALLOWED_CONTENT_TYPES = [ + 'admin::user', + 'admin::role', + 'admin::permission', + 'api::article.article', + 'api::author.author', + 'api::homepage.homepage', + 'api::upcoming-match.upcoming-match', + /** + * UPLOADS + */ + 'plugin::upload.file', +]; // TODO: we should start using @strapi.io addresses to have the chance one day to // actually receive and check the emails; also: it is not nice to spam other peoples diff --git a/e2e/data/with-admin.tar b/e2e/data/with-admin.tar index cbdcb3723e..63ca1b2c90 100644 Binary files a/e2e/data/with-admin.tar and b/e2e/data/with-admin.tar differ diff --git a/e2e/scripts/dts-export.js b/e2e/scripts/dts-export.js index d58ab6cac7..9d19084f98 100755 --- a/e2e/scripts/dts-export.js +++ b/e2e/scripts/dts-export.js @@ -32,7 +32,7 @@ const exportData = async () => { const engine = createTransferEngine(source, destination, { versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped - only: ['content'], + only: ['content', 'files'], transforms: { links: [ { diff --git a/e2e/scripts/dts-import.js b/e2e/scripts/dts-import.js index 1f2c48eba7..91bca192c3 100644 --- a/e2e/scripts/dts-import.js +++ b/e2e/scripts/dts-import.js @@ -24,7 +24,7 @@ export const resetDatabaseAndImportDataFromPath = async (filePath) => { const engine = createTransferEngine(source, destination, { versionStrategy: 'ignore', schemaStrategy: 'ignore', - only: ['content'], + only: ['content', 'files'], transforms: { links: [ { @@ -69,6 +69,7 @@ const createDestinationProvider = () => { auth: { type: 'token', token: CUSTOM_TRANSFER_TOKEN_ACCESS_KEY }, strategy: 'restore', restore: { + assets: true, entities: { include: ALLOWED_CONTENT_TYPES, }, diff --git a/e2e/tests/admin/logout.spec.js b/e2e/tests/admin/logout.spec.js index 8dd98dfe9d..cabecc990b 100644 --- a/e2e/tests/admin/logout.spec.js +++ b/e2e/tests/admin/logout.spec.js @@ -11,7 +11,7 @@ test.describe('Log Out', () => { }); test('a user should be able to logout', async ({ page }) => { - await page.getByText('John Smith').click(); + await page.getByText('test testing').click(); await page.getByText('Logout').click(); await expect(page.getByText('Log in to your Strapi account')).toBeVisible(); diff --git a/e2e/tests/content-manager/editview.spec.js b/e2e/tests/content-manager/editview.spec.js deleted file mode 100644 index 404d728672..0000000000 --- a/e2e/tests/content-manager/editview.spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { login } from '../../utils/login'; -import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import'; - -test.describe('Edit View', () => { - test.beforeEach(async ({ page }) => { - await resetDatabaseAndImportDataFromPath('./e2e/data/with-admin.tar'); - await page.goto('/admin'); - await login({ page }); - }); - - /** - * @note There is only one field in this content-type. - */ - test('A user should be able to navigate to the EditView of the content manager to create, save, publish, unpublish & delete a new entry', async ({ - page, - }) => { - await page.getByRole('link', { name: 'Content Manager' }).click(); - await page - .getByRole('link', { name: /Create new entry/ }) - .nth(1) - .click(); - - /** - * Now we're in the edit view. - */ - await page.waitForURL('**/content-manager/collection-types/api::testing.testing/create'); - - await page.getByRole('textbox', { name: 'title' }).fill('my content'); - - await page.getByRole('button', { name: 'Save' }).click(); - - await expect(page.getByText('Saved')).toBeVisible(); - - await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled(); - - await page.getByRole('button', { name: 'Publish' }).click(); - - await expect(page.getByText('Published', { exact: true })).toBeVisible(); - - await expect(page.getByRole('button', { name: 'Unpublish' })).not.toBeDisabled(); - - await page.getByRole('textbox', { name: 'title' }).fill('my content revised'); - - await expect(page.getByRole('button', { name: 'Unpublish' })).toBeDisabled(); - - await page.getByRole('button', { name: 'Save' }).click(); - - await expect(page.getByRole('button', { name: 'Unpublish' })).not.toBeDisabled(); - - await page.getByRole('button', { name: 'Unpublish' }).click(); - - await expect(page.getByRole('dialog', { name: 'Confirmation' })).toBeVisible(); - - await page.getByRole('button', { name: 'Yes, confirm' }).click(); - - await expect(page.getByText('Unpublished')).toBeVisible(); - - await expect(page.getByRole('button', { name: 'Publish' })).not.toBeDisabled(); - - await page.getByRole('button', { name: 'Delete this entry' }).click(); - - await expect(page.getByRole('dialog', { name: 'Confirmation' })).toBeVisible(); - - await page.getByRole('button', { name: 'Confirm' }).click(); - - await expect(page.getByText('Deleted')).toBeVisible(); - - /** - * We're back on the list view - */ - await page.waitForURL( - '**/content-manager/collection-types/api::testing.testing?page=1&pageSize=10&sort=title:ASC' - ); - - await expect(page.getByRole('link', { name: /Create new entry/ }).nth(1)).toBeVisible(); - }); -}); diff --git a/e2e/tests/content-manager/editview.spec.ts b/e2e/tests/content-manager/editview.spec.ts new file mode 100644 index 0000000000..771c6bc631 --- /dev/null +++ b/e2e/tests/content-manager/editview.spec.ts @@ -0,0 +1,177 @@ +import { test, expect } from '@playwright/test'; +import { login } from '../../utils/login'; +import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import'; + +test.describe('Edit View', () => { + test.beforeEach(async ({ page }) => { + await resetDatabaseAndImportDataFromPath('./e2e/data/with-admin.tar'); + await page.goto('/admin'); + await login({ page }); + }); + + test.describe('Collection Type', () => { + test('A user should be able to navigate to the EditView of the content manager to create, save, publish, unpublish & delete a new entry', async ({ + page, + }) => { + await page.getByRole('link', { name: 'Content Manager' }).click(); + await page.getByRole('link', { name: /Create new entry/ }).click(); + + /** + * Now we're in the edit view. + */ + await page.waitForURL('**/content-manager/collection-types/api::article.article/create'); + + await page.getByRole('textbox', { name: 'title' }).fill('Being from Kansas City'); + + await page.getByRole('button', { name: 'Save' }).click(); + + await expect(page.getByText('Saved')).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled(); + + await expect(page.getByText('Editing draft version')).toBeVisible(); + + await page.getByRole('link', { name: 'Content Manager' }).click(); + + await page.waitForURL( + '**/content-manager/collection-types/api::article.article?page=1&pageSize=10&sort=title:ASC' + ); + + await expect(page.getByRole('gridcell', { name: 'Being from Kansas City' })).toBeVisible(); + + await page.getByRole('gridcell', { name: 'Being from Kansas City' }).click(); + + await page.waitForURL('**/content-manager/collection-types/api::article.article/**'); + + await page.getByRole('textbox', { name: 'title' }).fill(''); + + await page.getByRole('textbox', { name: 'title' }).fill('Being an American'); + + await page + .getByRole('textbox') + .nth(1) + .fill('I miss the denver broncos, now I can only watch it on the evening.'); + + await page.getByRole('combobox', { name: 'authors' }).click(); + + await page.getByRole('option', { name: 'Ted Lasso' }).click(); + + await expect(page.getByRole('link', { name: 'Ted Lasso' })).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Publish' })).toBeDisabled(); + + await expect(page.getByRole('button', { name: 'Save' })).not.toBeDisabled(); + await page.getByRole('button', { name: 'Save' }).click(); + await expect(page.getByText('Saved')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled(); + + await expect(page.getByRole('button', { name: 'Publish' })).not.toBeDisabled(); + await page.getByRole('button', { name: 'Publish' }).click(); + await expect(page.getByText('Published', { exact: true })).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Unpublish' })).not.toBeDisabled(); + + await page.getByRole('textbox', { name: 'title' }).fill('Being an American in the UK'); + + await expect(page.getByRole('button', { name: 'Unpublish' })).toBeDisabled(); + + await page.getByRole('button', { name: 'Save' }).click(); + + await expect(page.getByRole('button', { name: 'Unpublish' })).not.toBeDisabled(); + + await page.getByRole('button', { name: 'Unpublish' }).click(); + + await expect(page.getByRole('dialog', { name: 'Confirmation' })).toBeVisible(); + + await page.getByRole('button', { name: 'Yes, confirm' }).click(); + + await expect(page.getByText('Unpublished')).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Publish' })).not.toBeDisabled(); + + await page.getByRole('button', { name: 'Delete this entry' }).click(); + + await expect(page.getByRole('dialog', { name: 'Confirmation' })).toBeVisible(); + + await page.getByRole('button', { name: 'Confirm' }).click(); + + await expect(page.getByText('Deleted')).toBeVisible(); + + /** + * We're back on the list view + */ + await page.waitForURL( + '**/content-manager/collection-types/api::article.article?page=1&pageSize=10&sort=title:ASC' + ); + + await expect( + page.getByRole('gridcell', { name: 'Being from Kansas City' }) + ).not.toBeVisible(); + }); + }); + + test.describe('Single Type', () => { + test('A user should be able to navigate to the EditView of the content manager to create, save, publish, unpublish & delete a new entry', async ({ + page, + }) => { + await page.getByRole('link', { name: 'Content Manager' }).click(); + + await page.getByRole('link', { name: 'Homepage' }).click(); + + /** + * Now we're in the edit view. + */ + await page.waitForURL('**/content-manager/single-types/api::homepage.homepage'); + + await page.getByRole('textbox', { name: 'title' }).fill('Welcome to AFC Richmond'); + + await page + .getByRole('textbox') + .nth(1) + .fill( + "We're a premier league football club based in South West London with a vicious rivalry with Fulham. Because who doens't hate them?" + ); + + await page.getByRole('button', { name: 'Save' }).click(); + + await expect(page.getByText('Saved')).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled(); + + await page.getByRole('button', { name: 'Publish' }).click(); + + await expect(page.getByText('Published', { exact: true })).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Unpublish' })).not.toBeDisabled(); + + await page.getByRole('combobox', { name: 'admin_user' }).click(); + await page.getByRole('option').nth(0).click(); + + await expect(page.getByRole('button', { name: 'Unpublish' })).toBeDisabled(); + + await page.getByRole('button', { name: 'Save' }).click(); + + await expect(page.getByRole('button', { name: 'Unpublish' })).not.toBeDisabled(); + + await page.getByRole('button', { name: 'Unpublish' }).click(); + + await expect(page.getByRole('dialog', { name: 'Confirmation' })).toBeVisible(); + + await page.getByRole('button', { name: 'Yes, confirm' }).click(); + + await expect(page.getByText('Unpublished')).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Publish' })).not.toBeDisabled(); + + await page.getByRole('button', { name: 'Delete this entry' }).click(); + + await expect(page.getByRole('dialog', { name: 'Confirmation' })).toBeVisible(); + + await page.getByRole('button', { name: 'Confirm' }).click(); + + await expect(page.getByText('Deleted')).toBeVisible(); + + await expect(page.getByRole('textbox', { name: 'title' })).toHaveText(''); + }); + }); +}); diff --git a/e2e/tests/content-manager/listview.spec.js b/e2e/tests/content-manager/listview.spec.ts similarity index 92% rename from e2e/tests/content-manager/listview.spec.js rename to e2e/tests/content-manager/listview.spec.ts index 013d6860e6..b1e847e9f6 100644 --- a/e2e/tests/content-manager/listview.spec.js +++ b/e2e/tests/content-manager/listview.spec.ts @@ -18,7 +18,7 @@ test.describe('List View', () => { await page.getByRole('link', { name: 'Content Manager' }).click(); await expect(page).toHaveTitle('Content Manager'); - await expect(page.getByRole('heading', { name: 'testing' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible(); await expect(page.getByRole('link', { name: /Create new entry/ }).first()).toBeVisible(); }); }); diff --git a/packages/core/admin/admin/src/content-manager/components/ContentTypeFormWrapper.tsx b/packages/core/admin/admin/src/content-manager/components/ContentTypeFormWrapper.tsx index 62fc41146c..1a22be9b06 100644 --- a/packages/core/admin/admin/src/content-manager/components/ContentTypeFormWrapper.tsx +++ b/packages/core/admin/admin/src/content-manager/components/ContentTypeFormWrapper.tsx @@ -101,7 +101,7 @@ const ContentTypeFormWrapper = ({ const { put, post, del } = fetchClient; const isSingleType = collectionType === 'single-types'; - const isCreatingEntry = !isSingleType && !id; + const isCreatingEntry = !isSingleType && id === 'create'; const requestURL = isCreatingEntry && !origin diff --git a/packages/core/admin/ee/server/src/controllers/index.ts b/packages/core/admin/ee/server/src/controllers/index.ts index d98d44f1cb..fabe1ca94f 100644 --- a/packages/core/admin/ee/server/src/controllers/index.ts +++ b/packages/core/admin/ee/server/src/controllers/index.ts @@ -1,4 +1,4 @@ -import 'koa-bodyparser'; +import type {} from 'koa-body'; import authentication from './authentication'; import role from './role'; diff --git a/packages/core/admin/ee/server/tsconfig.json b/packages/core/admin/ee/server/tsconfig.json index f0cd442737..0c7e409ca3 100644 --- a/packages/core/admin/ee/server/tsconfig.json +++ b/packages/core/admin/ee/server/tsconfig.json @@ -2,10 +2,7 @@ "extends": "tsconfig/base.json", "include": ["src"], "compilerOptions": { - "types": ["lodash", "jest"], "rootDir": "../../", - "baseUrl": ".", - "esModuleInterop": true, - "allowJs": true + "baseUrl": "." } } diff --git a/packages/core/admin/package.json b/packages/core/admin/package.json index 949de3e27d..14b33c704a 100644 --- a/packages/core/admin/package.json +++ b/packages/core/admin/package.json @@ -92,7 +92,6 @@ "js-cookie": "2.2.1", "jsonwebtoken": "9.0.0", "koa": "2.13.4", - "koa-bodyparser": "4.4.1", "koa-compose": "4.1.0", "koa-passport": "5.0.0", "koa-static": "5.0.0", @@ -162,6 +161,7 @@ "@types/prettier": "2.7.3", "@types/react-window": "1.8.8", "@types/sanitize-html": "2.9.5", + "koa-body": "4.2.0", "msw": "1.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/core/admin/server/src/controllers/admin.ts b/packages/core/admin/server/src/controllers/admin.ts index c33ffdc1b1..b8e1c2d77b 100644 --- a/packages/core/admin/server/src/controllers/admin.ts +++ b/packages/core/admin/server/src/controllers/admin.ts @@ -26,16 +26,6 @@ import type { const { isUsingTypeScript } = tsUtils; -/* TODO extend the request in this way once Replace is available - type FileRequest = Context['request'] & { file: unknown }; - type FileContext = Replace; -*/ -declare module 'koa' { - interface Request { - files: unknown; - } -} - /** * A set of functions called "actions" for `Admin` */ diff --git a/packages/core/admin/server/src/controllers/index.ts b/packages/core/admin/server/src/controllers/index.ts index 8079f532d1..45dfd9eb1f 100644 --- a/packages/core/admin/server/src/controllers/index.ts +++ b/packages/core/admin/server/src/controllers/index.ts @@ -1,4 +1,4 @@ -import 'koa-bodyparser'; +import type {} from 'koa-body'; import admin from './admin'; import apiToken from './api-token'; diff --git a/packages/core/content-manager/package.json b/packages/core/content-manager/package.json index 2e2175be25..0bc8280183 100644 --- a/packages/core/content-manager/package.json +++ b/packages/core/content-manager/package.json @@ -52,14 +52,14 @@ "@strapi/types": "4.17.1", "@strapi/utils": "4.17.1", "koa": "2.13.4", - "koa-bodyparser": "4.4.1", "lodash": "4.17.21", "qs": "6.11.1" }, "devDependencies": { "@strapi/pack-up": "workspace:*", "@types/jest": "29.5.2", - "@types/lodash": "^4.14.191" + "@types/lodash": "^4.14.191", + "koa-body": "4.2.0" }, "engines": { "node": ">=18.0.0 <=20.x.x", diff --git a/packages/core/content-manager/server/src/services/uid.ts b/packages/core/content-manager/server/src/services/uid.ts index 0b60433562..a3d8af952d 100644 --- a/packages/core/content-manager/server/src/services/uid.ts +++ b/packages/core/content-manager/server/src/services/uid.ts @@ -31,7 +31,10 @@ export default ({ strapi }: { strapi: Strapi }) => ({ return this.findUniqueUID({ contentTypeUID, field, - value: slugify(defaultValue || contentType.modelName, options), + value: slugify( + _.isFunction(defaultValue) ? defaultValue() : defaultValue || contentType.modelName, + options + ), }); }, diff --git a/packages/core/content-type-builder/package.json b/packages/core/content-type-builder/package.json index 5bd2e1b43f..5ea23d2ca6 100644 --- a/packages/core/content-type-builder/package.json +++ b/packages/core/content-type-builder/package.json @@ -65,7 +65,6 @@ "@strapi/utils": "4.17.1", "fs-extra": "10.1.0", "immer": "9.0.19", - "koa-bodyparser": "4.4.1", "lodash": "4.17.21", "pluralize": "8.0.0", "prop-types": "^15.8.1", @@ -80,9 +79,9 @@ "@strapi/types": "4.17.1", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.4.3", - "@types/koa-bodyparser": "4.3.12", "@types/pluralize": "0.0.30", "koa": "2.13.4", + "koa-body": "4.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "6.21.1", diff --git a/packages/core/content-type-builder/server/src/controllers/content-types.ts b/packages/core/content-type-builder/server/src/controllers/content-types.ts index 37e748c7a3..0f0ab73783 100644 --- a/packages/core/content-type-builder/server/src/controllers/content-types.ts +++ b/packages/core/content-type-builder/server/src/controllers/content-types.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import type { Context } from 'koa'; -import 'koa-bodyparser'; +import type {} from 'koa-body'; import type { UID } from '@strapi/types'; import { getService } from '../utils'; import { diff --git a/packages/core/core/src/Strapi.ts b/packages/core/core/src/Strapi.ts index b93ab0bf60..55ea3a7046 100644 --- a/packages/core/core/src/Strapi.ts +++ b/packages/core/core/src/Strapi.ts @@ -178,6 +178,9 @@ class Strapi extends Container implements StrapiI { features: FeaturesService; + // @ts-expect-error - Assigned in constructor + ee: StrapiI['ee']; + constructor(opts: StrapiOptions = {}) { super(); diff --git a/packages/core/core/src/load/glob.ts b/packages/core/core/src/load/glob.ts deleted file mode 100644 index 02ecc72998..0000000000 --- a/packages/core/core/src/load/glob.ts +++ /dev/null @@ -1,15 +0,0 @@ -import glob, { IOptions } from 'glob'; - -/** - * Promise based glob - */ -function promiseGlob(...args: [string, IOptions]): Promise { - return new Promise((resolve, reject) => { - glob(...args, (err, files) => { - if (err) return reject(err); - resolve(files); - }); - }); -} - -export default promiseGlob; diff --git a/packages/core/core/src/utils/fetch.ts b/packages/core/core/src/utils/fetch.ts index 07e105ef05..75c9646626 100644 --- a/packages/core/core/src/utils/fetch.ts +++ b/packages/core/core/src/utils/fetch.ts @@ -2,6 +2,7 @@ import type { Fetch, Strapi } from '@strapi/types'; import { ProxyAgent } from 'undici'; // TODO: once core Node exposes a stable way to create a ProxyAgent we will use that instead of undici +export type { Fetch }; // Create a wrapper for Node's Fetch API that applies a global proxy export function createStrapiFetch(strapi: Strapi): Fetch { diff --git a/packages/core/core/src/utils/transform-content-types-to-models.ts b/packages/core/core/src/utils/transform-content-types-to-models.ts index ac183626cc..f5a385109b 100644 --- a/packages/core/core/src/utils/transform-content-types-to-models.ts +++ b/packages/core/core/src/utils/transform-content-types-to-models.ts @@ -28,7 +28,6 @@ export const transformContentTypesToModels = ( ): DatabaseConfig['models'] => { return contentTypes.map((contentType) => { // Add document id to content types - // @ts-expect-error - `default` function is not typed into `Attribute` // as it is not documented const documentIdAttribute: Record = contentType.modelType === 'contentType' diff --git a/packages/core/data-transfer/src/strapi/remote/handlers/utils.ts b/packages/core/data-transfer/src/strapi/remote/handlers/utils.ts index 51997f019e..ecb8352838 100644 --- a/packages/core/data-transfer/src/strapi/remote/handlers/utils.ts +++ b/packages/core/data-transfer/src/strapi/remote/handlers/utils.ts @@ -1,3 +1,4 @@ +import type { IncomingMessage } from 'node:http'; import { randomUUID } from 'crypto'; import type { Context } from 'koa'; import type { RawData, ServerOptions } from 'ws'; @@ -9,7 +10,7 @@ import { ProviderError, ProviderTransferError } from '../../../errors/providers' import { VALID_TRANSFER_COMMANDS, ValidTransferCommand } from './constants'; import { TransferMethod } from '../constants'; -type WSCallback = (client: WebSocket, request: Response) => void; +type WSCallback = (client: WebSocket, request: IncomingMessage) => void; export interface HandlerOptions { verify: (ctx: Context, scope?: TransferMethod) => Promise; diff --git a/packages/core/email/package.json b/packages/core/email/package.json index d19e797b52..21332d2a7a 100644 --- a/packages/core/email/package.json +++ b/packages/core/email/package.json @@ -70,6 +70,7 @@ "@types/koa": "2.13.4", "@types/lodash": "^4.14.191", "koa": "2.13.4", + "koa-body": "4.2.0", "msw": "1.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/core/email/server/src/controllers/email.ts b/packages/core/email/server/src/controllers/email.ts index cd1a2d45a4..90b2882e5c 100644 --- a/packages/core/email/server/src/controllers/email.ts +++ b/packages/core/email/server/src/controllers/email.ts @@ -2,6 +2,7 @@ import { pick } from 'lodash/fp'; import { errors } from '@strapi/utils'; import type Koa from 'koa'; +import type {} from 'koa-body'; import type { EmailConfig, SendOptions } from '../types'; const { ApplicationError } = errors; @@ -13,7 +14,7 @@ const { ApplicationError } = errors; */ const emailController = { async send(ctx: Koa.Context) { - const options: SendOptions = ctx.request.body; + const options = ctx.request.body as SendOptions; try { await strapi.plugin('email').service('email').send(options); @@ -32,7 +33,7 @@ const emailController = { }, async test(ctx: Koa.Context) { - const { to } = ctx.request.body; + const { to } = ctx.request.body as Pick; if (!to) { throw new ApplicationError('No recipient(s) are given'); diff --git a/packages/core/email/server/tsconfig.build.json b/packages/core/email/server/tsconfig.build.json index 69f14252c1..9d2b729786 100644 --- a/packages/core/email/server/tsconfig.build.json +++ b/packages/core/email/server/tsconfig.build.json @@ -1,7 +1,7 @@ { - "extends": "./server/tsconfig.json", + "extends": "./tsconfig.json", "include": ["./src", "../shared"], - "exclude": ["./server/src/**/*.test.ts"], + "exclude": ["node_modules", "**/*.test.ts"], "compilerOptions": { "rootDir": "../", "baseUrl": ".", diff --git a/packages/core/email/server/tsconfig.eslint.json b/packages/core/email/server/tsconfig.eslint.json index b531808514..7f43fc1682 100644 --- a/packages/core/email/server/tsconfig.eslint.json +++ b/packages/core/email/server/tsconfig.eslint.json @@ -3,6 +3,6 @@ "compilerOptions": { "noEmit": true }, - "include": ["src"], + "include": ["src", "../shared"], "exclude": ["node_modules"] } diff --git a/packages/core/email/server/tsconfig.json b/packages/core/email/server/tsconfig.json index 25009b641d..a7248ede4d 100644 --- a/packages/core/email/server/tsconfig.json +++ b/packages/core/email/server/tsconfig.json @@ -3,7 +3,6 @@ "include": ["./src", "../shared"], "compilerOptions": { "rootDir": "../", - "baseUrl": ".", - "esModuleInterop": true + "baseUrl": "." } } diff --git a/packages/core/helper-plugin/package.json b/packages/core/helper-plugin/package.json index 7ede5e9951..776e9e1761 100644 --- a/packages/core/helper-plugin/package.json +++ b/packages/core/helper-plugin/package.json @@ -73,6 +73,7 @@ "@strapi/icons": "1.14.1", "@strapi/pack-up": "4.17.1", "@strapi/types": "4.17.1", + "@strapi/utils": "4.17.1", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.4.3", "cross-env": "^7.0.3", diff --git a/packages/core/strapi/package.json b/packages/core/strapi/package.json index cdeb1c34a4..74a0ecfe68 100644 --- a/packages/core/strapi/package.json +++ b/packages/core/strapi/package.json @@ -173,9 +173,12 @@ }, "devDependencies": { "@strapi/pack-up": "workspace:*", + "@types/find-root": "1.1.4", "@types/jest": "29.5.2", "@types/lodash": "^4.14.191", "@types/node": "18.18.4", + "@types/webpack-bundle-analyzer": "4.6.3", + "@types/webpack-hot-middleware": "2.25.9", "eslint-config-custom": "4.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/packages/core/strapi/src/node/create-build-context.ts b/packages/core/strapi/src/node/create-build-context.ts index 827dc76684..c22bcb2f02 100644 --- a/packages/core/strapi/src/node/create-build-context.ts +++ b/packages/core/strapi/src/node/create-build-context.ts @@ -17,6 +17,8 @@ interface BaseOptions { stats?: boolean; minify?: boolean; sourcemaps?: boolean; + bundler?: 'webpack' | 'vite'; + open?: boolean; } interface BuildContext extends BaseContext { @@ -51,7 +53,7 @@ const DEFAULT_BROWSERSLIST = [ 'not dead', ]; -const createBuildContext = async ({ +const createBuildContext = async ({ cwd, logger, tsconfig, diff --git a/packages/core/strapi/src/node/webpack/watch.ts b/packages/core/strapi/src/node/webpack/watch.ts index 65ecf7a36e..9f6284c2e8 100644 --- a/packages/core/strapi/src/node/webpack/watch.ts +++ b/packages/core/strapi/src/node/webpack/watch.ts @@ -23,7 +23,6 @@ const watch = async (ctx: BuildContext): Promise => { const devMiddleware = webpackDevMiddleware(compiler); - // @ts-expect-error ignored const hotMiddleware = webpackHotMiddleware(compiler, { log: false, path: '/__webpack_hmr', diff --git a/packages/core/types/src/modules/entity-service/params/populate.ts b/packages/core/types/src/modules/entity-service/params/populate.ts index 66ca633612..37458447e8 100644 --- a/packages/core/types/src/modules/entity-service/params/populate.ts +++ b/packages/core/types/src/modules/entity-service/params/populate.ts @@ -56,7 +56,7 @@ type GetPopulatableKeysWithoutTarget = Exc * Fragment populate notation for polymorphic attributes */ export type Fragment = { - on?: { [TSchemaUID in TMaybeTargets]?: boolean | NestedParams }; + on?: { [key: string]: boolean | NestedParams }; }; type PopulateClause< @@ -108,9 +108,9 @@ export type ObjectNotation = [ } >, // Loose fallback when registries are not extended - | { [TKey in string]?: boolean | NestedParams } + | { [key: string]: boolean | NestedParams } | { - [TKey in string]?: + [key: string]: | boolean | Fragment // TODO: V5: Remove root-level nested params for morph data structures and only allow fragments diff --git a/packages/core/types/src/modules/entity-service/params/sort.ts b/packages/core/types/src/modules/entity-service/params/sort.ts index 168abf3b20..9c8c5447d6 100644 --- a/packages/core/types/src/modules/entity-service/params/sort.ts +++ b/packages/core/types/src/modules/entity-service/params/sort.ts @@ -61,7 +61,9 @@ export type StringNotation = * type E = [42]; // ❌ * type F = 'title'; // ❌ */ -export type ArrayNotation = Any[]; +export type ArrayNotation = + | StringNotation[] + | ObjectNotation[]; /** * Object notation for a sort @@ -74,15 +76,21 @@ export type ArrayNotation = Any = { - [TKey in ObjectNotationKeys]?: TKey extends SingleAttribute - ? // First level sort (scalar attributes, id, ...) - OrderKind.Any - : TKey extends Attribute.GetKeysWithTarget - ? // Deep sort (relations with a target, components, media, ...) - ObjectNotation> - : never; -}; +export type ObjectNotation = Utils.Expression.If< + Common.AreSchemaRegistriesExtended, + { + [TKey in ObjectNotationKeys]?: TKey extends SingleAttribute + ? // First level sort (scalar attributes, id, ...) + OrderKind.Any + : TKey extends Attribute.GetKeysWithTarget + ? // Deep sort (relations with a target, components, media, ...) + ObjectNotation> + : never; + }, + { + [key: string]: OrderKind.Any | ObjectNotation; + } +>; /** * Represents the keys of an object notation for a sort diff --git a/packages/core/types/src/types/core/attributes/base.ts b/packages/core/types/src/types/core/attributes/base.ts index fafbfb68d4..e76fb9574d 100644 --- a/packages/core/types/src/types/core/attributes/base.ts +++ b/packages/core/types/src/types/core/attributes/base.ts @@ -61,7 +61,7 @@ export interface UniqueOption { } export interface DefaultOption { - default?: T; + default?: T | (() => T); } export interface ConfigurableOption { diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index d5f87c6815..ef2ea004bc 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -56,7 +56,6 @@ }, "devDependencies": { "@strapi/pack-up": "4.17.1", - "@strapi/types": "4.17.1", "@types/koa": "2.13.4", "@types/node": "18.18.4", "eslint-config-custom": "4.17.1", diff --git a/packages/core/utils/src/__tests__/query-populate.test.ts b/packages/core/utils/src/__tests__/query-populate.test.ts index 8e423b3c77..aecf7e56c0 100644 --- a/packages/core/utils/src/__tests__/query-populate.test.ts +++ b/packages/core/utils/src/__tests__/query-populate.test.ts @@ -1,5 +1,4 @@ import { traverseQueryPopulate } from '../traverse'; -import { setGlobalStrapi, getStrapiFactory } from './test-utils'; describe('traverseQueryPopulate', () => { test('should return an empty object incase no populatable field exists', async () => { @@ -18,7 +17,7 @@ describe('traverseQueryPopulate', () => { }); test('should return all populatable fields', async () => { - const strapi = getStrapiFactory({ + const strapi = { getModel: jest.fn((uid) => { return { uid, @@ -39,9 +38,9 @@ describe('traverseQueryPopulate', () => { })), }, }, - })(); + } as any; - setGlobalStrapi(strapi); + global.strapi = strapi; const query = await traverseQueryPopulate(jest.fn(), { schema: { @@ -68,7 +67,7 @@ describe('traverseQueryPopulate', () => { }); test('should return only selected populatable field', async () => { - const strapi = getStrapiFactory({ + const strapi = { getModel: jest.fn((uid) => { return { uid, @@ -88,9 +87,9 @@ describe('traverseQueryPopulate', () => { })), }, }, - })(); + } as any; - setGlobalStrapi(strapi); + global.strapi = strapi; const query = await traverseQueryPopulate(jest.fn(), { schema: { @@ -117,7 +116,7 @@ describe('traverseQueryPopulate', () => { }); test('should populate dynamiczone', async () => { - const strapi = getStrapiFactory({ + const strapi = { getModel: jest.fn((uid) => { return { uid, @@ -137,9 +136,9 @@ describe('traverseQueryPopulate', () => { })), }, }, - })(); + } as any; - setGlobalStrapi(strapi); + global.strapi = strapi; const query = await traverseQueryPopulate(jest.fn(), { schema: { @@ -174,7 +173,7 @@ describe('traverseQueryPopulate', () => { }); test('should deep populate dynamiczone components', async () => { - const strapi = getStrapiFactory({ + const strapi = { getModel: jest.fn((uid) => { if (uid === 'blog.test-como') { return { @@ -224,9 +223,9 @@ describe('traverseQueryPopulate', () => { })), }, }, - })(); + } as any; - setGlobalStrapi(strapi); + global.strapi = strapi; const query = await traverseQueryPopulate(jest.fn(), { schema: { diff --git a/packages/core/utils/src/__tests__/test-utils.ts b/packages/core/utils/src/__tests__/test-utils.ts deleted file mode 100644 index 2a3c1e51f7..0000000000 --- a/packages/core/utils/src/__tests__/test-utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { LoadedStrapi } from '@strapi/types'; - -/** - * Update the global store with the given strapi value - */ -export const setGlobalStrapi = (strapi: LoadedStrapi): void => { - (global as unknown as Global).strapi = strapi; -}; - -/** - * Create a "Strapi" like object factory based on the - * given params and cast it to the correct type - */ -export const getStrapiFactory = - < - T extends { - [key in keyof Partial]: unknown; - } - >( - properties?: T - ) => - (additionalProperties?: Partial) => { - return { ...properties, ...additionalProperties } as LoadedStrapi; - }; diff --git a/packages/core/utils/src/convert-query-params.ts b/packages/core/utils/src/convert-query-params.ts index c69086a4d0..461abedb9f 100644 --- a/packages/core/utils/src/convert-query-params.ts +++ b/packages/core/utils/src/convert-query-params.ts @@ -43,15 +43,22 @@ type FieldsParams = string | string[]; type FiltersParams = unknown; export interface PopulateAttributesParams { - [key: string]: PopulateObjectParams; + [key: string]: boolean | PopulateObjectParams; } export interface PopulateObjectParams { sort?: SortParams; fields?: FieldsParams; filters?: FiltersParams; - populate?: PopulateParams; + populate?: string | string[] | PopulateAttributesParams; publicationState?: 'live' | 'preview'; - on: PopulateAttributesParams; + on?: PopulateAttributesParams; + count?: boolean; + ordering?: unknown; + _q?: string; + limit?: number | string; + start?: number | string; + page?: number | string; + pageSize?: number | string; } type PopulateParams = string | string[] | PopulateAttributesParams; @@ -314,6 +321,12 @@ const convertPopulateQueryParams = ( throw new InvalidPopulateError(); }; +const hasFragmentPopulateDefined = ( + populate: PopulateObjectParams +): populate is PopulateObjectParams & Required> => { + return typeof populate === 'object' && 'on' in populate && !isNil(populate.on); +}; + const convertPopulateObject = (populate: PopulateAttributesParams, schema?: Model) => { if (!schema) { return {}; @@ -322,6 +335,10 @@ const convertPopulateObject = (populate: PopulateAttributesParams, schema?: Mode const { attributes } = schema; return Object.entries(populate).reduce((acc, [key, subPopulate]) => { + if (_.isBoolean(subPopulate)) { + return { ...acc, [key]: subPopulate }; + } + const attribute = attributes[key]; if (!attribute) { @@ -332,10 +349,7 @@ const convertPopulateObject = (populate: PopulateAttributesParams, schema?: Mode const isAllowedAttributeForFragmentPopulate = isDynamicZoneAttribute(attribute) || isMorphToRelationalAttribute(attribute); - const hasFragmentPopulateDefined = - typeof subPopulate === 'object' && 'on' in subPopulate && !isNil(subPopulate.on); - - if (isAllowedAttributeForFragmentPopulate && hasFragmentPopulateDefined) { + if (isAllowedAttributeForFragmentPopulate && hasFragmentPopulateDefined(subPopulate)) { return { ...acc, [key]: { @@ -408,7 +422,7 @@ const convertPopulateObject = (populate: PopulateAttributesParams, schema?: Mode }, {}); }; -const convertNestedPopulate = (subPopulate: PopulateObjectParams, schema?: Model) => { +const convertNestedPopulate = (subPopulate: boolean | PopulateObjectParams, schema?: Model) => { if (_.isString(subPopulate)) { return parseType({ type: 'boolean', value: subPopulate, forceCast: true }); } @@ -422,7 +436,7 @@ const convertNestedPopulate = (subPopulate: PopulateObjectParams, schema?: Model } const { sort, filters, fields, populate, count, ordering, page, pageSize, start, limit } = - subPopulate; + subPopulate as PopulateObjectParams; const query: Query = {}; diff --git a/packages/plugins/graphql/package.json b/packages/plugins/graphql/package.json index 70a90f2071..68f10fe314 100644 --- a/packages/plugins/graphql/package.json +++ b/packages/plugins/graphql/package.json @@ -73,6 +73,7 @@ "@strapi/types": "4.17.1", "@types/graphql-depth-limit": "1.1.5", "@types/graphql-upload": "15.0.2", + "@types/koa-bodyparser": "4.3.12", "@types/koa__cors": "5.0.0", "cross-env": "^7.0.3", "eslint-config-custom": "4.17.1", diff --git a/packages/plugins/i18n/package.json b/packages/plugins/i18n/package.json index 9d857d052b..e24b3c2b30 100644 --- a/packages/plugins/i18n/package.json +++ b/packages/plugins/i18n/package.json @@ -76,7 +76,6 @@ "@strapi/types": "4.17.1", "@testing-library/react": "14.0.0", "@testing-library/user-event": "14.4.3", - "@types/koa-bodyparser": "4.3.12", "koa": "2.13.4", "msw": "1.3.0", "react": "^18.2.0", diff --git a/packages/utils/pack-up/src/node/build.ts b/packages/utils/pack-up/src/node/build.ts index e58bbbaec9..9e60a63242 100644 --- a/packages/utils/pack-up/src/node/build.ts +++ b/packages/utils/pack-up/src/node/build.ts @@ -154,6 +154,8 @@ const build = async (opts: BuildOptions = {}) => { }, error(err) { handler.fail(ctx, task, err); + // exit as soon as one task fails + process.exit(1); }, }); } diff --git a/packages/utils/pack-up/src/node/tasks/dts/build.ts b/packages/utils/pack-up/src/node/tasks/dts/build.ts index 89f7e6437d..e6a51121c8 100644 --- a/packages/utils/pack-up/src/node/tasks/dts/build.ts +++ b/packages/utils/pack-up/src/node/tasks/dts/build.ts @@ -63,14 +63,12 @@ const dtsBuildTask: TaskHandler = { printDiagnostic(diagnostic, { logger: ctx.logger, cwd: ctx.cwd }); } - if (emitResult.emitSkipped) { - const errors = allDiagnostics.filter( - (diag) => diag.category === ts.DiagnosticCategory.Error - ); + const errors = allDiagnostics.filter( + (diag) => diag.category === ts.DiagnosticCategory.Error + ); - if (errors.length) { - throw new Error('Failed to compile TypeScript definitions'); - } + if (errors.length) { + throw new Error('Failed to compile TypeScript definitions'); } }) ) @@ -99,8 +97,6 @@ const dtsBuildTask: TaskHandler = { if (isError(err)) { ctx.logger.error(err.message); } - - process.exit(1); }, }; diff --git a/yarn.lock b/yarn.lock index bee5efde4b..9c7ccd5689 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9261,7 +9261,7 @@ __metadata: js-cookie: "npm:2.2.1" jsonwebtoken: "npm:9.0.0" koa: "npm:2.13.4" - koa-bodyparser: "npm:4.4.1" + koa-body: "npm:4.2.0" koa-compose: "npm:4.1.0" koa-passport: "npm:5.0.0" koa-static: "npm:5.0.0" @@ -9605,6 +9605,7 @@ __metadata: "@strapi/icons": "npm:1.14.1" "@strapi/pack-up": "npm:4.17.1" "@strapi/types": "npm:4.17.1" + "@strapi/utils": "npm:4.17.1" "@testing-library/react": "npm:14.0.0" "@testing-library/user-event": "npm:14.4.3" axios: "npm:1.6.0" @@ -9781,7 +9782,7 @@ __metadata: "@types/jest": "npm:29.5.2" "@types/lodash": "npm:^4.14.191" koa: "npm:2.13.4" - koa-bodyparser: "npm:4.4.1" + koa-body: "npm:4.2.0" lodash: "npm:4.17.21" qs: "npm:6.11.1" languageName: unknown @@ -9802,12 +9803,11 @@ __metadata: "@strapi/utils": "npm:4.17.1" "@testing-library/react": "npm:14.0.0" "@testing-library/user-event": "npm:14.4.3" - "@types/koa-bodyparser": "npm:4.3.12" "@types/pluralize": "npm:0.0.30" fs-extra: "npm:10.1.0" immer: "npm:9.0.19" koa: "npm:2.13.4" - koa-bodyparser: "npm:4.4.1" + koa-body: "npm:4.2.0" lodash: "npm:4.17.21" pluralize: "npm:8.0.0" prop-types: "npm:^15.8.1" @@ -9884,6 +9884,7 @@ __metadata: "@types/koa": "npm:2.13.4" "@types/lodash": "npm:^4.14.191" koa: "npm:2.13.4" + koa-body: "npm:4.2.0" lodash: "npm:4.17.21" msw: "npm:1.3.0" prop-types: "npm:^15.8.1" @@ -9920,6 +9921,7 @@ __metadata: "@strapi/utils": "npm:4.17.1" "@types/graphql-depth-limit": "npm:1.1.5" "@types/graphql-upload": "npm:15.0.2" + "@types/koa-bodyparser": "npm:4.3.12" "@types/koa__cors": "npm:5.0.0" cross-env: "npm:^7.0.3" eslint-config-custom: "npm:4.17.1" @@ -9964,7 +9966,6 @@ __metadata: "@strapi/utils": "npm:4.17.1" "@testing-library/react": "npm:14.0.0" "@testing-library/user-event": "npm:14.4.3" - "@types/koa-bodyparser": "npm:4.3.12" axios: "npm:1.6.0" formik: "npm:2.4.0" immer: "npm:9.0.19" @@ -10243,10 +10244,13 @@ __metadata: "@strapi/types": "npm:4.17.1" "@strapi/typescript-utils": "npm:4.17.1" "@strapi/utils": "npm:4.17.1" + "@types/find-root": "npm:1.1.4" "@types/jest": "npm:29.5.2" "@types/lodash": "npm:^4.14.191" "@types/node": "npm:18.18.4" "@types/nodemon": "npm:1.19.6" + "@types/webpack-bundle-analyzer": "npm:4.6.3" + "@types/webpack-hot-middleware": "npm:2.25.9" "@vitejs/plugin-react-swc": "npm:3.5.0" boxen: "npm:5.1.2" browserslist: "npm:^4.22.2" @@ -10422,7 +10426,6 @@ __metadata: dependencies: "@sindresorhus/slugify": "npm:1.1.0" "@strapi/pack-up": "npm:4.17.1" - "@strapi/types": "npm:4.17.1" "@types/koa": "npm:2.13.4" "@types/node": "npm:18.18.4" date-fns: "npm:2.30.0" @@ -11207,6 +11210,13 @@ __metadata: languageName: node linkType: hard +"@types/find-root@npm:1.1.4": + version: 1.1.4 + resolution: "@types/find-root@npm:1.1.4" + checksum: e438c62b3ef3b706d058764797ec5227479ed4be98e9fec5456fb3b9a2096ccd3e35a84c94e2018278e22c651f12ae6ff4d6d7006b09fa5132da3612e2118076 + languageName: node + linkType: hard + "@types/fined@npm:*": version: 1.1.3 resolution: "@types/fined@npm:1.1.3" @@ -12197,6 +12207,28 @@ __metadata: languageName: node linkType: hard +"@types/webpack-bundle-analyzer@npm:4.6.3": + version: 4.6.3 + resolution: "@types/webpack-bundle-analyzer@npm:4.6.3" + dependencies: + "@types/node": "npm:*" + tapable: "npm:^2.2.0" + webpack: "npm:^5" + checksum: 646b78aa5e06094b9558d49826fcecff6d7c67ab5e02120b9567a4cec904aaebb820ffac5f57963aada98a0603ae56c8aebfd9b54e8bb0597540e6687da063ac + languageName: node + linkType: hard + +"@types/webpack-hot-middleware@npm:2.25.9": + version: 2.25.9 + resolution: "@types/webpack-hot-middleware@npm:2.25.9" + dependencies: + "@types/connect": "npm:*" + tapable: "npm:^2.2.0" + webpack: "npm:^5" + checksum: bfa30ed24fcad7f4e4bce956ee7f2ddcc332428be38c2fa3613e8c53bd077d19806dc801de9c36cb77b966b38c9c9e27ef0367da25b684237f793955e406b657 + languageName: node + linkType: hard + "@types/ws@npm:^8.5.4": version: 8.5.4 resolution: "@types/ws@npm:8.5.4" @@ -28222,7 +28254,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.11.1, qs@npm:^6.10.0, qs@npm:^6.10.2, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.4.0, qs@npm:^6.5.2, qs@npm:^6.9.6": +"qs@npm:6.11.1, qs@npm:^6.10.0, qs@npm:^6.10.2, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.4.0, qs@npm:^6.9.6": version: 6.11.1 resolution: "qs@npm:6.11.1" dependencies: @@ -28231,6 +28263,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.5.2": + version: 6.11.2 + resolution: "qs@npm:6.11.2" + dependencies: + side-channel: "npm:^1.0.4" + checksum: f2321d0796664d0f94e92447ccd3bdfd6b6f3a50b6b762aa79d7f5b1ea3a7a9f94063ba896b82bc2a877ed6a7426d4081e4f16568fdb04f0ee188cca9d8505b4 + languageName: node + linkType: hard + "qs@npm:~6.5.2": version: 6.5.3 resolution: "qs@npm:6.5.3" @@ -28289,7 +28330,7 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.1, raw-body@npm:^2.2.0, raw-body@npm:^2.3.3": +"raw-body@npm:2.5.1, raw-body@npm:^2.2.0": version: 2.5.1 resolution: "raw-body@npm:2.5.1" dependencies: @@ -28301,6 +28342,18 @@ __metadata: languageName: node linkType: hard +"raw-body@npm:^2.3.3": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: 863b5171e140546a4d99f349b720abac4410338e23df5e409cfcc3752538c9caf947ce382c89129ba976f71894bd38b5806c774edac35ebf168d02aa1ac11a95 + languageName: node + linkType: hard + "rc@npm:1.2.8, rc@npm:^1.2.7, rc@npm:^1.2.8": version: 1.2.8 resolution: "rc@npm:1.2.8" @@ -33331,7 +33384,7 @@ __metadata: languageName: node linkType: hard -"webpack@npm:^5.89.0": +"webpack@npm:^5, webpack@npm:^5.89.0": version: 5.89.0 resolution: "webpack@npm:5.89.0" dependencies: