@strapi/plugin-documentation 4.2.0-beta.1 → 4.2.0-beta.4
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/__mocks__/strapi.js +41 -0
- package/__tests__/build-component-schema.test.js +271 -0
- package/package.json +6 -6
- package/server/config/{default-config.js → default-plugin-config.js} +30 -1
- package/server/config/index.js +2 -2
- package/server/services/documentation.js +24 -17
- package/server/services/helpers/build-api-endpoint-path.js +185 -0
- package/server/services/helpers/build-component-schema.js +156 -0
- package/server/services/helpers/index.js +9 -0
- package/server/{utils → services/helpers/utils}/clean-schema-attributes.js +9 -2
- package/server/services/helpers/utils/get-api-responses.js +105 -0
- package/server/{utils → services/helpers/utils}/get-schema-data.js +0 -0
- package/server/services/helpers/utils/loop-content-type-names.js +52 -0
- package/server/services/helpers/utils/pascal-case.js +9 -0
- package/server/{utils → services/helpers/utils}/query-params.js +0 -0
- package/server/services/helpers/utils/routes.js +10 -0
- package/yarn-error.log +95 -0
- package/server/services/utils/components.json +0 -25
- package/server/services/utils/parametersOptions.json +0 -134
- package/server/services/utils/unknownComponent.json +0 -11
- package/server/utils/builders/build-api-endpoint-path.js +0 -180
- package/server/utils/builders/build-api-requests.js +0 -41
- package/server/utils/builders/build-api-responses.js +0 -109
- package/server/utils/builders/index.js +0 -11
- package/server/utils/error-response.js +0 -22
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const strapi = {
|
|
4
|
+
plugins: {
|
|
5
|
+
'users-permissions': {
|
|
6
|
+
contentTypes: {
|
|
7
|
+
role: {
|
|
8
|
+
attributes: {
|
|
9
|
+
name: {
|
|
10
|
+
type: 'string',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
routes: {
|
|
16
|
+
'content-api': {
|
|
17
|
+
routes: [],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
api: {
|
|
23
|
+
restaurant: {
|
|
24
|
+
contentTypes: {
|
|
25
|
+
restaurant: {
|
|
26
|
+
attributes: {
|
|
27
|
+
name: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
routes: {
|
|
34
|
+
restaurant: { routes: [] },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
contentType: () => ({ info: {}, attributes: { test: { type: 'string' } } }),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
module.exports = strapi;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
const buildComponentSchema = require('../server/services/helpers/build-component-schema');
|
|
5
|
+
const strapi = require('../__mocks__/strapi');
|
|
6
|
+
|
|
7
|
+
describe('Build Component Schema', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Reset the mocked strapi instance
|
|
10
|
+
global.strapi = _.cloneDeep(strapi);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('builds the Response schema', () => {
|
|
14
|
+
const apiMocks = [
|
|
15
|
+
{
|
|
16
|
+
name: 'users-permissions',
|
|
17
|
+
getter: 'plugin',
|
|
18
|
+
ctNames: ['role'],
|
|
19
|
+
},
|
|
20
|
+
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
let schemas = {};
|
|
24
|
+
for (const mock of apiMocks) {
|
|
25
|
+
schemas = {
|
|
26
|
+
...schemas,
|
|
27
|
+
...buildComponentSchema(mock),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const schemaNames = Object.keys(schemas);
|
|
32
|
+
const [pluginResponseName, apiResponseName] = Object.keys(schemas);
|
|
33
|
+
const [pluginResponseValue, apiResponseValue] = Object.values(schemas);
|
|
34
|
+
|
|
35
|
+
const expectedShape = {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
data: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
id: { type: 'string' },
|
|
42
|
+
attributes: { type: 'object', properties: { test: { type: 'string' } } },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
meta: { type: 'object' },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
expect(schemaNames.length).toBe(2);
|
|
50
|
+
expect(pluginResponseName).toBe('UsersPermissionsRoleResponse');
|
|
51
|
+
expect(apiResponseName).toBe('RestaurantResponse');
|
|
52
|
+
expect(pluginResponseValue).toStrictEqual(expectedShape);
|
|
53
|
+
expect(apiResponseValue).toStrictEqual(expectedShape);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('builds the ResponseList schema', () => {
|
|
57
|
+
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
|
58
|
+
{ method: 'GET', path: '/test', handler: 'test.find' },
|
|
59
|
+
];
|
|
60
|
+
global.strapi.api.restaurant.routes.restaurant.routes = [
|
|
61
|
+
{ method: 'GET', path: '/test', handler: 'test.find' },
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const apiMocks = [
|
|
65
|
+
{
|
|
66
|
+
name: 'users-permissions',
|
|
67
|
+
getter: 'plugin',
|
|
68
|
+
ctNames: ['role'],
|
|
69
|
+
},
|
|
70
|
+
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
let schemas = {};
|
|
74
|
+
for (const mock of apiMocks) {
|
|
75
|
+
schemas = {
|
|
76
|
+
...schemas,
|
|
77
|
+
...buildComponentSchema(mock),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const schemaNames = Object.keys(schemas);
|
|
82
|
+
const pluginListResponseValue = schemas['UsersPermissionsRoleListResponse'];
|
|
83
|
+
const apiListResponseValue = schemas['RestaurantListResponse'];
|
|
84
|
+
|
|
85
|
+
const expectedShape = {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
data: {
|
|
89
|
+
type: 'array',
|
|
90
|
+
items: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
id: { type: 'string' },
|
|
94
|
+
attributes: { type: 'object', properties: { test: { type: 'string' } } },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
meta: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
pagination: {
|
|
102
|
+
properties: {
|
|
103
|
+
page: { type: 'integer' },
|
|
104
|
+
pageSize: { type: 'integer', minimum: 25 },
|
|
105
|
+
pageCount: { type: 'integer', maximum: 1 },
|
|
106
|
+
total: { type: 'integer' },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(schemaNames.length).toBe(4);
|
|
115
|
+
expect(schemaNames.includes('UsersPermissionsRoleListResponse')).toBe(true);
|
|
116
|
+
expect(schemaNames.includes('RestaurantListResponse')).toBe(true);
|
|
117
|
+
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
|
118
|
+
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('builds the Request schema', () => {
|
|
122
|
+
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
|
123
|
+
{ method: 'POST', path: '/test', handler: 'test.create' },
|
|
124
|
+
];
|
|
125
|
+
global.strapi.api.restaurant.routes.restaurant.routes = [
|
|
126
|
+
{ method: 'POST', path: '/test', handler: 'test.create' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const apiMocks = [
|
|
130
|
+
{
|
|
131
|
+
name: 'users-permissions',
|
|
132
|
+
getter: 'plugin',
|
|
133
|
+
ctNames: ['role'],
|
|
134
|
+
},
|
|
135
|
+
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
let schemas = {};
|
|
139
|
+
for (const mock of apiMocks) {
|
|
140
|
+
schemas = {
|
|
141
|
+
...schemas,
|
|
142
|
+
...buildComponentSchema(mock),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const schemaNames = Object.keys(schemas);
|
|
147
|
+
const pluginListResponseValue = schemas['UsersPermissionsRoleRequest'];
|
|
148
|
+
const apiListResponseValue = schemas['RestaurantRequest'];
|
|
149
|
+
|
|
150
|
+
const expectedShape = {
|
|
151
|
+
type: 'object',
|
|
152
|
+
required: ['data'],
|
|
153
|
+
properties: {
|
|
154
|
+
data: {
|
|
155
|
+
required: [],
|
|
156
|
+
type: 'object',
|
|
157
|
+
properties: { test: { type: 'string' } },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
expect(schemaNames.length).toBe(4);
|
|
163
|
+
expect(schemaNames.includes('UsersPermissionsRoleRequest')).toBe(true);
|
|
164
|
+
expect(schemaNames.includes('RestaurantRequest')).toBe(true);
|
|
165
|
+
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
|
166
|
+
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('builds the LocalizationResponse schema', () => {
|
|
170
|
+
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
|
171
|
+
{ method: 'GET', path: '/localizations', handler: 'test' },
|
|
172
|
+
];
|
|
173
|
+
global.strapi.api.restaurant.routes.restaurant.routes = [
|
|
174
|
+
{ method: 'GET', path: '/localizations', handler: 'test' },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const apiMocks = [
|
|
178
|
+
{
|
|
179
|
+
name: 'users-permissions',
|
|
180
|
+
getter: 'plugin',
|
|
181
|
+
ctNames: ['role'],
|
|
182
|
+
},
|
|
183
|
+
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
let schemas = {};
|
|
187
|
+
for (const mock of apiMocks) {
|
|
188
|
+
schemas = {
|
|
189
|
+
...schemas,
|
|
190
|
+
...buildComponentSchema(mock),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const schemaNames = Object.keys(schemas);
|
|
195
|
+
const pluginListResponseValue = schemas['UsersPermissionsRoleLocalizationResponse'];
|
|
196
|
+
const apiListResponseValue = schemas['RestaurantLocalizationResponse'];
|
|
197
|
+
|
|
198
|
+
const expectedShape = {
|
|
199
|
+
type: 'object',
|
|
200
|
+
properties: {
|
|
201
|
+
id: { type: 'string' },
|
|
202
|
+
test: { type: 'string' },
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
expect(schemaNames.length).toBe(4);
|
|
207
|
+
expect(schemaNames.includes('UsersPermissionsRoleLocalizationResponse')).toBe(true);
|
|
208
|
+
expect(schemaNames.includes('RestaurantLocalizationResponse')).toBe(true);
|
|
209
|
+
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
|
210
|
+
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('builds the LocalizationRequest schema', () => {
|
|
214
|
+
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
|
215
|
+
{ method: 'POST', path: '/localizations', handler: 'test' },
|
|
216
|
+
];
|
|
217
|
+
global.strapi.api.restaurant.routes.restaurant.routes = [
|
|
218
|
+
{ method: 'POST', path: '/localizations', handler: 'test' },
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
const apiMocks = [
|
|
222
|
+
{
|
|
223
|
+
name: 'users-permissions',
|
|
224
|
+
getter: 'plugin',
|
|
225
|
+
ctNames: ['role'],
|
|
226
|
+
},
|
|
227
|
+
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
let schemas = {};
|
|
231
|
+
for (const mock of apiMocks) {
|
|
232
|
+
schemas = {
|
|
233
|
+
...schemas,
|
|
234
|
+
...buildComponentSchema(mock),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const schemaNames = Object.keys(schemas);
|
|
239
|
+
const pluginListResponseValue = schemas['UsersPermissionsRoleLocalizationRequest'];
|
|
240
|
+
const apiListResponseValue = schemas['RestaurantLocalizationRequest'];
|
|
241
|
+
|
|
242
|
+
const expectedShape = {
|
|
243
|
+
type: 'object',
|
|
244
|
+
required: ['locale'],
|
|
245
|
+
properties: { test: { type: 'string' } },
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
expect(schemaNames.length).toBe(8);
|
|
249
|
+
expect(schemaNames.includes('UsersPermissionsRoleLocalizationRequest')).toBe(true);
|
|
250
|
+
expect(schemaNames.includes('RestaurantLocalizationRequest')).toBe(true);
|
|
251
|
+
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
|
252
|
+
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('creates the correct name given multiple content types', () => {
|
|
256
|
+
const apiMock = {
|
|
257
|
+
name: 'users-permissions',
|
|
258
|
+
getter: 'plugin',
|
|
259
|
+
ctNames: ['permission', 'role', 'user'],
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const schemas = buildComponentSchema(apiMock);
|
|
263
|
+
const schemaNames = Object.keys(schemas);
|
|
264
|
+
const [permission, role, user] = schemaNames;
|
|
265
|
+
|
|
266
|
+
expect(schemaNames.length).toBe(3);
|
|
267
|
+
expect(permission).toBe('UsersPermissionsPermissionResponse');
|
|
268
|
+
expect(role).toBe('UsersPermissionsRoleResponse');
|
|
269
|
+
expect(user).toBe('UsersPermissionsUserResponse');
|
|
270
|
+
});
|
|
271
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/plugin-documentation",
|
|
3
|
-
"version": "4.2.0-beta.
|
|
3
|
+
"version": "4.2.0-beta.4",
|
|
4
4
|
"description": "Create an OpenAPI Document and visualize your API with SWAGGER UI.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"test": "echo \"no tests yet\""
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@strapi/helper-plugin": "4.2.0-beta.
|
|
28
|
-
"@strapi/utils": "4.2.0-beta.
|
|
27
|
+
"@strapi/helper-plugin": "4.2.0-beta.4",
|
|
28
|
+
"@strapi/utils": "4.2.0-beta.4",
|
|
29
29
|
"bcryptjs": "2.4.3",
|
|
30
30
|
"cheerio": "^1.0.0-rc.5",
|
|
31
31
|
"fs-extra": "10.0.0",
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"react-router-dom": "5.2.0",
|
|
43
43
|
"redux": "^4.0.1",
|
|
44
44
|
"reselect": "^4.0.0",
|
|
45
|
-
"swagger-ui-dist": "
|
|
45
|
+
"swagger-ui-dist": "4.11.1"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@strapi/strapi": "^4.0.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
51
|
+
"node": ">=14.19.1 <=16.x.x",
|
|
52
52
|
"npm": ">=6.0.0"
|
|
53
53
|
},
|
|
54
54
|
"strapi": {
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"description": "Create an OpenAPI Document and visualize your API with SWAGGER UI.",
|
|
58
58
|
"kind": "plugin"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "5caff35a30e33b7a660eb946299f9e3d2d35b2b8"
|
|
61
61
|
}
|
|
@@ -21,7 +21,7 @@ module.exports = {
|
|
|
21
21
|
path: '/documentation',
|
|
22
22
|
showGeneratedFiles: true,
|
|
23
23
|
generateDefaultResponse: true,
|
|
24
|
-
plugins: ['email', 'upload'],
|
|
24
|
+
plugins: ['email', 'upload', 'users-permissions'],
|
|
25
25
|
},
|
|
26
26
|
servers: [],
|
|
27
27
|
externalDocs: {
|
|
@@ -41,5 +41,34 @@ module.exports = {
|
|
|
41
41
|
bearerFormat: 'JWT',
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
|
+
schemas: {
|
|
45
|
+
Error: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
required: ['error'],
|
|
48
|
+
properties: {
|
|
49
|
+
data: {
|
|
50
|
+
nullable: true,
|
|
51
|
+
oneOf: [{ type: 'object' }, { type: 'array', items: [] }],
|
|
52
|
+
},
|
|
53
|
+
error: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
status: {
|
|
57
|
+
type: 'integer',
|
|
58
|
+
},
|
|
59
|
+
name: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
},
|
|
62
|
+
message: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
},
|
|
65
|
+
details: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
44
73
|
},
|
|
45
74
|
};
|
package/server/config/index.js
CHANGED
|
@@ -5,8 +5,8 @@ const fs = require('fs-extra');
|
|
|
5
5
|
const _ = require('lodash');
|
|
6
6
|
const { getAbsoluteServerUrl } = require('@strapi/utils');
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const defaultPluginConfig = require('../config/default-plugin-config');
|
|
9
|
+
const { builApiEndpointPath, buildComponentSchema } = require('./helpers');
|
|
10
10
|
|
|
11
11
|
module.exports = ({ strapi }) => {
|
|
12
12
|
const config = strapi.config.get('plugin.documentation');
|
|
@@ -108,7 +108,7 @@ module.exports = ({ strapi }) => {
|
|
|
108
108
|
return [...apisToDocument, ...pluginsToDocument];
|
|
109
109
|
},
|
|
110
110
|
|
|
111
|
-
async
|
|
111
|
+
async getCustomConfig() {
|
|
112
112
|
const customConfigPath = this.getCustomDocumentationPath();
|
|
113
113
|
const pathExists = await fs.pathExists(customConfigPath);
|
|
114
114
|
if (pathExists) {
|
|
@@ -123,23 +123,31 @@ module.exports = ({ strapi }) => {
|
|
|
123
123
|
*/
|
|
124
124
|
async generateFullDoc(version = this.getDocumentationVersion()) {
|
|
125
125
|
let paths = {};
|
|
126
|
-
|
|
126
|
+
let schemas = {};
|
|
127
127
|
const apis = this.getPluginAndApiInfo();
|
|
128
128
|
for (const api of apis) {
|
|
129
129
|
const apiName = api.name;
|
|
130
130
|
const apiDirPath = path.join(this.getApiDocumentationPath(api), version);
|
|
131
131
|
|
|
132
132
|
const apiDocPath = path.join(apiDirPath, `${apiName}.json`);
|
|
133
|
-
const apiPathsObject = builApiEndpointPath(api);
|
|
134
133
|
|
|
135
|
-
|
|
134
|
+
const apiPath = builApiEndpointPath(api);
|
|
135
|
+
|
|
136
|
+
if (!apiPath) {
|
|
136
137
|
continue;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
await fs.ensureFile(apiDocPath);
|
|
140
|
-
await fs.writeJson(apiDocPath,
|
|
141
|
+
await fs.writeJson(apiDocPath, apiPath, { spaces: 2 });
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
const componentSchema = buildComponentSchema(api);
|
|
144
|
+
|
|
145
|
+
schemas = {
|
|
146
|
+
...schemas,
|
|
147
|
+
...componentSchema,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
paths = { ...paths, ...apiPath };
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
const fullDocJsonPath = path.join(
|
|
@@ -148,27 +156,26 @@ module.exports = ({ strapi }) => {
|
|
|
148
156
|
'full_documentation.json'
|
|
149
157
|
);
|
|
150
158
|
|
|
151
|
-
const
|
|
159
|
+
const defaultConfig = _.cloneDeep(defaultPluginConfig);
|
|
152
160
|
|
|
153
161
|
const serverUrl = getAbsoluteServerUrl(strapi.config);
|
|
154
162
|
const apiPath = strapi.config.get('api.rest.prefix');
|
|
155
163
|
|
|
156
|
-
_.set(
|
|
164
|
+
_.set(defaultConfig, 'servers', [
|
|
157
165
|
{
|
|
158
166
|
url: `${serverUrl}${apiPath}`,
|
|
159
167
|
description: 'Development server',
|
|
160
168
|
},
|
|
161
169
|
]);
|
|
170
|
+
_.set(defaultConfig, ['info', 'x-generation-date'], new Date().toISOString());
|
|
171
|
+
_.set(defaultConfig, ['info', 'version'], version);
|
|
172
|
+
_.merge(defaultConfig.components, { schemas });
|
|
162
173
|
|
|
163
|
-
|
|
164
|
-
_.
|
|
165
|
-
|
|
166
|
-
const customSettings = await this.getCustomSettings();
|
|
167
|
-
|
|
168
|
-
const settings = _.merge(defaultSettings, customSettings);
|
|
174
|
+
const customConfig = await this.getCustomConfig();
|
|
175
|
+
const config = _.merge(defaultConfig, customConfig);
|
|
169
176
|
|
|
170
177
|
await fs.ensureFile(fullDocJsonPath);
|
|
171
|
-
await fs.writeJson(fullDocJsonPath, { ...
|
|
178
|
+
await fs.writeJson(fullDocJsonPath, { ...config, paths }, { spaces: 2 });
|
|
172
179
|
},
|
|
173
180
|
};
|
|
174
181
|
};
|
|
@@ -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: 'string' },
|
|
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;
|