@strapi/plugin-documentation 4.10.0-beta.1 → 4.10.0
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/admin/src/pages/PluginPage/index.js +3 -3
- package/admin/src/pages/utils/useReactQuery.js +34 -14
- package/jest.config.front.js +5 -0
- package/jest.config.js +5 -0
- package/package.json +8 -5
- package/server/config/default-plugin-config.js +2 -41
- package/server/services/__mocks__/mock-content-types.js +269 -0
- package/server/services/__mocks__/mock-strapi-data.js +183 -0
- package/server/services/__tests__/build-component-schema.test.js +761 -0
- package/server/services/__tests__/documentation.test.js +481 -0
- package/server/services/__tests__/override.test.js +85 -0
- package/server/services/documentation.js +123 -84
- package/server/services/helpers/build-api-endpoint-path.js +3 -2
- package/server/services/helpers/build-component-schema.js +108 -70
- package/server/services/helpers/utils/clean-schema-attributes.js +15 -7
- package/server/services/helpers/utils/loop-content-type-names.js +3 -1
- package/server/services/index.js +2 -0
- package/server/services/override.js +52 -0
- package/server/services/utils/default-openapi-components.js +40 -0
- package/server/services/utils/get-plugins-that-need-documentation.js +24 -0
- package/__mocks__/strapi.js +0 -41
- package/__tests__/build-component-schema.test.js +0 -271
- package/admin/src/pages/utils/api.js +0 -31
|
@@ -2,37 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs-extra');
|
|
5
|
-
const
|
|
5
|
+
const { produce } = require('immer');
|
|
6
6
|
const { getAbsoluteServerUrl } = require('@strapi/utils');
|
|
7
|
-
|
|
8
|
-
const defaultPluginConfig = require('../config/default-plugin-config');
|
|
9
7
|
const { builApiEndpointPath, buildComponentSchema } = require('./helpers');
|
|
10
8
|
|
|
9
|
+
const defaultOpenApiComponents = require('./utils/default-openapi-components');
|
|
10
|
+
const { getPluginsThatNeedDocumentation } = require('./utils/get-plugins-that-need-documentation');
|
|
11
|
+
|
|
11
12
|
module.exports = ({ strapi }) => {
|
|
12
13
|
const config = strapi.config.get('plugin.documentation');
|
|
13
|
-
|
|
14
|
-
const
|
|
14
|
+
const pluginsThatNeedDocumentation = getPluginsThatNeedDocumentation(config);
|
|
15
|
+
const overrideService = strapi.plugin('documentation').service('override');
|
|
15
16
|
|
|
16
17
|
return {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @deprecated
|
|
21
|
+
* registerDoc is deprecated it will be removed in the next major release,
|
|
22
|
+
* use strapi.plugin('documentation').service('override').registerOverride() instead
|
|
23
|
+
* @param {object} doc - The openapi specifcation to override
|
|
24
|
+
* @param {object} options - The options to override the documentation
|
|
25
|
+
* @param {string} options.pluginOrigin - The name of the plugin that is overriding the documentation
|
|
26
|
+
* @param {string[]} options.excludeFromGeneration - The name of the plugin that is overriding the documentation
|
|
27
|
+
*/
|
|
28
|
+
registerDoc(doc, options) {
|
|
29
|
+
strapi.log.warn(
|
|
30
|
+
"@strapi/plugin-documentation has deprecated registerDoc, use strapi.plugin('documentation').service('override').registerOverride() instead"
|
|
31
|
+
);
|
|
32
|
+
overrideService.registerOverride(doc, options);
|
|
25
33
|
},
|
|
34
|
+
|
|
26
35
|
getDocumentationVersion() {
|
|
27
|
-
return
|
|
36
|
+
return config.info.version;
|
|
28
37
|
},
|
|
29
38
|
|
|
30
39
|
getFullDocumentationPath() {
|
|
31
40
|
return path.join(strapi.dirs.app.extensions, 'documentation', 'documentation');
|
|
32
41
|
},
|
|
33
42
|
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* @deprecated
|
|
46
|
+
* This method will be removed in the next major release
|
|
47
|
+
*/
|
|
34
48
|
getCustomDocumentationPath() {
|
|
35
|
-
// ??
|
|
36
49
|
return path.join(strapi.dirs.app.extensions, 'documentation', 'config', 'settings.json');
|
|
37
50
|
},
|
|
38
51
|
|
|
@@ -46,7 +59,8 @@ module.exports = ({ strapi }) => {
|
|
|
46
59
|
path.resolve(this.getFullDocumentationPath(), version, 'full_documentation.json')
|
|
47
60
|
)
|
|
48
61
|
);
|
|
49
|
-
|
|
62
|
+
|
|
63
|
+
const generatedDate = doc.info['x-generation-date'];
|
|
50
64
|
|
|
51
65
|
return { version, generatedDate, url: '' };
|
|
52
66
|
} catch (err) {
|
|
@@ -99,8 +113,7 @@ module.exports = ({ strapi }) => {
|
|
|
99
113
|
},
|
|
100
114
|
|
|
101
115
|
getPluginAndApiInfo() {
|
|
102
|
-
const
|
|
103
|
-
const pluginsToDocument = plugins.map((plugin) => {
|
|
116
|
+
const pluginsToDocument = pluginsThatNeedDocumentation.map((plugin) => {
|
|
104
117
|
return {
|
|
105
118
|
name: plugin,
|
|
106
119
|
getter: 'plugin',
|
|
@@ -119,92 +132,118 @@ module.exports = ({ strapi }) => {
|
|
|
119
132
|
return [...apisToDocument, ...pluginsToDocument];
|
|
120
133
|
},
|
|
121
134
|
|
|
122
|
-
async getCustomConfig() {
|
|
123
|
-
const customConfigPath = this.getCustomDocumentationPath();
|
|
124
|
-
const pathExists = await fs.pathExists(customConfigPath);
|
|
125
|
-
if (pathExists) {
|
|
126
|
-
return fs.readJson(customConfigPath);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return {};
|
|
130
|
-
},
|
|
131
|
-
|
|
132
135
|
/**
|
|
133
136
|
* @description - Creates the Swagger json files
|
|
134
137
|
*/
|
|
135
138
|
async generateFullDoc(version = this.getDocumentationVersion()) {
|
|
136
|
-
let paths = {};
|
|
137
|
-
let schemas = {};
|
|
138
139
|
const apis = this.getPluginAndApiInfo();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const apiDocPath = path.join(apiDirPath, `${apiName}.json`);
|
|
144
|
-
|
|
145
|
-
const apiPath = builApiEndpointPath(api);
|
|
140
|
+
const apisThatNeedGeneratedDocumentation = apis.filter(
|
|
141
|
+
({ name }) => !overrideService.excludedFromGeneration.includes(name)
|
|
142
|
+
);
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
|
|
144
|
+
// Initialize the generated documentation with defaults
|
|
145
|
+
let generatedDocumentation = produce(
|
|
146
|
+
{
|
|
147
|
+
...config,
|
|
148
|
+
components: defaultOpenApiComponents,
|
|
149
|
+
},
|
|
150
|
+
(draft) => {
|
|
151
|
+
if (draft.servers.length === 0) {
|
|
152
|
+
// When no servers found set the defaults
|
|
153
|
+
const serverUrl = getAbsoluteServerUrl(strapi.config);
|
|
154
|
+
const apiPath = strapi.config.get('api.rest.prefix');
|
|
155
|
+
draft.servers = [
|
|
156
|
+
{
|
|
157
|
+
url: `${serverUrl}${apiPath}`,
|
|
158
|
+
description: 'Development server',
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
// Set the generated date
|
|
163
|
+
draft.info['x-generation-date'] = new Date().toISOString();
|
|
164
|
+
// Set the plugins that need documentation
|
|
165
|
+
draft['x-strapi-config'].plugins = pluginsThatNeedDocumentation;
|
|
166
|
+
// Delete the mutateDocumentation key from the config so it doesn't end up in the spec
|
|
167
|
+
delete draft['x-strapi-config'].mutateDocumentation;
|
|
149
168
|
}
|
|
169
|
+
);
|
|
170
|
+
// Generate the documentation for each api and update the generatedDocumentation
|
|
171
|
+
for (const api of apisThatNeedGeneratedDocumentation) {
|
|
172
|
+
const apiName = api.name;
|
|
150
173
|
|
|
174
|
+
const newApiPath = builApiEndpointPath(api);
|
|
175
|
+
const generatedSchemas = buildComponentSchema(api);
|
|
176
|
+
|
|
177
|
+
// TODO: To be confirmed, do we still need to write these files...?
|
|
178
|
+
const apiDirPath = path.join(this.getApiDocumentationPath(api), version);
|
|
179
|
+
const apiDocPath = path.join(apiDirPath, `${apiName}.json`);
|
|
151
180
|
await fs.ensureFile(apiDocPath);
|
|
152
|
-
await fs.writeJson(apiDocPath,
|
|
181
|
+
await fs.writeJson(apiDocPath, newApiPath, { spaces: 2 });
|
|
153
182
|
|
|
154
|
-
|
|
183
|
+
generatedDocumentation = produce(generatedDocumentation, (draft) => {
|
|
184
|
+
if (generatedSchemas) {
|
|
185
|
+
draft.components = {
|
|
186
|
+
schemas: { ...draft.components.schemas, ...generatedSchemas },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
155
189
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
};
|
|
190
|
+
if (newApiPath) {
|
|
191
|
+
draft.paths = { ...draft.paths, ...newApiPath };
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
160
195
|
|
|
161
|
-
|
|
196
|
+
// When overrides are present update the generatedDocumentation
|
|
197
|
+
if (overrideService.registeredOverrides.length > 0) {
|
|
198
|
+
generatedDocumentation = produce(generatedDocumentation, (draft) => {
|
|
199
|
+
overrideService.registeredOverrides.forEach((override) => {
|
|
200
|
+
// Only run the overrrides when no override version is provided,
|
|
201
|
+
// or when the generated documentation version matches the override version
|
|
202
|
+
if (!override?.info?.version || override.info.version === version) {
|
|
203
|
+
if (override.tags) {
|
|
204
|
+
// Merge override tags with the generated tags
|
|
205
|
+
draft.tags = draft.tags || [];
|
|
206
|
+
draft.tags.push(...override.tags);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (override.paths) {
|
|
210
|
+
// Merge override paths with the generated paths
|
|
211
|
+
// The override will add a new path or replace the value of an existing path
|
|
212
|
+
draft.paths = { ...draft.paths, ...override.paths };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (override.components) {
|
|
216
|
+
Object.entries(override.components).forEach(([overrideKey, overrideValue]) => {
|
|
217
|
+
draft.components[overrideKey] = draft.components[overrideKey] || {};
|
|
218
|
+
// Merge override components with the generated components,
|
|
219
|
+
// The override will add a new component or replace the value of an existing component
|
|
220
|
+
draft.components[overrideKey] = {
|
|
221
|
+
...draft.components[overrideKey],
|
|
222
|
+
...overrideValue,
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
162
229
|
}
|
|
163
230
|
|
|
231
|
+
// Escape hatch, allow the user to provide a mutateDocumentation function that can alter any part of
|
|
232
|
+
// the generated documentation before it is written to the file system
|
|
233
|
+
const userMutatesDocumentation = config['x-strapi-config'].mutateDocumentation;
|
|
234
|
+
const finalDocumentation = userMutatesDocumentation
|
|
235
|
+
? produce(generatedDocumentation, userMutatesDocumentation)
|
|
236
|
+
: generatedDocumentation;
|
|
237
|
+
|
|
238
|
+
// Get the file path for the final documentation
|
|
164
239
|
const fullDocJsonPath = path.join(
|
|
165
240
|
this.getFullDocumentationPath(),
|
|
166
241
|
version,
|
|
167
242
|
'full_documentation.json'
|
|
168
243
|
);
|
|
169
|
-
|
|
170
|
-
const defaultConfig = _.cloneDeep(defaultPluginConfig);
|
|
171
|
-
|
|
172
|
-
const serverUrl = getAbsoluteServerUrl(strapi.config);
|
|
173
|
-
const apiPath = strapi.config.get('api.rest.prefix');
|
|
174
|
-
|
|
175
|
-
_.set(defaultConfig, 'servers', [
|
|
176
|
-
{
|
|
177
|
-
url: `${serverUrl}${apiPath}`,
|
|
178
|
-
description: 'Development server',
|
|
179
|
-
},
|
|
180
|
-
]);
|
|
181
|
-
_.set(defaultConfig, ['info', 'x-generation-date'], new Date().toISOString());
|
|
182
|
-
_.set(defaultConfig, ['info', 'version'], version);
|
|
183
|
-
_.merge(defaultConfig.components, { schemas });
|
|
184
|
-
|
|
185
|
-
const customConfig = await this.getCustomConfig();
|
|
186
|
-
const config = _.merge(defaultConfig, customConfig);
|
|
187
|
-
|
|
188
|
-
const finalDoc = { ...config, paths };
|
|
189
|
-
|
|
190
|
-
registeredDocs.forEach((doc) => {
|
|
191
|
-
// Add tags
|
|
192
|
-
finalDoc.tags = finalDoc.tags || [];
|
|
193
|
-
finalDoc.tags.push(...(doc.tags || []));
|
|
194
|
-
|
|
195
|
-
// Add Paths
|
|
196
|
-
_.assign(finalDoc.paths, doc.paths);
|
|
197
|
-
|
|
198
|
-
// Add components
|
|
199
|
-
_.forEach(doc.components || {}, (val, key) => {
|
|
200
|
-
finalDoc.components[key] = finalDoc.components[key] || {};
|
|
201
|
-
|
|
202
|
-
_.assign(finalDoc.components[key], val);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
244
|
+
// Write the documentation to the file system
|
|
206
245
|
await fs.ensureFile(fullDocJsonPath);
|
|
207
|
-
await fs.writeJson(fullDocJsonPath,
|
|
246
|
+
await fs.writeJson(fullDocJsonPath, finalDocumentation, { spaces: 2 });
|
|
208
247
|
},
|
|
209
248
|
};
|
|
210
249
|
};
|
|
@@ -81,7 +81,7 @@ const getPathWithPrefix = (prefix, route) => {
|
|
|
81
81
|
*
|
|
82
82
|
* @returns {object}
|
|
83
83
|
*/
|
|
84
|
-
const getPaths = ({ routeInfo, uniqueName, contentTypeInfo }) => {
|
|
84
|
+
const getPaths = ({ routeInfo, uniqueName, contentTypeInfo, kind }) => {
|
|
85
85
|
// Get the routes for the current content type
|
|
86
86
|
const contentTypeRoutes = routeInfo.routes.filter((route) => {
|
|
87
87
|
return (
|
|
@@ -98,10 +98,11 @@ const getPaths = ({ routeInfo, uniqueName, contentTypeInfo }) => {
|
|
|
98
98
|
const hasPathParams = route.path.includes('/:');
|
|
99
99
|
const pathWithPrefix = getPathWithPrefix(routeInfo.prefix, route);
|
|
100
100
|
const routePath = hasPathParams ? parsePathWithVariables(pathWithPrefix) : pathWithPrefix;
|
|
101
|
+
|
|
101
102
|
const { responses } = getApiResponses({
|
|
102
103
|
uniqueName,
|
|
103
104
|
route,
|
|
104
|
-
isListOfEntities,
|
|
105
|
+
isListOfEntities: kind !== 'singleType' && isListOfEntities,
|
|
105
106
|
isLocalizationPath,
|
|
106
107
|
});
|
|
107
108
|
|
|
@@ -7,6 +7,18 @@ const loopContentTypeNames = require('./utils/loop-content-type-names');
|
|
|
7
7
|
const pascalCase = require('./utils/pascal-case');
|
|
8
8
|
const { hasFindMethod, isLocalizedPath } = require('./utils/routes');
|
|
9
9
|
|
|
10
|
+
const getRequiredAttributes = (allAttributes) => {
|
|
11
|
+
return Object.entries(allAttributes).reduce((acc, attribute) => {
|
|
12
|
+
const [attributeKey, attributeValue] = attribute;
|
|
13
|
+
|
|
14
|
+
if (attributeValue.required) {
|
|
15
|
+
acc.push(attributeKey);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return acc;
|
|
19
|
+
}, []);
|
|
20
|
+
};
|
|
21
|
+
|
|
10
22
|
/**
|
|
11
23
|
* @decription Get all open api schema objects for a given content type
|
|
12
24
|
*
|
|
@@ -20,16 +32,17 @@ const { hasFindMethod, isLocalizedPath } = require('./utils/routes');
|
|
|
20
32
|
const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
21
33
|
// Store response and request schemas in an object
|
|
22
34
|
let schemas = {};
|
|
23
|
-
let
|
|
35
|
+
let strapiComponentSchemas = {};
|
|
24
36
|
// adds a ComponentSchema to the Schemas so it can be used as Ref
|
|
25
|
-
const
|
|
26
|
-
if (!Object.keys(schema) || !Object.keys(schema.properties))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...
|
|
37
|
+
const didAddStrapiComponentsToSchemas = (schemaName, schema) => {
|
|
38
|
+
if (!Object.keys(schema) || !Object.keys(schema.properties)) return false;
|
|
39
|
+
|
|
40
|
+
// Add the Strapi components to the schema
|
|
41
|
+
strapiComponentSchemas = {
|
|
42
|
+
...strapiComponentSchemas,
|
|
31
43
|
[schemaName]: schema,
|
|
32
44
|
};
|
|
45
|
+
|
|
33
46
|
return true;
|
|
34
47
|
};
|
|
35
48
|
// Get all the route methods
|
|
@@ -38,39 +51,31 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
38
51
|
const hasLocalizationPath = routeInfo.routes.filter((route) =>
|
|
39
52
|
isLocalizedPath(route.path)
|
|
40
53
|
).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
54
|
|
|
55
|
+
const attributesToOmit = [
|
|
56
|
+
'createdAt',
|
|
57
|
+
'updatedAt',
|
|
58
|
+
'publishedAt',
|
|
59
|
+
'publishedBy',
|
|
60
|
+
'updatedBy',
|
|
61
|
+
'createdBy',
|
|
62
|
+
'localizations',
|
|
63
|
+
];
|
|
64
|
+
const attributesForRequest = _.omit(attributes, attributesToOmit);
|
|
65
|
+
// Get a list of required attribute names
|
|
66
|
+
const requiredRequestAttributes = getRequiredAttributes(attributesForRequest);
|
|
67
|
+
// Build the request schemas when the route has POST or PUT methods
|
|
68
|
+
if (routeMethods.includes('POST') || routeMethods.includes('PUT')) {
|
|
69
|
+
// Build localization requests schemas
|
|
65
70
|
if (hasLocalizationPath) {
|
|
66
71
|
schemas = {
|
|
67
72
|
...schemas,
|
|
68
73
|
[`${pascalCase(uniqueName)}LocalizationRequest`]: {
|
|
69
|
-
required: [...
|
|
74
|
+
required: [...requiredRequestAttributes, 'locale'],
|
|
70
75
|
type: 'object',
|
|
71
76
|
properties: cleanSchemaAttributes(attributesForRequest, {
|
|
72
77
|
isRequest: true,
|
|
73
|
-
|
|
78
|
+
didAddStrapiComponentsToSchemas,
|
|
74
79
|
}),
|
|
75
80
|
},
|
|
76
81
|
};
|
|
@@ -84,11 +89,11 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
84
89
|
required: ['data'],
|
|
85
90
|
properties: {
|
|
86
91
|
data: {
|
|
87
|
-
required:
|
|
92
|
+
...(requiredRequestAttributes.length && { required: requiredRequestAttributes }),
|
|
88
93
|
type: 'object',
|
|
89
94
|
properties: cleanSchemaAttributes(attributesForRequest, {
|
|
90
95
|
isRequest: true,
|
|
91
|
-
|
|
96
|
+
didAddStrapiComponentsToSchemas,
|
|
92
97
|
}),
|
|
93
98
|
},
|
|
94
99
|
},
|
|
@@ -96,14 +101,26 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
96
101
|
};
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
// Build the localization response schema
|
|
99
105
|
if (hasLocalizationPath) {
|
|
100
106
|
schemas = {
|
|
101
107
|
...schemas,
|
|
102
|
-
[`${pascalCase(uniqueName)}
|
|
108
|
+
[`${pascalCase(uniqueName)}ResponseDataObjectLocalized`]: {
|
|
103
109
|
type: 'object',
|
|
104
110
|
properties: {
|
|
105
111
|
id: { type: 'number' },
|
|
106
|
-
|
|
112
|
+
attributes: {
|
|
113
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}`,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
[`${pascalCase(uniqueName)}LocalizationResponse`]: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
data: {
|
|
121
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}ResponseDataObjectLocalized`,
|
|
122
|
+
},
|
|
123
|
+
meta: { type: 'object' },
|
|
107
124
|
},
|
|
108
125
|
},
|
|
109
126
|
};
|
|
@@ -112,6 +129,46 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
112
129
|
// Check for routes that need to return a list
|
|
113
130
|
const hasListOfEntities = routeInfo.routes.filter((route) => hasFindMethod(route.handler)).length;
|
|
114
131
|
if (hasListOfEntities) {
|
|
132
|
+
// Buld the localized list response schema
|
|
133
|
+
if (hasLocalizationPath) {
|
|
134
|
+
schemas = {
|
|
135
|
+
...schemas,
|
|
136
|
+
[`${pascalCase(uniqueName)}ListResponseDataItemLocalized`]: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
id: { type: 'number' },
|
|
140
|
+
attributes: {
|
|
141
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}`,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
[`${pascalCase(uniqueName)}LocalizationListResponse`]: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
data: {
|
|
149
|
+
type: 'array',
|
|
150
|
+
items: {
|
|
151
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}ListResponseDataItemLocalized`,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
meta: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
pagination: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {
|
|
160
|
+
page: { type: 'integer' },
|
|
161
|
+
pageSize: { type: 'integer', minimum: 25 },
|
|
162
|
+
pageCount: { type: 'integer', maximum: 1 },
|
|
163
|
+
total: { type: 'integer' },
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
115
172
|
// Build the list response schema
|
|
116
173
|
schemas = {
|
|
117
174
|
...schemas,
|
|
@@ -120,27 +177,12 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
120
177
|
properties: {
|
|
121
178
|
id: { type: 'number' },
|
|
122
179
|
attributes: {
|
|
123
|
-
|
|
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 }),
|
|
180
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}`,
|
|
140
181
|
},
|
|
141
182
|
},
|
|
142
183
|
},
|
|
143
184
|
[`${pascalCase(uniqueName)}ListResponse`]: {
|
|
185
|
+
type: 'object',
|
|
144
186
|
properties: {
|
|
145
187
|
data: {
|
|
146
188
|
type: 'array',
|
|
@@ -152,6 +194,7 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
152
194
|
type: 'object',
|
|
153
195
|
properties: {
|
|
154
196
|
pagination: {
|
|
197
|
+
type: 'object',
|
|
155
198
|
properties: {
|
|
156
199
|
page: { type: 'integer' },
|
|
157
200
|
pageSize: { type: 'integer', minimum: 25 },
|
|
@@ -166,35 +209,29 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
166
209
|
};
|
|
167
210
|
}
|
|
168
211
|
|
|
212
|
+
const requiredAttributes = getRequiredAttributes(attributes);
|
|
169
213
|
// Build the response schema
|
|
170
214
|
schemas = {
|
|
171
215
|
...schemas,
|
|
172
|
-
[`${pascalCase(uniqueName)}
|
|
216
|
+
[`${pascalCase(uniqueName)}`]: {
|
|
173
217
|
type: 'object',
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
addComponentSchema,
|
|
180
|
-
componentSchemaRefName: `#/components/schemas/${pascalCase(
|
|
181
|
-
uniqueName
|
|
182
|
-
)}ResponseDataObjectLocalized`,
|
|
183
|
-
}),
|
|
184
|
-
},
|
|
185
|
-
},
|
|
218
|
+
...(requiredAttributes.length && { required: requiredAttributes }),
|
|
219
|
+
properties: cleanSchemaAttributes(attributes, {
|
|
220
|
+
didAddStrapiComponentsToSchemas,
|
|
221
|
+
componentSchemaRefName: `#/components/schemas/${pascalCase(uniqueName)}`,
|
|
222
|
+
}),
|
|
186
223
|
},
|
|
187
|
-
[`${pascalCase(uniqueName)}
|
|
224
|
+
[`${pascalCase(uniqueName)}ResponseDataObject`]: {
|
|
188
225
|
type: 'object',
|
|
189
226
|
properties: {
|
|
190
227
|
id: { type: 'number' },
|
|
191
228
|
attributes: {
|
|
192
|
-
|
|
193
|
-
properties: cleanSchemaAttributes(attributes, { addComponentSchema }),
|
|
229
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}`,
|
|
194
230
|
},
|
|
195
231
|
},
|
|
196
232
|
},
|
|
197
233
|
[`${pascalCase(uniqueName)}Response`]: {
|
|
234
|
+
type: 'object',
|
|
198
235
|
properties: {
|
|
199
236
|
data: {
|
|
200
237
|
$ref: `#/components/schemas/${pascalCase(uniqueName)}ResponseDataObject`,
|
|
@@ -203,7 +240,8 @@ const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
|
203
240
|
},
|
|
204
241
|
},
|
|
205
242
|
};
|
|
206
|
-
|
|
243
|
+
|
|
244
|
+
return { ...schemas, ...strapiComponentSchemas };
|
|
207
245
|
};
|
|
208
246
|
|
|
209
247
|
const buildComponentSchema = (api) => {
|
|
@@ -7,7 +7,7 @@ const pascalCase = require('./pascal-case');
|
|
|
7
7
|
* @description - Converts types found on attributes to OpenAPI acceptable data types
|
|
8
8
|
*
|
|
9
9
|
* @param {object} attributes - The attributes found on a contentType
|
|
10
|
-
* @param {{ typeMap: Map, isRequest: boolean,
|
|
10
|
+
* @param {{ typeMap: Map, isRequest: boolean, didAddStrapiComponentsToSchemas: function, componentSchemaRefName: string }} opts
|
|
11
11
|
* @returns Attributes using OpenAPI acceptable data types
|
|
12
12
|
*/
|
|
13
13
|
const cleanSchemaAttributes = (
|
|
@@ -15,7 +15,7 @@ const cleanSchemaAttributes = (
|
|
|
15
15
|
{
|
|
16
16
|
typeMap = new Map(),
|
|
17
17
|
isRequest = false,
|
|
18
|
-
|
|
18
|
+
didAddStrapiComponentsToSchemas = () => {},
|
|
19
19
|
componentSchemaRefName = '',
|
|
20
20
|
} = {}
|
|
21
21
|
) => {
|
|
@@ -107,7 +107,7 @@ const cleanSchemaAttributes = (
|
|
|
107
107
|
const refComponentSchema = {
|
|
108
108
|
$ref: `#/components/schemas/${pascalCase(attribute.component)}Component`,
|
|
109
109
|
};
|
|
110
|
-
const componentExists =
|
|
110
|
+
const componentExists = didAddStrapiComponentsToSchemas(
|
|
111
111
|
`${pascalCase(attribute.component)}Component`,
|
|
112
112
|
rawComponentSchema
|
|
113
113
|
);
|
|
@@ -133,12 +133,17 @@ const cleanSchemaAttributes = (
|
|
|
133
133
|
...cleanSchemaAttributes(componentAttributes, {
|
|
134
134
|
typeMap,
|
|
135
135
|
isRequest,
|
|
136
|
-
|
|
136
|
+
didAddStrapiComponentsToSchemas,
|
|
137
137
|
}),
|
|
138
138
|
},
|
|
139
139
|
};
|
|
140
|
-
const refComponentSchema = {
|
|
141
|
-
|
|
140
|
+
const refComponentSchema = {
|
|
141
|
+
$ref: `#/components/schemas/${pascalCase(component)}Component`,
|
|
142
|
+
};
|
|
143
|
+
const componentExists = didAddStrapiComponentsToSchemas(
|
|
144
|
+
`${pascalCase(component)}Component`,
|
|
145
|
+
rawComponentSchema
|
|
146
|
+
);
|
|
142
147
|
const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema;
|
|
143
148
|
return finalComponentSchema;
|
|
144
149
|
});
|
|
@@ -168,7 +173,10 @@ const cleanSchemaAttributes = (
|
|
|
168
173
|
attributesCopy[prop] = {
|
|
169
174
|
type: 'object',
|
|
170
175
|
properties: {
|
|
171
|
-
data: getSchemaData(
|
|
176
|
+
data: getSchemaData(
|
|
177
|
+
isListOfEntities,
|
|
178
|
+
cleanSchemaAttributes(imageAttributes, { typeMap })
|
|
179
|
+
),
|
|
172
180
|
},
|
|
173
181
|
};
|
|
174
182
|
break;
|
|
@@ -15,7 +15,8 @@ const loopContentTypeNames = (api, callback) => {
|
|
|
15
15
|
for (const contentTypeName of api.ctNames) {
|
|
16
16
|
// Get the attributes found on the api's contentType
|
|
17
17
|
const uid = `${api.getter}::${api.name}.${contentTypeName}`;
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
const { attributes, info: contentTypeInfo, kind } = strapi.contentType(uid);
|
|
19
20
|
|
|
20
21
|
// Get the routes for the current api
|
|
21
22
|
const routeInfo =
|
|
@@ -39,6 +40,7 @@ const loopContentTypeNames = (api, callback) => {
|
|
|
39
40
|
attributes,
|
|
40
41
|
uniqueName,
|
|
41
42
|
contentTypeInfo,
|
|
43
|
+
kind,
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
result = {
|