@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.
- package/LICENSE +22 -0
- package/README.md +151 -0
- package/__mocks__/strapi.js +41 -0
- package/__tests__/build-component-schema.test.js +271 -0
- package/admin/src/components/FieldActionWrapper/index.js +14 -0
- package/admin/src/components/PluginIcon/index.js +12 -0
- package/admin/src/index.js +80 -0
- package/admin/src/pages/PluginPage/index.js +199 -0
- package/admin/src/pages/PluginPage/tests/index.test.js +873 -0
- package/admin/src/pages/PluginPage/tests/server.js +23 -0
- package/admin/src/pages/SettingsPage/index.js +181 -0
- package/admin/src/pages/SettingsPage/tests/index.test.js +612 -0
- package/admin/src/pages/SettingsPage/tests/server.js +18 -0
- package/admin/src/pages/utils/api.js +31 -0
- package/admin/src/pages/utils/schema.js +11 -0
- package/admin/src/pages/utils/useReactQuery.js +46 -0
- package/admin/src/permissions.js +19 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/translations/ar.json +20 -0
- package/admin/src/translations/cs.json +21 -0
- package/admin/src/translations/de.json +26 -0
- package/admin/src/translations/dk.json +39 -0
- package/admin/src/translations/en.json +39 -0
- package/admin/src/translations/es.json +39 -0
- package/admin/src/translations/fr.json +26 -0
- package/admin/src/translations/id.json +24 -0
- package/admin/src/translations/it.json +26 -0
- package/admin/src/translations/ko.json +39 -0
- package/admin/src/translations/ms.json +23 -0
- package/admin/src/translations/nl.json +21 -0
- package/admin/src/translations/pl.json +39 -0
- package/admin/src/translations/pt-BR.json +21 -0
- package/admin/src/translations/pt.json +21 -0
- package/admin/src/translations/ru.json +28 -0
- package/admin/src/translations/sk.json +24 -0
- package/admin/src/translations/sv.json +39 -0
- package/admin/src/translations/th.json +24 -0
- package/admin/src/translations/tr.json +20 -0
- package/admin/src/translations/uk.json +23 -0
- package/admin/src/translations/vi.json +24 -0
- package/admin/src/translations/zh-Hans.json +28 -0
- package/admin/src/translations/zh.json +39 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/admin/src/utils/index.js +2 -0
- package/admin/src/utils/openWithNewTab.js +20 -0
- package/package.json +66 -0
- package/server/bootstrap.js +54 -0
- package/server/config/default-plugin-config.js +74 -0
- package/server/config/index.js +7 -0
- package/server/controllers/documentation.js +241 -0
- package/server/controllers/index.js +7 -0
- package/server/index.js +17 -0
- package/server/middlewares/documentation.js +25 -0
- package/server/middlewares/index.js +7 -0
- package/server/middlewares/restrict-access.js +24 -0
- package/server/public/index.html +70 -0
- package/server/public/login.html +145 -0
- package/server/register.js +11 -0
- package/server/routes/index.js +84 -0
- package/server/services/documentation.js +210 -0
- package/server/services/helpers/build-api-endpoint-path.js +185 -0
- package/server/services/helpers/build-component-schema.js +216 -0
- package/server/services/helpers/index.js +9 -0
- package/server/services/helpers/utils/clean-schema-attributes.js +235 -0
- package/server/services/helpers/utils/get-api-responses.js +105 -0
- package/server/services/helpers/utils/get-schema-data.js +32 -0
- package/server/services/helpers/utils/loop-content-type-names.js +53 -0
- package/server/services/helpers/utils/pascal-case.js +9 -0
- package/server/services/helpers/utils/query-params.js +95 -0
- package/server/services/helpers/utils/routes.js +10 -0
- package/server/services/index.js +7 -0
- package/strapi-admin.js +3 -0
- 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,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;
|