@strapi/plugin-documentation 0.0.0-00a3f69152eb918683ed5c05bfed9c45495c0a87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +151 -0
  3. package/__mocks__/strapi.js +41 -0
  4. package/__tests__/build-component-schema.test.js +271 -0
  5. package/admin/src/components/FieldActionWrapper/index.js +14 -0
  6. package/admin/src/components/PluginIcon/index.js +12 -0
  7. package/admin/src/index.js +80 -0
  8. package/admin/src/pages/PluginPage/index.js +199 -0
  9. package/admin/src/pages/PluginPage/tests/index.test.js +873 -0
  10. package/admin/src/pages/PluginPage/tests/server.js +23 -0
  11. package/admin/src/pages/SettingsPage/index.js +181 -0
  12. package/admin/src/pages/SettingsPage/tests/index.test.js +612 -0
  13. package/admin/src/pages/SettingsPage/tests/server.js +18 -0
  14. package/admin/src/pages/utils/api.js +31 -0
  15. package/admin/src/pages/utils/schema.js +11 -0
  16. package/admin/src/pages/utils/useReactQuery.js +46 -0
  17. package/admin/src/permissions.js +19 -0
  18. package/admin/src/pluginId.js +5 -0
  19. package/admin/src/translations/ar.json +20 -0
  20. package/admin/src/translations/cs.json +21 -0
  21. package/admin/src/translations/de.json +26 -0
  22. package/admin/src/translations/dk.json +39 -0
  23. package/admin/src/translations/en.json +39 -0
  24. package/admin/src/translations/es.json +39 -0
  25. package/admin/src/translations/fr.json +26 -0
  26. package/admin/src/translations/id.json +24 -0
  27. package/admin/src/translations/it.json +26 -0
  28. package/admin/src/translations/ko.json +39 -0
  29. package/admin/src/translations/ms.json +23 -0
  30. package/admin/src/translations/nl.json +21 -0
  31. package/admin/src/translations/pl.json +39 -0
  32. package/admin/src/translations/pt-BR.json +21 -0
  33. package/admin/src/translations/pt.json +21 -0
  34. package/admin/src/translations/ru.json +28 -0
  35. package/admin/src/translations/sk.json +24 -0
  36. package/admin/src/translations/sv.json +39 -0
  37. package/admin/src/translations/th.json +24 -0
  38. package/admin/src/translations/tr.json +20 -0
  39. package/admin/src/translations/uk.json +23 -0
  40. package/admin/src/translations/vi.json +24 -0
  41. package/admin/src/translations/zh-Hans.json +28 -0
  42. package/admin/src/translations/zh.json +39 -0
  43. package/admin/src/utils/getTrad.js +5 -0
  44. package/admin/src/utils/index.js +2 -0
  45. package/admin/src/utils/openWithNewTab.js +20 -0
  46. package/package.json +66 -0
  47. package/server/bootstrap.js +54 -0
  48. package/server/config/default-plugin-config.js +74 -0
  49. package/server/config/index.js +7 -0
  50. package/server/controllers/documentation.js +241 -0
  51. package/server/controllers/index.js +7 -0
  52. package/server/index.js +17 -0
  53. package/server/middlewares/documentation.js +25 -0
  54. package/server/middlewares/index.js +7 -0
  55. package/server/middlewares/restrict-access.js +24 -0
  56. package/server/public/index.html +70 -0
  57. package/server/public/login.html +145 -0
  58. package/server/register.js +11 -0
  59. package/server/routes/index.js +84 -0
  60. package/server/services/documentation.js +210 -0
  61. package/server/services/helpers/build-api-endpoint-path.js +185 -0
  62. package/server/services/helpers/build-component-schema.js +216 -0
  63. package/server/services/helpers/index.js +9 -0
  64. package/server/services/helpers/utils/clean-schema-attributes.js +235 -0
  65. package/server/services/helpers/utils/get-api-responses.js +105 -0
  66. package/server/services/helpers/utils/get-schema-data.js +32 -0
  67. package/server/services/helpers/utils/loop-content-type-names.js +53 -0
  68. package/server/services/helpers/utils/pascal-case.js +9 -0
  69. package/server/services/helpers/utils/query-params.js +95 -0
  70. package/server/services/helpers/utils/routes.js +10 -0
  71. package/server/services/index.js +7 -0
  72. package/strapi-admin.js +3 -0
  73. package/strapi-server.js +3 -0
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const pathToRegexp = require('path-to-regexp');
5
+
6
+ const pascalCase = require('./utils/pascal-case');
7
+ const queryParams = require('./utils/query-params');
8
+ const loopContentTypeNames = require('./utils/loop-content-type-names');
9
+ const getApiResponses = require('./utils/get-api-responses');
10
+ const { hasFindMethod, isLocalizedPath } = require('./utils/routes');
11
+
12
+ /**
13
+ * @description Parses a route with ':variable'
14
+ *
15
+ * @param {string} routePath - The route's path property
16
+ * @returns {string}
17
+ */
18
+ const parsePathWithVariables = (routePath) => {
19
+ return pathToRegexp
20
+ .parse(routePath)
21
+ .map((token) => {
22
+ if (_.isObject(token)) {
23
+ return `${token.prefix}{${token.name}}`;
24
+ }
25
+
26
+ return token;
27
+ })
28
+ .join('');
29
+ };
30
+
31
+ /**
32
+ * @description Builds the required object for a path parameter
33
+ *
34
+ * @param {string} routePath - The route's path property
35
+ *
36
+ * @returns {object } Swagger path params object
37
+ */
38
+ const getPathParams = (routePath) => {
39
+ return pathToRegexp
40
+ .parse(routePath)
41
+ .filter((token) => _.isObject(token))
42
+ .map((param) => {
43
+ return {
44
+ name: param.name,
45
+ in: 'path',
46
+ description: '',
47
+ deprecated: false,
48
+ required: true,
49
+ schema: { type: 'number' },
50
+ };
51
+ });
52
+ };
53
+
54
+ /**
55
+ *
56
+ * @param {string} prefix - The prefix found on the routes object
57
+ * @param {string} route - The current route
58
+ * @property {string} route.path - The current route's path
59
+ * @property {object} route.config - The current route's config object
60
+ *
61
+ * @returns {string}
62
+ */
63
+ const getPathWithPrefix = (prefix, route) => {
64
+ // When the prefix is set on the routes and
65
+ // the current route is not trying to remove it
66
+ if (prefix && !_.has(route.config, 'prefix')) {
67
+ // Add the prefix to the path
68
+ return prefix.concat(route.path);
69
+ }
70
+
71
+ // Otherwise just return path
72
+ return route.path;
73
+ };
74
+ /**
75
+ * @description Gets all paths based on routes
76
+ *
77
+ * @param {object} apiInfo
78
+ * @property {object} apiInfo.routeInfo - The api routes object
79
+ * @property {string} apiInfo.uniqueName - Content type name | Api name + Content type name
80
+ * @property {object} apiInfo.contentTypeInfo - The info object found on content type schemas
81
+ *
82
+ * @returns {object}
83
+ */
84
+ const getPaths = ({ routeInfo, uniqueName, contentTypeInfo }) => {
85
+ // Get the routes for the current content type
86
+ const contentTypeRoutes = routeInfo.routes.filter((route) => {
87
+ return (
88
+ route.path.includes(contentTypeInfo.pluralName) ||
89
+ route.path.includes(contentTypeInfo.singularName)
90
+ );
91
+ });
92
+
93
+ const paths = contentTypeRoutes.reduce((acc, route) => {
94
+ // TODO: Find a more reliable way to determine list of entities vs a single entity
95
+ const isListOfEntities = hasFindMethod(route.handler);
96
+ const isLocalizationPath = isLocalizedPath(route.path);
97
+ const methodVerb = route.method.toLowerCase();
98
+ const hasPathParams = route.path.includes('/:');
99
+ const pathWithPrefix = getPathWithPrefix(routeInfo.prefix, route);
100
+ const routePath = hasPathParams ? parsePathWithVariables(pathWithPrefix) : pathWithPrefix;
101
+ const { responses } = getApiResponses({
102
+ uniqueName,
103
+ route,
104
+ isListOfEntities,
105
+ isLocalizationPath,
106
+ });
107
+
108
+ const swaggerConfig = {
109
+ responses,
110
+ tags: [_.upperFirst(uniqueName)],
111
+ parameters: [],
112
+ operationId: `${methodVerb}${routePath}`,
113
+ };
114
+
115
+ if (isListOfEntities) {
116
+ swaggerConfig.parameters.push(...queryParams);
117
+ }
118
+
119
+ if (hasPathParams) {
120
+ const pathParams = getPathParams(route.path);
121
+ swaggerConfig.parameters.push(...pathParams);
122
+ }
123
+
124
+ if (['post', 'put'].includes(methodVerb)) {
125
+ const refName = isLocalizationPath ? 'LocalizationRequest' : 'Request';
126
+ const requestBody = {
127
+ required: true,
128
+ content: {
129
+ 'application/json': {
130
+ schema: {
131
+ $ref: `#/components/schemas/${pascalCase(uniqueName)}${refName}`,
132
+ },
133
+ },
134
+ },
135
+ };
136
+
137
+ swaggerConfig.requestBody = requestBody;
138
+ }
139
+
140
+ _.set(acc, `${routePath}.${methodVerb}`, swaggerConfig);
141
+
142
+ return acc;
143
+ }, {});
144
+
145
+ return paths;
146
+ };
147
+
148
+ /**
149
+ * @decription Gets all open api paths object for a given content type
150
+ *
151
+ * @param {object} apiInfo
152
+ *
153
+ * @returns {object} Open API paths
154
+ */
155
+ const getAllPathsForContentType = (apiInfo) => {
156
+ let paths = {};
157
+
158
+ const pathsObject = getPaths(apiInfo);
159
+
160
+ paths = {
161
+ ...paths,
162
+ ...pathsObject,
163
+ };
164
+
165
+ return paths;
166
+ };
167
+
168
+ /**
169
+ * @description - Builds the Swagger paths object for each api
170
+ *
171
+ * @param {object} api - Information about the current api
172
+ * @property {string} api.name - The name of the api
173
+ * @property {string} api.getter - The getter for the api (api | plugin)
174
+ * @property {array} api.ctNames - The name of all contentTypes found on the api
175
+ *
176
+ * @returns {object}
177
+ */
178
+ const buildApiEndpointPath = (api) => {
179
+ // A reusable loop for building paths and component schemas
180
+ // Uses the api param to build a new set of params for each content type
181
+ // Passes these new params to the function provided
182
+ return loopContentTypeNames(api, getAllPathsForContentType);
183
+ };
184
+
185
+ module.exports = buildApiEndpointPath;
@@ -0,0 +1,216 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ const cleanSchemaAttributes = require('./utils/clean-schema-attributes');
6
+ const loopContentTypeNames = require('./utils/loop-content-type-names');
7
+ const pascalCase = require('./utils/pascal-case');
8
+ const { hasFindMethod, isLocalizedPath } = require('./utils/routes');
9
+
10
+ /**
11
+ * @decription Get all open api schema objects for a given content type
12
+ *
13
+ * @param {object} apiInfo
14
+ * @property {string} apiInfo.uniqueName - Api name | Api name + Content type name
15
+ * @property {object} apiInfo.attributes - Attributes on content type
16
+ * @property {object} apiInfo.routeInfo - The routes for the api
17
+ *
18
+ * @returns {object} Open API schemas
19
+ */
20
+ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
21
+ // Store response and request schemas in an object
22
+ let schemas = {};
23
+ let componentSchemas = {};
24
+ // adds a ComponentSchema to the Schemas so it can be used as Ref
25
+ const addComponentSchema = (schemaName, schema) => {
26
+ if (!Object.keys(schema) || !Object.keys(schema.properties)) {
27
+ return false;
28
+ }
29
+ componentSchemas = {
30
+ ...componentSchemas,
31
+ [schemaName]: schema,
32
+ };
33
+ return true;
34
+ };
35
+ // Get all the route methods
36
+ const routeMethods = routeInfo.routes.map((route) => route.method);
37
+ // Check for localized paths
38
+ const hasLocalizationPath = routeInfo.routes.filter((route) =>
39
+ isLocalizedPath(route.path)
40
+ ).length;
41
+ // When the route methods contain any post or put requests
42
+ if (routeMethods.includes('POST') || routeMethods.includes('PUT')) {
43
+ const attributesToOmit = [
44
+ 'createdAt',
45
+ 'updatedAt',
46
+ 'publishedAt',
47
+ 'publishedBy',
48
+ 'updatedBy',
49
+ 'createdBy',
50
+ 'localizations',
51
+ ];
52
+ const attributesForRequest = _.omit(attributes, attributesToOmit);
53
+
54
+ // Get a list of required attribute names
55
+ const requiredAttributes = Object.entries(attributesForRequest).reduce((acc, attribute) => {
56
+ const [attributeKey, attributeValue] = attribute;
57
+
58
+ if (attributeValue.required) {
59
+ acc.push(attributeKey);
60
+ }
61
+
62
+ return acc;
63
+ }, []);
64
+
65
+ if (hasLocalizationPath) {
66
+ schemas = {
67
+ ...schemas,
68
+ [`${pascalCase(uniqueName)}LocalizationRequest`]: {
69
+ required: [...requiredAttributes, 'locale'],
70
+ type: 'object',
71
+ properties: cleanSchemaAttributes(attributesForRequest, {
72
+ isRequest: true,
73
+ addComponentSchema,
74
+ }),
75
+ },
76
+ };
77
+ }
78
+
79
+ // Build the request schema
80
+ schemas = {
81
+ ...schemas,
82
+ [`${pascalCase(uniqueName)}Request`]: {
83
+ type: 'object',
84
+ required: ['data'],
85
+ properties: {
86
+ data: {
87
+ required: requiredAttributes,
88
+ type: 'object',
89
+ properties: cleanSchemaAttributes(attributesForRequest, {
90
+ isRequest: true,
91
+ addComponentSchema,
92
+ }),
93
+ },
94
+ },
95
+ },
96
+ };
97
+ }
98
+
99
+ if (hasLocalizationPath) {
100
+ schemas = {
101
+ ...schemas,
102
+ [`${pascalCase(uniqueName)}LocalizationResponse`]: {
103
+ type: 'object',
104
+ properties: {
105
+ id: { type: 'number' },
106
+ ...cleanSchemaAttributes(attributes, { addComponentSchema }),
107
+ },
108
+ },
109
+ };
110
+ }
111
+
112
+ // Check for routes that need to return a list
113
+ const hasListOfEntities = routeInfo.routes.filter((route) => hasFindMethod(route.handler)).length;
114
+ if (hasListOfEntities) {
115
+ // Build the list response schema
116
+ schemas = {
117
+ ...schemas,
118
+ [`${pascalCase(uniqueName)}ListResponseDataItem`]: {
119
+ type: 'object',
120
+ properties: {
121
+ id: { type: 'number' },
122
+ attributes: {
123
+ type: 'object',
124
+ properties: cleanSchemaAttributes(attributes, {
125
+ addComponentSchema,
126
+ componentSchemaRefName: `#/components/schemas/${pascalCase(
127
+ uniqueName
128
+ )}ListResponseDataItemLocalized`,
129
+ }),
130
+ },
131
+ },
132
+ },
133
+ [`${pascalCase(uniqueName)}ListResponseDataItemLocalized`]: {
134
+ type: 'object',
135
+ properties: {
136
+ id: { type: 'number' },
137
+ attributes: {
138
+ type: 'object',
139
+ properties: cleanSchemaAttributes(attributes, { addComponentSchema }),
140
+ },
141
+ },
142
+ },
143
+ [`${pascalCase(uniqueName)}ListResponse`]: {
144
+ properties: {
145
+ data: {
146
+ type: 'array',
147
+ items: {
148
+ $ref: `#/components/schemas/${pascalCase(uniqueName)}ListResponseDataItem`,
149
+ },
150
+ },
151
+ meta: {
152
+ type: 'object',
153
+ properties: {
154
+ pagination: {
155
+ properties: {
156
+ page: { type: 'integer' },
157
+ pageSize: { type: 'integer', minimum: 25 },
158
+ pageCount: { type: 'integer', maximum: 1 },
159
+ total: { type: 'integer' },
160
+ },
161
+ },
162
+ },
163
+ },
164
+ },
165
+ },
166
+ };
167
+ }
168
+
169
+ // Build the response schema
170
+ schemas = {
171
+ ...schemas,
172
+ [`${pascalCase(uniqueName)}ResponseDataObject`]: {
173
+ type: 'object',
174
+ properties: {
175
+ id: { type: 'number' },
176
+ attributes: {
177
+ type: 'object',
178
+ properties: cleanSchemaAttributes(attributes, {
179
+ addComponentSchema,
180
+ componentSchemaRefName: `#/components/schemas/${pascalCase(
181
+ uniqueName
182
+ )}ResponseDataObjectLocalized`,
183
+ }),
184
+ },
185
+ },
186
+ },
187
+ [`${pascalCase(uniqueName)}ResponseDataObjectLocalized`]: {
188
+ type: 'object',
189
+ properties: {
190
+ id: { type: 'number' },
191
+ attributes: {
192
+ type: 'object',
193
+ properties: cleanSchemaAttributes(attributes, { addComponentSchema }),
194
+ },
195
+ },
196
+ },
197
+ [`${pascalCase(uniqueName)}Response`]: {
198
+ properties: {
199
+ data: {
200
+ $ref: `#/components/schemas/${pascalCase(uniqueName)}ResponseDataObject`,
201
+ },
202
+ meta: { type: 'object' },
203
+ },
204
+ },
205
+ };
206
+ return { ...schemas, ...componentSchemas };
207
+ };
208
+
209
+ const buildComponentSchema = (api) => {
210
+ // A reusable loop for building paths and component schemas
211
+ // Uses the api param to build a new set of params for each content type
212
+ // Passes these new params to the function provided
213
+ return loopContentTypeNames(api, getAllSchemasForContentType);
214
+ };
215
+
216
+ module.exports = buildComponentSchema;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const builApiEndpointPath = require('./build-api-endpoint-path');
4
+ const buildComponentSchema = require('./build-component-schema');
5
+
6
+ module.exports = {
7
+ builApiEndpointPath,
8
+ buildComponentSchema,
9
+ };
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const getSchemaData = require('./get-schema-data');
5
+ const pascalCase = require('./pascal-case');
6
+ /**
7
+ * @description - Converts types found on attributes to OpenAPI acceptable data types
8
+ *
9
+ * @param {object} attributes - The attributes found on a contentType
10
+ * @param {{ typeMap: Map, isRequest: boolean, addComponentSchema: function, componentSchemaRefName: string }} opts
11
+ * @returns Attributes using OpenAPI acceptable data types
12
+ */
13
+ const cleanSchemaAttributes = (
14
+ attributes,
15
+ {
16
+ typeMap = new Map(),
17
+ isRequest = false,
18
+ addComponentSchema = () => {},
19
+ componentSchemaRefName = '',
20
+ } = {}
21
+ ) => {
22
+ const attributesCopy = _.cloneDeep(attributes);
23
+
24
+ for (const prop of Object.keys(attributesCopy)) {
25
+ const attribute = attributesCopy[prop];
26
+ if (attribute.default) {
27
+ delete attributesCopy[prop].default;
28
+ }
29
+
30
+ switch (attribute.type) {
31
+ case 'password': {
32
+ if (!isRequest) {
33
+ delete attributesCopy[prop];
34
+ break;
35
+ }
36
+
37
+ attributesCopy[prop] = { type: 'string', format: 'password', example: '*******' };
38
+ break;
39
+ }
40
+ case 'email': {
41
+ attributesCopy[prop] = { type: 'string', format: 'email' };
42
+ break;
43
+ }
44
+ case 'string':
45
+ case 'text':
46
+ case 'richtext': {
47
+ attributesCopy[prop] = { type: 'string' };
48
+ break;
49
+ }
50
+ case 'timestamp': {
51
+ attributesCopy[prop] = { type: 'string', format: 'timestamp', example: Date.now() };
52
+ break;
53
+ }
54
+ case 'time': {
55
+ attributesCopy[prop] = { type: 'string', format: 'time', example: '12:54.000' };
56
+ break;
57
+ }
58
+ case 'date': {
59
+ attributesCopy[prop] = { type: 'string', format: 'date' };
60
+ break;
61
+ }
62
+ case 'datetime': {
63
+ attributesCopy[prop] = { type: 'string', format: 'date-time' };
64
+ break;
65
+ }
66
+ case 'boolean': {
67
+ attributesCopy[prop] = { type: 'boolean' };
68
+ break;
69
+ }
70
+ case 'enumeration': {
71
+ attributesCopy[prop] = { type: 'string', enum: attribute.enum };
72
+ break;
73
+ }
74
+ case 'decimal':
75
+ case 'float': {
76
+ attributesCopy[prop] = { type: 'number', format: 'float' };
77
+ break;
78
+ }
79
+ case 'integer': {
80
+ attributesCopy[prop] = { type: 'integer' };
81
+ break;
82
+ }
83
+ case 'biginteger': {
84
+ attributesCopy[prop] = { type: 'string', pattern: '^\\d*$', example: '123456789' };
85
+ break;
86
+ }
87
+ case 'json': {
88
+ attributesCopy[prop] = {};
89
+ break;
90
+ }
91
+ case 'uid': {
92
+ attributesCopy[prop] = { type: 'string' };
93
+ break;
94
+ }
95
+ case 'component': {
96
+ const componentAttributes = strapi.components[attribute.component].attributes;
97
+ const rawComponentSchema = {
98
+ type: 'object',
99
+ properties: {
100
+ ...(isRequest ? {} : { id: { type: 'number' } }),
101
+ ...cleanSchemaAttributes(componentAttributes, {
102
+ typeMap,
103
+ isRequest,
104
+ }),
105
+ },
106
+ };
107
+ const refComponentSchema = {
108
+ $ref: `#/components/schemas/${pascalCase(attribute.component)}Component`,
109
+ };
110
+ const componentExists = addComponentSchema(
111
+ `${pascalCase(attribute.component)}Component`,
112
+ rawComponentSchema
113
+ );
114
+ const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema;
115
+ if (attribute.repeatable) {
116
+ attributesCopy[prop] = {
117
+ type: 'array',
118
+ items: finalComponentSchema,
119
+ };
120
+ } else {
121
+ attributesCopy[prop] = finalComponentSchema;
122
+ }
123
+ break;
124
+ }
125
+ case 'dynamiczone': {
126
+ const components = attribute.components.map((component) => {
127
+ const componentAttributes = strapi.components[component].attributes;
128
+ const rawComponentSchema = {
129
+ type: 'object',
130
+ properties: {
131
+ ...(isRequest ? {} : { id: { type: 'number' } }),
132
+ __component: { type: 'string' },
133
+ ...cleanSchemaAttributes(componentAttributes, {
134
+ typeMap,
135
+ isRequest,
136
+ addComponentSchema,
137
+ }),
138
+ },
139
+ };
140
+ const refComponentSchema = { $ref: `#/components/schemas/${pascalCase(component)}` };
141
+ const componentExists = addComponentSchema(pascalCase(component), rawComponentSchema);
142
+ const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema;
143
+ return finalComponentSchema;
144
+ });
145
+
146
+ attributesCopy[prop] = {
147
+ type: 'array',
148
+ items: {
149
+ anyOf: components,
150
+ },
151
+ };
152
+ break;
153
+ }
154
+ case 'media': {
155
+ const imageAttributes = strapi.contentType('plugin::upload.file').attributes;
156
+ const isListOfEntities = attribute.multiple;
157
+
158
+ if (isRequest) {
159
+ const oneOfType = {
160
+ oneOf: [{ type: 'integer' }, { type: 'string' }],
161
+ example: 'string or id',
162
+ };
163
+
164
+ attributesCopy[prop] = isListOfEntities ? { type: 'array', items: oneOfType } : oneOfType;
165
+ break;
166
+ }
167
+
168
+ attributesCopy[prop] = {
169
+ type: 'object',
170
+ properties: {
171
+ data: getSchemaData(isListOfEntities, cleanSchemaAttributes(imageAttributes)),
172
+ },
173
+ };
174
+ break;
175
+ }
176
+
177
+ case 'relation': {
178
+ const isListOfEntities = attribute.relation.includes('ToMany');
179
+
180
+ if (isRequest) {
181
+ const oneOfType = {
182
+ oneOf: [{ type: 'integer' }, { type: 'string' }],
183
+ example: 'string or id',
184
+ };
185
+
186
+ attributesCopy[prop] = isListOfEntities ? { type: 'array', items: oneOfType } : oneOfType;
187
+ break;
188
+ }
189
+
190
+ if (prop === 'localizations') {
191
+ attributesCopy[prop] = {
192
+ type: 'object',
193
+ properties: {
194
+ data: {
195
+ type: 'array',
196
+ items: componentSchemaRefName.length ? { $ref: componentSchemaRefName } : {},
197
+ },
198
+ },
199
+ };
200
+ break;
201
+ }
202
+
203
+ if (!attribute.target || typeMap.has(attribute.target)) {
204
+ attributesCopy[prop] = {
205
+ type: 'object',
206
+ properties: { data: getSchemaData(isListOfEntities, {}) },
207
+ };
208
+ break;
209
+ }
210
+
211
+ typeMap.set(attribute.target, true);
212
+ const targetAttributes = strapi.contentType(attribute.target).attributes;
213
+
214
+ attributesCopy[prop] = {
215
+ type: 'object',
216
+ properties: {
217
+ data: getSchemaData(
218
+ isListOfEntities,
219
+ cleanSchemaAttributes(targetAttributes, { typeMap, isRequest })
220
+ ),
221
+ },
222
+ };
223
+
224
+ break;
225
+ }
226
+ default: {
227
+ throw new Error(`Invalid type ${attribute.type} while generating open api schema.`);
228
+ }
229
+ }
230
+ }
231
+
232
+ return attributesCopy;
233
+ };
234
+
235
+ module.exports = cleanSchemaAttributes;