@strapi/plugin-documentation 5.0.0-beta.0 → 5.0.0-beta.1
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/README.md +0 -1
- package/dist/_chunks/{index-7xstUX8_.mjs → App-ig-uE4do.mjs} +48 -14
- package/dist/_chunks/App-ig-uE4do.mjs.map +1 -0
- package/dist/_chunks/{index-D1KkfApT.js → App-o4uH8gaQ.js} +73 -20
- package/dist/_chunks/App-o4uH8gaQ.js.map +1 -0
- package/dist/_chunks/{index-VpLAJXMs.mjs → Settings-3hsPOP_b.mjs} +64 -33
- package/dist/_chunks/Settings-3hsPOP_b.mjs.map +1 -0
- package/dist/_chunks/{index-NbPCucJl.js → Settings-XmOzLTUn.js} +69 -37
- package/dist/_chunks/Settings-XmOzLTUn.js.map +1 -0
- package/dist/_chunks/getTrad-bnElvR8_.js +5 -0
- package/dist/_chunks/getTrad-bnElvR8_.js.map +1 -0
- package/dist/_chunks/getTrad-md7Tjpcv.mjs +6 -0
- package/dist/_chunks/getTrad-md7Tjpcv.mjs.map +1 -0
- package/{server/public/index.html → dist/_chunks/index-MKWIGajW.mjs} +9 -4
- package/dist/_chunks/index-MKWIGajW.mjs.map +1 -0
- package/dist/_chunks/index-WbbYm9_u.js +75 -0
- package/dist/_chunks/index-WbbYm9_u.js.map +1 -0
- package/dist/_chunks/{index-NvJ4m2q5.mjs → index-jpDwTC-Q.mjs} +121 -117
- package/dist/_chunks/index-jpDwTC-Q.mjs.map +1 -0
- package/dist/_chunks/{index-r7HsQTou.js → index-vNbIS1u2.js} +119 -115
- package/dist/_chunks/index-vNbIS1u2.js.map +1 -0
- package/dist/_chunks/login-HAajOKpu.js +150 -0
- package/dist/_chunks/login-HAajOKpu.js.map +1 -0
- package/{server/public/login.html → dist/_chunks/login-slUa679p.mjs} +6 -1
- package/dist/_chunks/login-slUa679p.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/admin/src/components/SettingsForm.d.ts +8 -0
- package/dist/admin/src/constants.d.ts +18 -0
- package/dist/admin/src/index.d.ts +14 -0
- package/dist/admin/src/pages/App.d.ts +2 -0
- package/dist/admin/src/pages/Settings.d.ts +2 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/services/api.d.ts +25 -0
- package/dist/admin/src/types.d.ts +16 -0
- package/dist/admin/src/utils/baseQuery.d.ts +20 -0
- package/dist/admin/src/utils/getTrad.d.ts +1 -0
- package/dist/admin/src/utils/index.d.ts +2 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +2 -0
- package/dist/server/index.js +1263 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +1238 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -0
- package/dist/server/src/config/default-plugin-config.d.ts +3 -0
- package/dist/server/src/config/default-plugin-config.d.ts.map +1 -0
- package/dist/server/src/config/index.d.ts +4 -0
- package/dist/server/src/config/index.d.ts.map +1 -0
- package/dist/server/src/controllers/documentation.d.ts +12 -0
- package/dist/server/src/controllers/documentation.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +14 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +91 -0
- package/dist/server/src/index.d.ts.map +1 -0
- package/dist/server/src/middlewares/documentation.d.ts +5 -0
- package/dist/server/src/middlewares/documentation.d.ts.map +1 -0
- package/dist/server/src/middlewares/restrict-access.d.ts +4 -0
- package/dist/server/src/middlewares/restrict-access.d.ts.map +1 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/register.d.ts.map +1 -0
- package/dist/server/src/routes/index.d.ts +36 -0
- package/dist/server/src/routes/index.d.ts.map +1 -0
- package/dist/server/src/services/__mocks__/mock-content-types.d.ts +449 -0
- package/dist/server/src/services/__mocks__/mock-content-types.d.ts.map +1 -0
- package/dist/server/src/services/__mocks__/mock-strapi-data.d.ts +592 -0
- package/dist/server/src/services/__mocks__/mock-strapi-data.d.ts.map +1 -0
- package/dist/server/src/services/documentation.d.ts +36 -0
- package/dist/server/src/services/documentation.d.ts.map +1 -0
- package/dist/server/src/services/helpers/build-api-endpoint-path.d.ts +7 -0
- package/dist/server/src/services/helpers/build-api-endpoint-path.d.ts.map +1 -0
- package/dist/server/src/services/helpers/build-component-schema.d.ts +4 -0
- package/dist/server/src/services/helpers/build-component-schema.d.ts.map +1 -0
- package/dist/server/src/services/helpers/index.d.ts +4 -0
- package/dist/server/src/services/helpers/index.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/clean-schema-attributes.d.ts +15 -0
- package/dist/server/src/services/helpers/utils/clean-schema-attributes.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/get-api-responses.d.ts +15 -0
- package/dist/server/src/services/helpers/utils/get-api-responses.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/get-schema-data.d.ts +12 -0
- package/dist/server/src/services/helpers/utils/get-schema-data.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/loop-content-type-names.d.ts +7 -0
- package/dist/server/src/services/helpers/utils/loop-content-type-names.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/pascal-case.d.ts +3 -0
- package/dist/server/src/services/helpers/utils/pascal-case.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/query-params.d.ts +4 -0
- package/dist/server/src/services/helpers/utils/query-params.d.ts.map +1 -0
- package/dist/server/src/services/helpers/utils/routes.d.ts +3 -0
- package/dist/server/src/services/helpers/utils/routes.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +43 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/override.d.ts +21 -0
- package/dist/server/src/services/override.d.ts.map +1 -0
- package/dist/server/src/services/utils/get-plugins-that-need-documentation.d.ts +4 -0
- package/dist/server/src/services/utils/get-plugins-that-need-documentation.d.ts.map +1 -0
- package/dist/server/src/types.d.ts +28 -0
- package/dist/server/src/types.d.ts.map +1 -0
- package/dist/server/src/utils.d.ts +12 -0
- package/dist/server/src/utils.d.ts.map +1 -0
- package/package.json +33 -15
- package/strapi-server.js +1 -1
- package/.eslintignore +0 -1
- package/.eslintrc +0 -17
- package/admin/src/constants.js +0 -17
- package/admin/src/hooks/useDocumentation.js +0 -81
- package/admin/src/index.js +0 -62
- package/admin/src/pages/PluginPage/index.jsx +0 -212
- package/admin/src/pages/PluginPage/tests/index.test.jsx +0 -160
- package/admin/src/pages/SettingsPage/index.jsx +0 -202
- package/admin/src/pages/SettingsPage/tests/index.test.jsx +0 -72
- package/admin/src/pluginId.js +0 -5
- package/admin/src/translations/ar.json +0 -20
- package/admin/src/translations/cs.json +0 -21
- package/admin/src/translations/de.json +0 -26
- package/admin/src/translations/dk.json +0 -39
- package/admin/src/translations/en.json +0 -39
- package/admin/src/translations/es.json +0 -39
- package/admin/src/translations/fr.json +0 -26
- package/admin/src/translations/id.json +0 -24
- package/admin/src/translations/it.json +0 -26
- package/admin/src/translations/ko.json +0 -39
- package/admin/src/translations/ms.json +0 -23
- package/admin/src/translations/nl.json +0 -21
- package/admin/src/translations/pl.json +0 -39
- package/admin/src/translations/pt-BR.json +0 -21
- package/admin/src/translations/pt.json +0 -21
- package/admin/src/translations/ru.json +0 -39
- package/admin/src/translations/sk.json +0 -24
- package/admin/src/translations/sv.json +0 -39
- package/admin/src/translations/th.json +0 -24
- package/admin/src/translations/tr.json +0 -39
- package/admin/src/translations/uk.json +0 -23
- package/admin/src/translations/vi.json +0 -24
- package/admin/src/translations/zh-Hans.json +0 -28
- package/admin/src/translations/zh.json +0 -39
- package/admin/src/utils/getTrad.js +0 -5
- package/admin/src/utils/index.js +0 -2
- package/admin/src/utils/prefixPluginTranslations.js +0 -13
- package/dist/_chunks/index-7xstUX8_.mjs.map +0 -1
- package/dist/_chunks/index-D1KkfApT.js.map +0 -1
- package/dist/_chunks/index-NbPCucJl.js.map +0 -1
- package/dist/_chunks/index-NvJ4m2q5.mjs.map +0 -1
- package/dist/_chunks/index-VpLAJXMs.mjs.map +0 -1
- package/dist/_chunks/index-r7HsQTou.js.map +0 -1
- package/dist/_chunks/useDocumentation-6Ks-_Ms6.mjs +0 -68
- package/dist/_chunks/useDocumentation-6Ks-_Ms6.mjs.map +0 -1
- package/dist/_chunks/useDocumentation-S0e4mU-U.js +0 -67
- package/dist/_chunks/useDocumentation-S0e4mU-U.js.map +0 -1
- package/jest.config.front.js +0 -7
- package/jest.config.js +0 -6
- package/packup.config.ts +0 -22
- package/server/bootstrap.js +0 -54
- package/server/config/default-plugin-config.js +0 -35
- package/server/config/index.js +0 -7
- package/server/controllers/documentation.js +0 -241
- package/server/controllers/index.js +0 -7
- package/server/index.js +0 -17
- package/server/middlewares/documentation.js +0 -25
- package/server/middlewares/index.js +0 -7
- package/server/middlewares/restrict-access.js +0 -24
- package/server/register.js +0 -11
- package/server/routes/index.js +0 -84
- package/server/services/__mocks__/mock-content-types.js +0 -264
- package/server/services/__mocks__/mock-strapi-data.js +0 -183
- package/server/services/__tests__/build-component-schema.test.js +0 -761
- package/server/services/__tests__/documentation.test.js +0 -481
- package/server/services/__tests__/override.test.js +0 -85
- package/server/services/documentation.js +0 -246
- package/server/services/helpers/build-api-endpoint-path.js +0 -186
- package/server/services/helpers/build-component-schema.js +0 -254
- package/server/services/helpers/index.js +0 -9
- package/server/services/helpers/utils/clean-schema-attributes.js +0 -246
- package/server/services/helpers/utils/get-api-responses.js +0 -105
- package/server/services/helpers/utils/get-schema-data.js +0 -32
- package/server/services/helpers/utils/loop-content-type-names.js +0 -55
- package/server/services/helpers/utils/pascal-case.js +0 -9
- package/server/services/helpers/utils/query-params.js +0 -105
- package/server/services/helpers/utils/routes.js +0 -10
- package/server/services/index.js +0 -9
- package/server/services/override.js +0 -52
- package/server/services/utils/default-openapi-components.js +0 -40
- package/server/services/utils/get-plugins-that-need-documentation.js +0 -24
- package/tests/server.js +0 -37
- package/tests/setup.js +0 -15
|
@@ -0,0 +1,1238 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import koaStatic from "koa-static";
|
|
3
|
+
import swaggerUi from "swagger-ui-dist";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import { produce } from "immer";
|
|
6
|
+
import _ from "lodash";
|
|
7
|
+
import * as pathToRegexp from "path-to-regexp";
|
|
8
|
+
import bcrypt from "bcryptjs";
|
|
9
|
+
import { validateYupSchema, yup } from "@strapi/utils";
|
|
10
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
11
|
+
return strapi2.plugin("documentation").service(name);
|
|
12
|
+
};
|
|
13
|
+
const RBAC_ACTIONS = [
|
|
14
|
+
{
|
|
15
|
+
section: "plugins",
|
|
16
|
+
displayName: "Access the Documentation",
|
|
17
|
+
uid: "read",
|
|
18
|
+
pluginName: "documentation"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
section: "plugins",
|
|
22
|
+
displayName: "Update and delete",
|
|
23
|
+
uid: "settings.update",
|
|
24
|
+
pluginName: "documentation"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
section: "plugins",
|
|
28
|
+
displayName: "Regenerate",
|
|
29
|
+
uid: "settings.regenerate",
|
|
30
|
+
pluginName: "documentation"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
section: "settings",
|
|
34
|
+
displayName: "Access the documentation settings page",
|
|
35
|
+
uid: "settings.read",
|
|
36
|
+
pluginName: "documentation",
|
|
37
|
+
category: "documentation"
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
async function bootstrap({ strapi: strapi2 }) {
|
|
41
|
+
await strapi2.admin?.services.permission.actionProvider.registerMany(RBAC_ACTIONS);
|
|
42
|
+
const pluginStore = strapi2.store({
|
|
43
|
+
environment: "",
|
|
44
|
+
type: "plugin",
|
|
45
|
+
name: "documentation"
|
|
46
|
+
});
|
|
47
|
+
const config2 = await pluginStore.get({ key: "config" });
|
|
48
|
+
if (!config2) {
|
|
49
|
+
pluginStore.set({ key: "config", value: { restrictedAccess: false } });
|
|
50
|
+
}
|
|
51
|
+
await getService("documentation").generateFullDoc();
|
|
52
|
+
}
|
|
53
|
+
const addDocumentMiddlewares = async ({ strapi: strapi2 }) => {
|
|
54
|
+
strapi2.server.routes([
|
|
55
|
+
{
|
|
56
|
+
method: "GET",
|
|
57
|
+
path: "/plugins/documentation/(.*)",
|
|
58
|
+
async handler(ctx, next) {
|
|
59
|
+
ctx.url = path.basename(ctx.url);
|
|
60
|
+
return koaStatic(swaggerUi.getAbsoluteFSPath(), {
|
|
61
|
+
maxage: 864e5,
|
|
62
|
+
defer: true
|
|
63
|
+
})(ctx, next);
|
|
64
|
+
},
|
|
65
|
+
config: {
|
|
66
|
+
auth: false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
};
|
|
71
|
+
async function register({ strapi: strapi2 }) {
|
|
72
|
+
await addDocumentMiddlewares({ strapi: strapi2 });
|
|
73
|
+
}
|
|
74
|
+
const pascalCase = (string) => {
|
|
75
|
+
return _.upperFirst(_.camelCase(string));
|
|
76
|
+
};
|
|
77
|
+
const params = [
|
|
78
|
+
{
|
|
79
|
+
name: "sort",
|
|
80
|
+
in: "query",
|
|
81
|
+
description: "Sort by attributes ascending (asc) or descending (desc)",
|
|
82
|
+
deprecated: false,
|
|
83
|
+
required: false,
|
|
84
|
+
schema: {
|
|
85
|
+
type: "string"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "pagination[withCount]",
|
|
90
|
+
in: "query",
|
|
91
|
+
description: "Return page/pageSize (default: true)",
|
|
92
|
+
deprecated: false,
|
|
93
|
+
required: false,
|
|
94
|
+
schema: {
|
|
95
|
+
type: "boolean"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "pagination[page]",
|
|
100
|
+
in: "query",
|
|
101
|
+
description: "Page number (default: 0)",
|
|
102
|
+
deprecated: false,
|
|
103
|
+
required: false,
|
|
104
|
+
schema: {
|
|
105
|
+
type: "integer"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "pagination[pageSize]",
|
|
110
|
+
in: "query",
|
|
111
|
+
description: "Page size (default: 25)",
|
|
112
|
+
deprecated: false,
|
|
113
|
+
required: false,
|
|
114
|
+
schema: {
|
|
115
|
+
type: "integer"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "pagination[start]",
|
|
120
|
+
in: "query",
|
|
121
|
+
description: "Offset value (default: 0)",
|
|
122
|
+
deprecated: false,
|
|
123
|
+
required: false,
|
|
124
|
+
schema: {
|
|
125
|
+
type: "integer"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "pagination[limit]",
|
|
130
|
+
in: "query",
|
|
131
|
+
description: "Number of entities to return (default: 25)",
|
|
132
|
+
deprecated: false,
|
|
133
|
+
required: false,
|
|
134
|
+
schema: {
|
|
135
|
+
type: "integer"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "fields",
|
|
140
|
+
in: "query",
|
|
141
|
+
description: "Fields to return (ex: title,author)",
|
|
142
|
+
deprecated: false,
|
|
143
|
+
required: false,
|
|
144
|
+
schema: {
|
|
145
|
+
type: "string"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "populate",
|
|
150
|
+
in: "query",
|
|
151
|
+
description: "Relations to return",
|
|
152
|
+
deprecated: false,
|
|
153
|
+
required: false,
|
|
154
|
+
schema: {
|
|
155
|
+
type: "string"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "filters",
|
|
160
|
+
in: "query",
|
|
161
|
+
description: "Filters to apply",
|
|
162
|
+
deprecated: false,
|
|
163
|
+
required: false,
|
|
164
|
+
schema: {
|
|
165
|
+
type: "object"
|
|
166
|
+
},
|
|
167
|
+
style: "deepObject"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "locale",
|
|
171
|
+
in: "query",
|
|
172
|
+
description: "Locale to apply",
|
|
173
|
+
deprecated: false,
|
|
174
|
+
required: false,
|
|
175
|
+
schema: {
|
|
176
|
+
type: "string"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
];
|
|
180
|
+
const loopContentTypeNames = (api, callback) => {
|
|
181
|
+
let result = {};
|
|
182
|
+
for (const contentTypeName of api.ctNames) {
|
|
183
|
+
const uid = `${api.getter}::${api.name}.${contentTypeName}`;
|
|
184
|
+
const { attributes, info: contentTypeInfo, kind } = strapi.contentType(uid);
|
|
185
|
+
const routeInfo = api.getter === "plugin" ? (
|
|
186
|
+
// @ts-expect-error - This is a valid check
|
|
187
|
+
strapi.plugin(api.name).routes["content-api"]
|
|
188
|
+
) : strapi.api[api.name].routes[contentTypeName];
|
|
189
|
+
if (!routeInfo) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const apiName = _.upperFirst(api.name);
|
|
193
|
+
const uniqueName = api.name === contentTypeName ? apiName : `${apiName} - ${_.upperFirst(contentTypeName)}`;
|
|
194
|
+
const apiInfo = {
|
|
195
|
+
...api,
|
|
196
|
+
routeInfo,
|
|
197
|
+
attributes,
|
|
198
|
+
uniqueName,
|
|
199
|
+
contentTypeInfo,
|
|
200
|
+
kind
|
|
201
|
+
};
|
|
202
|
+
result = {
|
|
203
|
+
...result,
|
|
204
|
+
...callback(apiInfo)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
};
|
|
209
|
+
const getApiResponse = ({
|
|
210
|
+
uniqueName,
|
|
211
|
+
route,
|
|
212
|
+
isListOfEntities = false
|
|
213
|
+
}) => {
|
|
214
|
+
const getSchema = () => {
|
|
215
|
+
if (route.method === "DELETE") {
|
|
216
|
+
return {
|
|
217
|
+
type: "integer",
|
|
218
|
+
format: "int64"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (isListOfEntities) {
|
|
222
|
+
return { $ref: `#/components/schemas/${pascalCase(uniqueName)}ListResponse` };
|
|
223
|
+
}
|
|
224
|
+
return { $ref: `#/components/schemas/${pascalCase(uniqueName)}Response` };
|
|
225
|
+
};
|
|
226
|
+
const schema = getSchema();
|
|
227
|
+
return {
|
|
228
|
+
200: {
|
|
229
|
+
description: "OK",
|
|
230
|
+
content: {
|
|
231
|
+
"application/json": {
|
|
232
|
+
schema
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
400: {
|
|
237
|
+
description: "Bad Request",
|
|
238
|
+
content: {
|
|
239
|
+
"application/json": {
|
|
240
|
+
schema: {
|
|
241
|
+
$ref: "#/components/schemas/Error"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
401: {
|
|
247
|
+
description: "Unauthorized",
|
|
248
|
+
content: {
|
|
249
|
+
"application/json": {
|
|
250
|
+
schema: {
|
|
251
|
+
$ref: "#/components/schemas/Error"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
403: {
|
|
257
|
+
description: "Forbidden",
|
|
258
|
+
content: {
|
|
259
|
+
"application/json": {
|
|
260
|
+
schema: {
|
|
261
|
+
$ref: "#/components/schemas/Error"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
404: {
|
|
267
|
+
description: "Not Found",
|
|
268
|
+
content: {
|
|
269
|
+
"application/json": {
|
|
270
|
+
schema: {
|
|
271
|
+
$ref: "#/components/schemas/Error"
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
500: {
|
|
277
|
+
description: "Internal Server Error",
|
|
278
|
+
content: {
|
|
279
|
+
"application/json": {
|
|
280
|
+
schema: {
|
|
281
|
+
$ref: "#/components/schemas/Error"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
const hasFindMethod = (handler) => {
|
|
289
|
+
if (typeof handler === "string") {
|
|
290
|
+
return handler.split(".").pop() === "find";
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
};
|
|
294
|
+
const parsePathWithVariables = (routePath) => {
|
|
295
|
+
return pathToRegexp.parse(routePath).map((token) => {
|
|
296
|
+
if (_.isObject(token)) {
|
|
297
|
+
return `${token.prefix}{${token.name}}`;
|
|
298
|
+
}
|
|
299
|
+
return token;
|
|
300
|
+
}).join("");
|
|
301
|
+
};
|
|
302
|
+
const getPathParams = (routePath) => {
|
|
303
|
+
return pathToRegexp.parse(routePath).reduce((acc, param) => {
|
|
304
|
+
if (!(typeof param === "object")) {
|
|
305
|
+
return acc;
|
|
306
|
+
}
|
|
307
|
+
acc.push({
|
|
308
|
+
name: `${param.name}`,
|
|
309
|
+
in: "path",
|
|
310
|
+
description: "",
|
|
311
|
+
deprecated: false,
|
|
312
|
+
required: true,
|
|
313
|
+
schema: { type: "number" }
|
|
314
|
+
});
|
|
315
|
+
return acc;
|
|
316
|
+
}, []);
|
|
317
|
+
};
|
|
318
|
+
const getPathWithPrefix = (prefix, route) => {
|
|
319
|
+
if (prefix && !_.has(route.config, "prefix")) {
|
|
320
|
+
return prefix.concat(route.path);
|
|
321
|
+
}
|
|
322
|
+
return route.path;
|
|
323
|
+
};
|
|
324
|
+
const getPaths = ({ routeInfo, uniqueName, contentTypeInfo, kind }) => {
|
|
325
|
+
const contentTypeRoutes = routeInfo.routes.filter((route) => {
|
|
326
|
+
return route.path.includes(contentTypeInfo.pluralName) || route.path.includes(contentTypeInfo.singularName);
|
|
327
|
+
});
|
|
328
|
+
const paths = contentTypeRoutes.reduce((acc, route) => {
|
|
329
|
+
const isListOfEntities = hasFindMethod(route.handler);
|
|
330
|
+
const methodVerb = route.method.toLowerCase();
|
|
331
|
+
const hasPathParams = route.path.includes("/:");
|
|
332
|
+
const pathWithPrefix = getPathWithPrefix(routeInfo.prefix, route);
|
|
333
|
+
const routePath = hasPathParams ? parsePathWithVariables(pathWithPrefix) : pathWithPrefix;
|
|
334
|
+
const responses = getApiResponse({
|
|
335
|
+
uniqueName,
|
|
336
|
+
route,
|
|
337
|
+
isListOfEntities: kind !== "singleType" && isListOfEntities
|
|
338
|
+
});
|
|
339
|
+
const swaggerConfig = {
|
|
340
|
+
responses,
|
|
341
|
+
tags: [_.upperFirst(uniqueName)],
|
|
342
|
+
parameters: [],
|
|
343
|
+
operationId: `${methodVerb}${routePath}`
|
|
344
|
+
};
|
|
345
|
+
if (isListOfEntities) {
|
|
346
|
+
swaggerConfig.parameters?.push(...params);
|
|
347
|
+
}
|
|
348
|
+
if (hasPathParams) {
|
|
349
|
+
const pathParams = getPathParams(route.path);
|
|
350
|
+
swaggerConfig.parameters?.push(...pathParams);
|
|
351
|
+
}
|
|
352
|
+
if (["post", "put"].includes(methodVerb)) {
|
|
353
|
+
const refName = "Request";
|
|
354
|
+
const requestBody = {
|
|
355
|
+
required: true,
|
|
356
|
+
content: {
|
|
357
|
+
"application/json": {
|
|
358
|
+
schema: {
|
|
359
|
+
$ref: `#/components/schemas/${pascalCase(uniqueName)}${refName}`
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
swaggerConfig.requestBody = requestBody;
|
|
365
|
+
}
|
|
366
|
+
_.set(acc, `${routePath}.${methodVerb}`, swaggerConfig);
|
|
367
|
+
return acc;
|
|
368
|
+
}, {});
|
|
369
|
+
return paths;
|
|
370
|
+
};
|
|
371
|
+
const buildApiEndpointPath = (api) => {
|
|
372
|
+
return loopContentTypeNames(api, getPaths);
|
|
373
|
+
};
|
|
374
|
+
const getSchemaData = (isListOfEntities, attributes) => {
|
|
375
|
+
if (isListOfEntities) {
|
|
376
|
+
return {
|
|
377
|
+
type: "array",
|
|
378
|
+
items: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
id: { type: "number" },
|
|
382
|
+
documentId: { type: "string" },
|
|
383
|
+
...attributes
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: {
|
|
391
|
+
id: { type: "number" },
|
|
392
|
+
documentId: { type: "string" },
|
|
393
|
+
...attributes
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
};
|
|
397
|
+
const cleanSchemaAttributes = (attributes, { typeMap = /* @__PURE__ */ new Map(), isRequest = false, didAddStrapiComponentsToSchemas }) => {
|
|
398
|
+
const schemaAttributes = {};
|
|
399
|
+
for (const prop of Object.keys(attributes)) {
|
|
400
|
+
const attribute = attributes[prop];
|
|
401
|
+
switch (attribute.type) {
|
|
402
|
+
case "password": {
|
|
403
|
+
if (!isRequest) {
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
schemaAttributes[prop] = { type: "string", format: "password", example: "*******" };
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
case "email": {
|
|
410
|
+
schemaAttributes[prop] = { type: "string", format: "email" };
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
case "string":
|
|
414
|
+
case "text":
|
|
415
|
+
case "richtext": {
|
|
416
|
+
schemaAttributes[prop] = { type: "string" };
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case "timestamp": {
|
|
420
|
+
schemaAttributes[prop] = { type: "string", format: "timestamp", example: Date.now() };
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "time": {
|
|
424
|
+
schemaAttributes[prop] = { type: "string", format: "time", example: "12:54.000" };
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case "date": {
|
|
428
|
+
schemaAttributes[prop] = { type: "string", format: "date" };
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case "datetime": {
|
|
432
|
+
schemaAttributes[prop] = { type: "string", format: "date-time" };
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case "boolean": {
|
|
436
|
+
schemaAttributes[prop] = { type: "boolean" };
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case "enumeration": {
|
|
440
|
+
schemaAttributes[prop] = { type: "string", enum: [...attribute.enum] };
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case "decimal":
|
|
444
|
+
case "float": {
|
|
445
|
+
schemaAttributes[prop] = { type: "number", format: "float" };
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case "integer": {
|
|
449
|
+
schemaAttributes[prop] = { type: "integer" };
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case "biginteger": {
|
|
453
|
+
schemaAttributes[prop] = { type: "string", pattern: "^\\d*$", example: "123456789" };
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case "json":
|
|
457
|
+
case "blocks": {
|
|
458
|
+
schemaAttributes[prop] = {};
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case "uid": {
|
|
462
|
+
schemaAttributes[prop] = { type: "string" };
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
case "component": {
|
|
466
|
+
const componentAttributes = strapi.components[attribute.component].attributes;
|
|
467
|
+
const rawComponentSchema = {
|
|
468
|
+
type: "object",
|
|
469
|
+
properties: {
|
|
470
|
+
...isRequest ? {} : { id: { type: "number" } },
|
|
471
|
+
...cleanSchemaAttributes(componentAttributes, {
|
|
472
|
+
typeMap,
|
|
473
|
+
isRequest,
|
|
474
|
+
didAddStrapiComponentsToSchemas
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const refComponentSchema = {
|
|
479
|
+
$ref: `#/components/schemas/${pascalCase(attribute.component)}Component`
|
|
480
|
+
};
|
|
481
|
+
const componentExists = didAddStrapiComponentsToSchemas(
|
|
482
|
+
`${pascalCase(attribute.component)}Component`,
|
|
483
|
+
rawComponentSchema
|
|
484
|
+
);
|
|
485
|
+
const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema;
|
|
486
|
+
if (attribute.repeatable) {
|
|
487
|
+
schemaAttributes[prop] = {
|
|
488
|
+
type: "array",
|
|
489
|
+
items: finalComponentSchema
|
|
490
|
+
};
|
|
491
|
+
} else {
|
|
492
|
+
schemaAttributes[prop] = finalComponentSchema;
|
|
493
|
+
}
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case "dynamiczone": {
|
|
497
|
+
const components = attribute.components.map((component) => {
|
|
498
|
+
const componentAttributes = strapi.components[component].attributes;
|
|
499
|
+
const rawComponentSchema = {
|
|
500
|
+
type: "object",
|
|
501
|
+
properties: {
|
|
502
|
+
...isRequest ? {} : { id: { type: "number" } },
|
|
503
|
+
__component: { type: "string" },
|
|
504
|
+
...cleanSchemaAttributes(componentAttributes, {
|
|
505
|
+
typeMap,
|
|
506
|
+
isRequest,
|
|
507
|
+
didAddStrapiComponentsToSchemas
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
const refComponentSchema = {
|
|
512
|
+
$ref: `#/components/schemas/${pascalCase(component)}Component`
|
|
513
|
+
};
|
|
514
|
+
const componentExists = didAddStrapiComponentsToSchemas(
|
|
515
|
+
`${pascalCase(component)}Component`,
|
|
516
|
+
rawComponentSchema
|
|
517
|
+
);
|
|
518
|
+
const finalComponentSchema = componentExists ? refComponentSchema : rawComponentSchema;
|
|
519
|
+
return finalComponentSchema;
|
|
520
|
+
});
|
|
521
|
+
schemaAttributes[prop] = {
|
|
522
|
+
type: "array",
|
|
523
|
+
items: {
|
|
524
|
+
anyOf: components
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case "media": {
|
|
530
|
+
const imageAttributes = strapi.contentType("plugin::upload.file").attributes;
|
|
531
|
+
const isListOfEntities = attribute.multiple ?? false;
|
|
532
|
+
if (isRequest) {
|
|
533
|
+
const oneOfType = {
|
|
534
|
+
oneOf: [{ type: "integer" }, { type: "string" }],
|
|
535
|
+
example: "string or id"
|
|
536
|
+
};
|
|
537
|
+
schemaAttributes[prop] = isListOfEntities ? { type: "array", items: oneOfType } : oneOfType;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
schemaAttributes[prop] = getSchemaData(
|
|
541
|
+
isListOfEntities,
|
|
542
|
+
cleanSchemaAttributes(imageAttributes, { typeMap, didAddStrapiComponentsToSchemas })
|
|
543
|
+
);
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case "relation": {
|
|
547
|
+
const isListOfEntities = attribute.relation.includes("ToMany");
|
|
548
|
+
if (isRequest) {
|
|
549
|
+
const oneOfType = {
|
|
550
|
+
oneOf: [{ type: "integer" }, { type: "string" }],
|
|
551
|
+
example: "string or id"
|
|
552
|
+
};
|
|
553
|
+
schemaAttributes[prop] = isListOfEntities ? { type: "array", items: oneOfType } : oneOfType;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
if (!("target" in attribute) || !attribute.target || typeMap.has(attribute.target)) {
|
|
557
|
+
schemaAttributes[prop] = getSchemaData(isListOfEntities, {});
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
typeMap.set(attribute.target, true);
|
|
561
|
+
const targetAttributes = strapi.contentType(attribute.target).attributes;
|
|
562
|
+
schemaAttributes[prop] = getSchemaData(
|
|
563
|
+
isListOfEntities,
|
|
564
|
+
cleanSchemaAttributes(targetAttributes, {
|
|
565
|
+
typeMap,
|
|
566
|
+
isRequest,
|
|
567
|
+
didAddStrapiComponentsToSchemas
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
default: {
|
|
573
|
+
throw new Error(`Invalid type ${attribute.type} while generating open api schema.`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return schemaAttributes;
|
|
578
|
+
};
|
|
579
|
+
const getRequiredAttributes = (allAttributes) => {
|
|
580
|
+
const requiredAttributes = [];
|
|
581
|
+
for (const key in allAttributes) {
|
|
582
|
+
if (allAttributes[key].required) {
|
|
583
|
+
requiredAttributes.push(key);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return requiredAttributes;
|
|
587
|
+
};
|
|
588
|
+
const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
|
589
|
+
let strapiComponentSchemas = {};
|
|
590
|
+
const schemas = {};
|
|
591
|
+
const typeName = pascalCase(uniqueName);
|
|
592
|
+
const didAddStrapiComponentsToSchemas = (schemaName, schema) => {
|
|
593
|
+
if (!Object.keys(schema) || !Object.keys(schema.properties))
|
|
594
|
+
return false;
|
|
595
|
+
strapiComponentSchemas = {
|
|
596
|
+
...strapiComponentSchemas,
|
|
597
|
+
[schemaName]: schema
|
|
598
|
+
};
|
|
599
|
+
return true;
|
|
600
|
+
};
|
|
601
|
+
const routeMethods = routeInfo.routes.map((route) => route.method);
|
|
602
|
+
const attributesToOmit = [
|
|
603
|
+
"createdAt",
|
|
604
|
+
"updatedAt",
|
|
605
|
+
"publishedAt",
|
|
606
|
+
"publishedBy",
|
|
607
|
+
"updatedBy",
|
|
608
|
+
"createdBy"
|
|
609
|
+
];
|
|
610
|
+
const attributesForRequest = _.omit(attributes, attributesToOmit);
|
|
611
|
+
const requiredRequestAttributes = getRequiredAttributes(attributesForRequest);
|
|
612
|
+
if (routeMethods.includes("POST") || routeMethods.includes("PUT")) {
|
|
613
|
+
Object.assign(schemas, {
|
|
614
|
+
[`${typeName}Request`]: {
|
|
615
|
+
type: "object",
|
|
616
|
+
required: ["data"],
|
|
617
|
+
properties: {
|
|
618
|
+
data: {
|
|
619
|
+
...requiredRequestAttributes.length && { required: requiredRequestAttributes },
|
|
620
|
+
type: "object",
|
|
621
|
+
properties: cleanSchemaAttributes(attributesForRequest, {
|
|
622
|
+
isRequest: true,
|
|
623
|
+
didAddStrapiComponentsToSchemas
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
const hasListOfEntities = routeInfo.routes.filter(
|
|
631
|
+
(route) => hasFindMethod(route.handler)
|
|
632
|
+
).length;
|
|
633
|
+
if (hasListOfEntities) {
|
|
634
|
+
Object.assign(schemas, {
|
|
635
|
+
[`${typeName}ListResponse`]: {
|
|
636
|
+
type: "object",
|
|
637
|
+
properties: {
|
|
638
|
+
data: {
|
|
639
|
+
type: "array",
|
|
640
|
+
items: {
|
|
641
|
+
$ref: `#/components/schemas/${typeName}`
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
meta: {
|
|
645
|
+
type: "object",
|
|
646
|
+
properties: {
|
|
647
|
+
pagination: {
|
|
648
|
+
type: "object",
|
|
649
|
+
properties: {
|
|
650
|
+
page: { type: "integer" },
|
|
651
|
+
pageSize: { type: "integer", minimum: 25 },
|
|
652
|
+
pageCount: { type: "integer", maximum: 1 },
|
|
653
|
+
total: { type: "integer" }
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
const requiredAttributes = getRequiredAttributes(attributes);
|
|
663
|
+
Object.assign(schemas, {
|
|
664
|
+
[`${typeName}`]: {
|
|
665
|
+
type: "object",
|
|
666
|
+
...requiredAttributes.length && { required: requiredAttributes },
|
|
667
|
+
properties: {
|
|
668
|
+
id: { type: "number" },
|
|
669
|
+
documentId: { type: "string" },
|
|
670
|
+
...cleanSchemaAttributes(attributes, { didAddStrapiComponentsToSchemas })
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
[`${typeName}Response`]: {
|
|
674
|
+
type: "object",
|
|
675
|
+
properties: {
|
|
676
|
+
data: {
|
|
677
|
+
$ref: `#/components/schemas/${typeName}`
|
|
678
|
+
},
|
|
679
|
+
meta: { type: "object" }
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
return { ...schemas, ...strapiComponentSchemas };
|
|
684
|
+
};
|
|
685
|
+
const buildComponentSchema = (api) => {
|
|
686
|
+
return loopContentTypeNames(api, getAllSchemasForContentType);
|
|
687
|
+
};
|
|
688
|
+
const getPluginsThatNeedDocumentation = (config2) => {
|
|
689
|
+
const defaultPlugins = ["upload", "users-permissions"];
|
|
690
|
+
const userPluginsConfig = config2["x-strapi-config"].plugins;
|
|
691
|
+
if (userPluginsConfig === null) {
|
|
692
|
+
return defaultPlugins;
|
|
693
|
+
}
|
|
694
|
+
if (userPluginsConfig.length) {
|
|
695
|
+
return userPluginsConfig;
|
|
696
|
+
}
|
|
697
|
+
return [];
|
|
698
|
+
};
|
|
699
|
+
const createService$1 = ({ strapi: strapi2 }) => {
|
|
700
|
+
const config2 = strapi2.config.get("plugin::documentation");
|
|
701
|
+
const pluginsThatNeedDocumentation = getPluginsThatNeedDocumentation(config2);
|
|
702
|
+
const overrideService = getService("override");
|
|
703
|
+
return {
|
|
704
|
+
getDocumentationVersion() {
|
|
705
|
+
return config2.info.version;
|
|
706
|
+
},
|
|
707
|
+
getFullDocumentationPath() {
|
|
708
|
+
return path.join(strapi2.dirs.app.extensions, "documentation", "documentation");
|
|
709
|
+
},
|
|
710
|
+
getDocumentationVersions() {
|
|
711
|
+
return fs.readdirSync(this.getFullDocumentationPath()).map((version) => {
|
|
712
|
+
try {
|
|
713
|
+
const filePath = path.resolve(
|
|
714
|
+
this.getFullDocumentationPath(),
|
|
715
|
+
version,
|
|
716
|
+
"full_documentation.json"
|
|
717
|
+
);
|
|
718
|
+
const doc = JSON.parse(fs.readFileSync(filePath).toString());
|
|
719
|
+
const generatedDate = doc.info["x-generation-date"];
|
|
720
|
+
return { version, generatedDate, url: "" };
|
|
721
|
+
} catch (err) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
}).filter((x) => x);
|
|
725
|
+
},
|
|
726
|
+
/**
|
|
727
|
+
* Returns settings stored in core-store
|
|
728
|
+
*/
|
|
729
|
+
async getDocumentationAccess() {
|
|
730
|
+
const { restrictedAccess } = await strapi2.store({
|
|
731
|
+
environment: "",
|
|
732
|
+
type: "plugin",
|
|
733
|
+
name: "documentation",
|
|
734
|
+
key: "config"
|
|
735
|
+
}).get();
|
|
736
|
+
return { restrictedAccess };
|
|
737
|
+
},
|
|
738
|
+
getApiDocumentationPath(api) {
|
|
739
|
+
if (api.getter === "plugin") {
|
|
740
|
+
return path.join(strapi2.dirs.app.extensions, api.name, "documentation");
|
|
741
|
+
}
|
|
742
|
+
return path.join(strapi2.dirs.app.api, api.name, "documentation");
|
|
743
|
+
},
|
|
744
|
+
async deleteDocumentation(version) {
|
|
745
|
+
const apis = this.getPluginAndApiInfo();
|
|
746
|
+
for (const api of apis) {
|
|
747
|
+
await fs.remove(path.join(this.getApiDocumentationPath(api), version));
|
|
748
|
+
}
|
|
749
|
+
await fs.remove(path.join(this.getFullDocumentationPath(), version));
|
|
750
|
+
},
|
|
751
|
+
getPluginAndApiInfo() {
|
|
752
|
+
const pluginsToDocument = pluginsThatNeedDocumentation.map((plugin) => {
|
|
753
|
+
return {
|
|
754
|
+
name: plugin,
|
|
755
|
+
getter: "plugin",
|
|
756
|
+
ctNames: Object.keys(strapi2.plugin(plugin).contentTypes)
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
const apisToDocument = Object.keys(strapi2.api).map((api) => {
|
|
760
|
+
return {
|
|
761
|
+
name: api,
|
|
762
|
+
getter: "api",
|
|
763
|
+
ctNames: Object.keys(strapi2.api[api].contentTypes)
|
|
764
|
+
};
|
|
765
|
+
});
|
|
766
|
+
return [...apisToDocument, ...pluginsToDocument];
|
|
767
|
+
},
|
|
768
|
+
/**
|
|
769
|
+
* @description - Creates the Swagger json files
|
|
770
|
+
*/
|
|
771
|
+
async generateFullDoc(versionOpt) {
|
|
772
|
+
const version = versionOpt ?? this.getDocumentationVersion();
|
|
773
|
+
const apis = this.getPluginAndApiInfo();
|
|
774
|
+
const apisThatNeedGeneratedDocumentation = apis.filter(
|
|
775
|
+
({ name }) => !overrideService.isEnabled(name)
|
|
776
|
+
);
|
|
777
|
+
const generatedDocumentation = await produce(config2, async (draft) => {
|
|
778
|
+
if (draft.servers?.length === 0) {
|
|
779
|
+
const serverUrl = strapi2.config.get("server.absoluteUrl");
|
|
780
|
+
const apiPath = strapi2.config.get("api.rest.prefix");
|
|
781
|
+
draft.servers = [
|
|
782
|
+
{
|
|
783
|
+
url: `${serverUrl}${apiPath}`,
|
|
784
|
+
description: "Development server"
|
|
785
|
+
}
|
|
786
|
+
];
|
|
787
|
+
}
|
|
788
|
+
if (!draft.components) {
|
|
789
|
+
draft.components = {};
|
|
790
|
+
}
|
|
791
|
+
draft.info["x-generation-date"] = (/* @__PURE__ */ new Date()).toISOString();
|
|
792
|
+
draft["x-strapi-config"].plugins = pluginsThatNeedDocumentation;
|
|
793
|
+
delete draft["x-strapi-config"].mutateDocumentation;
|
|
794
|
+
for (const api of apisThatNeedGeneratedDocumentation) {
|
|
795
|
+
const newApiPath = buildApiEndpointPath(api);
|
|
796
|
+
const generatedSchemas = buildComponentSchema(api);
|
|
797
|
+
if (generatedSchemas) {
|
|
798
|
+
draft.components.schemas = { ...draft.components.schemas, ...generatedSchemas };
|
|
799
|
+
}
|
|
800
|
+
if (newApiPath) {
|
|
801
|
+
draft.paths = { ...draft.paths, ...newApiPath };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (overrideService.registeredOverrides.length > 0) {
|
|
805
|
+
overrideService.registeredOverrides.forEach((override) => {
|
|
806
|
+
if (!override?.info?.version || override.info.version === version) {
|
|
807
|
+
if (override.tags) {
|
|
808
|
+
draft.tags = draft.tags || [];
|
|
809
|
+
draft.tags.push(...override.tags);
|
|
810
|
+
}
|
|
811
|
+
if (override.paths) {
|
|
812
|
+
draft.paths = { ...draft.paths, ...override.paths };
|
|
813
|
+
}
|
|
814
|
+
if (override.components) {
|
|
815
|
+
const keys = Object.keys(override.components);
|
|
816
|
+
keys.forEach((overrideKey) => {
|
|
817
|
+
draft.components = draft.components || {};
|
|
818
|
+
const overrideValue = override.components?.[overrideKey];
|
|
819
|
+
const originalValue = draft.components?.[overrideKey];
|
|
820
|
+
Object.assign(draft.components, {
|
|
821
|
+
[overrideKey]: {
|
|
822
|
+
...originalValue,
|
|
823
|
+
...overrideValue
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
const userMutatesDocumentation = config2["x-strapi-config"].mutateDocumentation;
|
|
833
|
+
const finalDocumentation = userMutatesDocumentation ? produce(generatedDocumentation, userMutatesDocumentation) : generatedDocumentation;
|
|
834
|
+
const fullDocJsonPath = path.join(
|
|
835
|
+
this.getFullDocumentationPath(),
|
|
836
|
+
version,
|
|
837
|
+
"full_documentation.json"
|
|
838
|
+
);
|
|
839
|
+
await fs.ensureFile(fullDocJsonPath);
|
|
840
|
+
await fs.writeJson(fullDocJsonPath, finalDocumentation, { spaces: 2 });
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
};
|
|
844
|
+
const createService = ({ strapi: strapi2 }) => {
|
|
845
|
+
const registeredOverrides = [];
|
|
846
|
+
const excludedFromGeneration = [];
|
|
847
|
+
return {
|
|
848
|
+
registeredOverrides,
|
|
849
|
+
excludedFromGeneration,
|
|
850
|
+
/**
|
|
851
|
+
*
|
|
852
|
+
* @param {(string | string[])} api - The name of the api or and array of apis to exclude from generation
|
|
853
|
+
*/
|
|
854
|
+
excludeFromGeneration(api) {
|
|
855
|
+
if (Array.isArray(api)) {
|
|
856
|
+
excludedFromGeneration.push(...api);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
excludedFromGeneration.push(api);
|
|
860
|
+
},
|
|
861
|
+
isEnabled(name) {
|
|
862
|
+
return excludedFromGeneration.includes(name);
|
|
863
|
+
},
|
|
864
|
+
registerOverride(override, opts) {
|
|
865
|
+
const { pluginOrigin, excludeFromGeneration = [] } = opts ?? {};
|
|
866
|
+
const pluginsThatNeedDocumentation = getPluginsThatNeedDocumentation(
|
|
867
|
+
strapi2.config.get("plugin::documentation")
|
|
868
|
+
);
|
|
869
|
+
if (pluginOrigin && !pluginsThatNeedDocumentation.includes(pluginOrigin))
|
|
870
|
+
return;
|
|
871
|
+
if (excludeFromGeneration.length) {
|
|
872
|
+
this.excludeFromGeneration(excludeFromGeneration);
|
|
873
|
+
}
|
|
874
|
+
let overrideToRegister = override;
|
|
875
|
+
if (typeof override === "string") {
|
|
876
|
+
overrideToRegister = require("yaml").parse(overrideToRegister);
|
|
877
|
+
}
|
|
878
|
+
registeredOverrides.push(overrideToRegister);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
};
|
|
882
|
+
const services = {
|
|
883
|
+
documentation: createService$1,
|
|
884
|
+
override: createService
|
|
885
|
+
};
|
|
886
|
+
const restrictAccess = async (ctx, next) => {
|
|
887
|
+
const pluginStore = strapi.store({ type: "plugin", name: "documentation" });
|
|
888
|
+
const config2 = await pluginStore.get({ key: "config" });
|
|
889
|
+
if (!config2.restrictedAccess) {
|
|
890
|
+
return next();
|
|
891
|
+
}
|
|
892
|
+
if (!ctx.session || !ctx.session.documentation || !ctx.session.documentation.logged) {
|
|
893
|
+
const querystring = ctx.querystring ? `?${ctx.querystring}` : "";
|
|
894
|
+
return ctx.redirect(`${strapi.config.server.url}/documentation/login${querystring}`);
|
|
895
|
+
}
|
|
896
|
+
return next();
|
|
897
|
+
};
|
|
898
|
+
const routes = [
|
|
899
|
+
{
|
|
900
|
+
method: "GET",
|
|
901
|
+
path: "/",
|
|
902
|
+
handler: "documentation.index",
|
|
903
|
+
config: {
|
|
904
|
+
auth: false,
|
|
905
|
+
middlewares: [restrictAccess]
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
method: "GET",
|
|
910
|
+
path: "/v:major(\\d+).:minor(\\d+).:patch(\\d+)",
|
|
911
|
+
handler: "documentation.index",
|
|
912
|
+
config: {
|
|
913
|
+
auth: false,
|
|
914
|
+
middlewares: [restrictAccess]
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
method: "GET",
|
|
919
|
+
path: "/login",
|
|
920
|
+
handler: "documentation.loginView",
|
|
921
|
+
config: {
|
|
922
|
+
auth: false
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
method: "POST",
|
|
927
|
+
path: "/login",
|
|
928
|
+
handler: "documentation.login",
|
|
929
|
+
config: {
|
|
930
|
+
auth: false
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
method: "GET",
|
|
935
|
+
path: "/getInfos",
|
|
936
|
+
handler: "documentation.getInfos",
|
|
937
|
+
config: {
|
|
938
|
+
policies: [
|
|
939
|
+
{ name: "admin::hasPermissions", config: { actions: ["plugin::documentation.read"] } }
|
|
940
|
+
]
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
method: "POST",
|
|
945
|
+
path: "/regenerateDoc",
|
|
946
|
+
handler: "documentation.regenerateDoc",
|
|
947
|
+
config: {
|
|
948
|
+
policies: [
|
|
949
|
+
{
|
|
950
|
+
name: "admin::hasPermissions",
|
|
951
|
+
config: { actions: ["plugin::documentation.settings.regenerate"] }
|
|
952
|
+
}
|
|
953
|
+
]
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
method: "PUT",
|
|
958
|
+
path: "/updateSettings",
|
|
959
|
+
handler: "documentation.updateSettings",
|
|
960
|
+
config: {
|
|
961
|
+
policies: [
|
|
962
|
+
{
|
|
963
|
+
name: "admin::hasPermissions",
|
|
964
|
+
config: { actions: ["plugin::documentation.settings.update"] }
|
|
965
|
+
}
|
|
966
|
+
]
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
method: "DELETE",
|
|
971
|
+
path: "/deleteDoc/:version",
|
|
972
|
+
handler: "documentation.deleteDoc",
|
|
973
|
+
config: {
|
|
974
|
+
policies: []
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
];
|
|
978
|
+
const validation = {
|
|
979
|
+
validatSettings: validateYupSchema(
|
|
980
|
+
yup.object().shape({
|
|
981
|
+
restrictedAccess: yup.boolean(),
|
|
982
|
+
password: yup.string().min(8).matches(/[a-z]/, "${path} must contain at least one lowercase character").matches(/[A-Z]/, "${path} must contain at least one uppercase character").matches(/\d/, "${path} must contain at least one number").when("restrictedAccess", (value, initSchema) => {
|
|
983
|
+
return value ? initSchema.required("password is required") : initSchema;
|
|
984
|
+
})
|
|
985
|
+
})
|
|
986
|
+
)
|
|
987
|
+
};
|
|
988
|
+
const documentation = {
|
|
989
|
+
async getInfos(ctx) {
|
|
990
|
+
try {
|
|
991
|
+
const docService = getService("documentation");
|
|
992
|
+
const docVersions = docService.getDocumentationVersions();
|
|
993
|
+
const documentationAccess = await docService.getDocumentationAccess();
|
|
994
|
+
ctx.send({
|
|
995
|
+
docVersions,
|
|
996
|
+
currentVersion: docService.getDocumentationVersion(),
|
|
997
|
+
prefix: "/documentation",
|
|
998
|
+
documentationAccess
|
|
999
|
+
});
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
strapi.log.error(err);
|
|
1002
|
+
ctx.badRequest();
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
async index(ctx, next) {
|
|
1006
|
+
try {
|
|
1007
|
+
const { major, minor, patch } = ctx.params;
|
|
1008
|
+
const version = major && minor && patch ? `${major}.${minor}.${patch}` : getService("documentation").getDocumentationVersion();
|
|
1009
|
+
const openAPISpecsPath = path.join(
|
|
1010
|
+
strapi.dirs.app.extensions,
|
|
1011
|
+
"documentation",
|
|
1012
|
+
"documentation",
|
|
1013
|
+
version,
|
|
1014
|
+
"full_documentation.json"
|
|
1015
|
+
);
|
|
1016
|
+
try {
|
|
1017
|
+
const documentation2 = fs.readFileSync(openAPISpecsPath, "utf8");
|
|
1018
|
+
const layout = (await import("../_chunks/index-MKWIGajW.mjs")).default;
|
|
1019
|
+
const filledLayout = _.template(layout)({
|
|
1020
|
+
backendUrl: strapi.config.server.url,
|
|
1021
|
+
spec: JSON.stringify(JSON.parse(documentation2))
|
|
1022
|
+
});
|
|
1023
|
+
try {
|
|
1024
|
+
const layoutPath = path.resolve(
|
|
1025
|
+
strapi.dirs.app.extensions,
|
|
1026
|
+
"documentation",
|
|
1027
|
+
"public",
|
|
1028
|
+
"index.html"
|
|
1029
|
+
);
|
|
1030
|
+
await fs.ensureFile(layoutPath);
|
|
1031
|
+
await fs.writeFile(layoutPath, filledLayout);
|
|
1032
|
+
ctx.url = path.basename(`${ctx.url}/index.html`);
|
|
1033
|
+
try {
|
|
1034
|
+
const staticFolder = path.resolve(
|
|
1035
|
+
strapi.dirs.app.extensions,
|
|
1036
|
+
"documentation",
|
|
1037
|
+
"public"
|
|
1038
|
+
);
|
|
1039
|
+
return koaStatic(staticFolder)(ctx, next);
|
|
1040
|
+
} catch (e) {
|
|
1041
|
+
strapi.log.error(e);
|
|
1042
|
+
}
|
|
1043
|
+
} catch (e) {
|
|
1044
|
+
strapi.log.error(e);
|
|
1045
|
+
}
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
strapi.log.error(e);
|
|
1048
|
+
}
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
strapi.log.error(e);
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
async loginView(ctx, next) {
|
|
1054
|
+
const cheerio = require("cheerio");
|
|
1055
|
+
const { error } = ctx.query;
|
|
1056
|
+
try {
|
|
1057
|
+
const layout = (await import("../_chunks/login-slUa679p.mjs")).default;
|
|
1058
|
+
const filledLayout = _.template(layout.toString())({
|
|
1059
|
+
actionUrl: `${strapi.config.server.url}/documentation/login`
|
|
1060
|
+
});
|
|
1061
|
+
const $ = cheerio.load(filledLayout);
|
|
1062
|
+
$(".error").text(_.isEmpty(error) ? "" : "Wrong password...");
|
|
1063
|
+
try {
|
|
1064
|
+
const layoutPath = path.resolve(
|
|
1065
|
+
strapi.dirs.app.extensions,
|
|
1066
|
+
"documentation",
|
|
1067
|
+
"public",
|
|
1068
|
+
"login.html"
|
|
1069
|
+
);
|
|
1070
|
+
await fs.ensureFile(layoutPath);
|
|
1071
|
+
await fs.writeFile(layoutPath, $.html());
|
|
1072
|
+
ctx.url = path.basename(`${ctx.url}/login.html`);
|
|
1073
|
+
try {
|
|
1074
|
+
const staticFolder = path.resolve(strapi.dirs.app.extensions, "documentation", "public");
|
|
1075
|
+
return koaStatic(staticFolder)(ctx, next);
|
|
1076
|
+
} catch (e) {
|
|
1077
|
+
strapi.log.error(e);
|
|
1078
|
+
}
|
|
1079
|
+
} catch (e) {
|
|
1080
|
+
strapi.log.error(e);
|
|
1081
|
+
}
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
strapi.log.error(e);
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
async login(ctx) {
|
|
1087
|
+
const {
|
|
1088
|
+
body: { password }
|
|
1089
|
+
} = ctx.request;
|
|
1090
|
+
const { password: hash } = await strapi.store({ type: "plugin", name: "documentation", key: "config" }).get();
|
|
1091
|
+
const isValid = await bcrypt.compare(password, hash);
|
|
1092
|
+
let querystring = "?error=password";
|
|
1093
|
+
if (isValid && ctx.session) {
|
|
1094
|
+
ctx.session.documentation = {
|
|
1095
|
+
logged: true
|
|
1096
|
+
};
|
|
1097
|
+
querystring = "";
|
|
1098
|
+
}
|
|
1099
|
+
ctx.redirect(`${strapi.config.server.url}/documentation${querystring}`);
|
|
1100
|
+
},
|
|
1101
|
+
async regenerateDoc(ctx) {
|
|
1102
|
+
const { version } = ctx.request.body;
|
|
1103
|
+
const service = getService("documentation");
|
|
1104
|
+
const documentationVersions = service.getDocumentationVersions().map((el) => el.version);
|
|
1105
|
+
if (_.isEmpty(version)) {
|
|
1106
|
+
return ctx.badRequest("Please provide a version.");
|
|
1107
|
+
}
|
|
1108
|
+
if (!documentationVersions.includes(version)) {
|
|
1109
|
+
return ctx.badRequest("The version you are trying to generate does not exist.");
|
|
1110
|
+
}
|
|
1111
|
+
try {
|
|
1112
|
+
strapi.reload.isWatching = false;
|
|
1113
|
+
await service.generateFullDoc(version);
|
|
1114
|
+
ctx.send({ ok: true });
|
|
1115
|
+
} finally {
|
|
1116
|
+
strapi.reload.isWatching = true;
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
async deleteDoc(ctx) {
|
|
1120
|
+
const { version } = ctx.params;
|
|
1121
|
+
const service = getService("documentation");
|
|
1122
|
+
const documentationVersions = service.getDocumentationVersions().map((el) => el.version);
|
|
1123
|
+
if (_.isEmpty(version)) {
|
|
1124
|
+
return ctx.badRequest("Please provide a version.");
|
|
1125
|
+
}
|
|
1126
|
+
if (!documentationVersions.includes(version)) {
|
|
1127
|
+
return ctx.badRequest("The version you are trying to delete does not exist.");
|
|
1128
|
+
}
|
|
1129
|
+
try {
|
|
1130
|
+
strapi.reload.isWatching = false;
|
|
1131
|
+
await service.deleteDocumentation(version);
|
|
1132
|
+
ctx.send({ ok: true });
|
|
1133
|
+
} finally {
|
|
1134
|
+
strapi.reload.isWatching = true;
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
async updateSettings(ctx) {
|
|
1138
|
+
const pluginStore = strapi.store({ type: "plugin", name: "documentation" });
|
|
1139
|
+
const data = await validation.validatSettings(ctx.request.body);
|
|
1140
|
+
const config2 = {
|
|
1141
|
+
restrictedAccess: Boolean(data.restrictedAccess)
|
|
1142
|
+
};
|
|
1143
|
+
if (data.password) {
|
|
1144
|
+
config2.password = await bcrypt.hash(data.password, 10);
|
|
1145
|
+
}
|
|
1146
|
+
await pluginStore.set({ key: "config", value: config2 });
|
|
1147
|
+
return ctx.send({ ok: true });
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
const controllers = {
|
|
1151
|
+
documentation
|
|
1152
|
+
};
|
|
1153
|
+
const defaultConfig = {
|
|
1154
|
+
openapi: "3.0.0",
|
|
1155
|
+
info: {
|
|
1156
|
+
version: "1.0.0",
|
|
1157
|
+
title: "DOCUMENTATION",
|
|
1158
|
+
description: "",
|
|
1159
|
+
termsOfService: "YOUR_TERMS_OF_SERVICE_URL",
|
|
1160
|
+
contact: {
|
|
1161
|
+
name: "TEAM",
|
|
1162
|
+
email: "contact-email@something.io",
|
|
1163
|
+
url: "mywebsite.io"
|
|
1164
|
+
},
|
|
1165
|
+
license: {
|
|
1166
|
+
name: "Apache 2.0",
|
|
1167
|
+
url: "https://www.apache.org/licenses/LICENSE-2.0.html"
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
"x-strapi-config": {
|
|
1171
|
+
plugins: null,
|
|
1172
|
+
mutateDocumentation: null
|
|
1173
|
+
},
|
|
1174
|
+
servers: [],
|
|
1175
|
+
externalDocs: {
|
|
1176
|
+
description: "Find out more",
|
|
1177
|
+
url: "https://docs.strapi.io/developer-docs/latest/getting-started/introduction.html"
|
|
1178
|
+
},
|
|
1179
|
+
security: [
|
|
1180
|
+
{
|
|
1181
|
+
bearerAuth: []
|
|
1182
|
+
}
|
|
1183
|
+
],
|
|
1184
|
+
paths: {},
|
|
1185
|
+
components: {
|
|
1186
|
+
securitySchemes: {
|
|
1187
|
+
bearerAuth: {
|
|
1188
|
+
type: "http",
|
|
1189
|
+
scheme: "bearer",
|
|
1190
|
+
bearerFormat: "JWT"
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
schemas: {
|
|
1194
|
+
Error: {
|
|
1195
|
+
type: "object",
|
|
1196
|
+
required: ["error"],
|
|
1197
|
+
properties: {
|
|
1198
|
+
data: {
|
|
1199
|
+
nullable: true,
|
|
1200
|
+
oneOf: [{ type: "object" }, { type: "array", items: { type: "object" } }]
|
|
1201
|
+
},
|
|
1202
|
+
error: {
|
|
1203
|
+
type: "object",
|
|
1204
|
+
properties: {
|
|
1205
|
+
status: {
|
|
1206
|
+
type: "integer"
|
|
1207
|
+
},
|
|
1208
|
+
name: {
|
|
1209
|
+
type: "string"
|
|
1210
|
+
},
|
|
1211
|
+
message: {
|
|
1212
|
+
type: "string"
|
|
1213
|
+
},
|
|
1214
|
+
details: {
|
|
1215
|
+
type: "object"
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
const config = {
|
|
1225
|
+
default: defaultConfig
|
|
1226
|
+
};
|
|
1227
|
+
const index = {
|
|
1228
|
+
bootstrap,
|
|
1229
|
+
config,
|
|
1230
|
+
routes,
|
|
1231
|
+
controllers,
|
|
1232
|
+
register,
|
|
1233
|
+
services
|
|
1234
|
+
};
|
|
1235
|
+
export {
|
|
1236
|
+
index as default
|
|
1237
|
+
};
|
|
1238
|
+
//# sourceMappingURL=index.mjs.map
|