@andrewronscki/nestjs-redoc 2.0.3
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/.editorconfig +14 -0
- package/.eslintrc.js +26 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/interfaces/index.d.ts +2 -0
- package/dist/interfaces/index.js +18 -0
- package/dist/interfaces/redocDocument.interface.d.ts +8 -0
- package/dist/interfaces/redocDocument.interface.js +2 -0
- package/dist/interfaces/redocOptions.interface.d.ts +39 -0
- package/dist/interfaces/redocOptions.interface.js +2 -0
- package/dist/model/index.d.ts +1 -0
- package/dist/model/index.js +17 -0
- package/dist/model/options.model.d.ts +3 -0
- package/dist/model/options.model.js +48 -0
- package/dist/redoc-module.d.ts +11 -0
- package/dist/redoc-module.js +126 -0
- package/jest.config.js +12 -0
- package/package.json +70 -0
- package/redoc.png +0 -0
- package/sample/app.module.ts +7 -0
- package/sample/cats/cats.controller.ts +81 -0
- package/sample/cats/cats.module.ts +9 -0
- package/sample/cats/cats.service.ts +17 -0
- package/sample/cats/classes/cat.class.ts +56 -0
- package/sample/cats/dto/create-cat.dto.ts +64 -0
- package/sample/cats/dto/extra-model.dto.ts +9 -0
- package/sample/cats/dto/pagination-query.dto.ts +42 -0
- package/sample/cats/dto/tag.dto.ts +6 -0
- package/sample/main.ts +48 -0
- package/tsconfig.build.json +8 -0
- package/views/redoc.handlebars +46 -0
package/.editorconfig
ADDED
package/.eslintrc.js
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module.exports = {
|
2
|
+
parser: '@typescript-eslint/parser',
|
3
|
+
parserOptions: {
|
4
|
+
project: 'tsconfig.json',
|
5
|
+
sourceType: 'module',
|
6
|
+
},
|
7
|
+
plugins: ['@typescript-eslint/eslint-plugin', 'prettier'],
|
8
|
+
extends: [
|
9
|
+
'plugin:@typescript-eslint/eslint-recommended',
|
10
|
+
'plugin:@typescript-eslint/recommended',
|
11
|
+
'prettier',
|
12
|
+
'prettier/@typescript-eslint',
|
13
|
+
],
|
14
|
+
root: true,
|
15
|
+
env: {
|
16
|
+
node: true,
|
17
|
+
jest: true,
|
18
|
+
},
|
19
|
+
rules: {
|
20
|
+
'@typescript-eslint/interface-name-prefix': 'off',
|
21
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
22
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
23
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
24
|
+
'prettier/prettier': 'error',
|
25
|
+
},
|
26
|
+
};
|
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 André Wronscki
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
## NestJS ReDoc (Swagger UI Alternative) - André Wronscki Version
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
|
5
|
+
I have tried most of the Redoc integrate NestJS solutions available on the market, but due to a lack of maintenance over a long period, many outdated packages were being used, leading to installation failures. This version has been modified and improved, allowing for perfect integration with NestJS 11.0.11
|
6
|
+
|
7
|
+
-André Wronscki
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Customizable theme
|
12
|
+
- It's almost a drop in replacement for you current swagger UI, you only need to import this package and modify any settings you may want to change (e.g: Page title, ReDoc options)
|
13
|
+
|
14
|
+
NB: Please read the [ReDoc](https://redocly.com/docs/redoc/) Documentation firstly.
|
15
|
+
|
16
|
+
## 🗿 Installation
|
17
|
+
|
18
|
+
Using npm: `npm install @andrewronscki/nestjs-redoc`
|
19
|
+
|
20
|
+
## How to use
|
21
|
+
|
22
|
+
You need to install the [Swagger Module](https://github.com/nestjs/swagger) first if you want to get definitions updated with your project.
|
23
|
+
|
24
|
+
In future versions you will be able to pass a URL parameter as document, but for the moment you need this document object from the swagger module
|
25
|
+
|
26
|
+
```typescript
|
27
|
+
const options = new DocumentBuilder()
|
28
|
+
.setTitle('Look, i have a title')
|
29
|
+
.setDescription('A very nice description')
|
30
|
+
.setBasePath('/api/v1')
|
31
|
+
.build();
|
32
|
+
const document = SwaggerModule.createDocument(app, options);
|
33
|
+
```
|
34
|
+
|
35
|
+
Then add the following example code.
|
36
|
+
|
37
|
+
**Note**: All properties are optional, if you don't specify a title we will fallback to the one you used in your DocumentBuilder instance.
|
38
|
+
|
39
|
+
```typescript
|
40
|
+
const redocOptions: RedocOptions = {
|
41
|
+
title: 'Hello Nest',
|
42
|
+
logo: {
|
43
|
+
url: 'https://redocly.github.io/redoc/petstore-logo.png',
|
44
|
+
backgroundColor: '#F0F0F0',
|
45
|
+
altText: 'PetStore logo',
|
46
|
+
},
|
47
|
+
sortPropsAlphabetically: true,
|
48
|
+
hideDownloadButton: false,
|
49
|
+
hideHostname: false,
|
50
|
+
auth: {
|
51
|
+
enabled: true,
|
52
|
+
user: 'admin',
|
53
|
+
password: '123',
|
54
|
+
},
|
55
|
+
tagGroups: [
|
56
|
+
{
|
57
|
+
name: 'Core resources',
|
58
|
+
tags: ['cats'],
|
59
|
+
},
|
60
|
+
],
|
61
|
+
};
|
62
|
+
// Instead of using SwaggerModule.setup() you call this module
|
63
|
+
await RedocModule.setup('/docs', app, document, redocOptions);
|
64
|
+
```
|
65
|
+
|
66
|
+
### You can build a function for the setup
|
67
|
+
|
68
|
+
Create a file "setup.redoc.ts"
|
69
|
+
|
70
|
+
```typescript
|
71
|
+
import type { INestApplication } from '@nestjs/common';
|
72
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
73
|
+
import { RedocModule, RedocOptions } from '@andrewronscki/nestjs-redoc';
|
74
|
+
import * as process from 'process';
|
75
|
+
|
76
|
+
export async function setupRedoc(app: INestApplication) {
|
77
|
+
const documentBuilder = new DocumentBuilder()
|
78
|
+
.setTitle(`${process.env.DOCS_TITLE}`)
|
79
|
+
.setVersion('1.0')
|
80
|
+
.setDescription('Powered by andrewronscki')
|
81
|
+
.addBearerAuth(
|
82
|
+
{
|
83
|
+
description: `Please enter token in following format: Bearer <JWT>`,
|
84
|
+
name: 'Authorization',
|
85
|
+
bearerFormat: 'Bearer',
|
86
|
+
scheme: 'Bearer',
|
87
|
+
type: 'http',
|
88
|
+
in: 'Header',
|
89
|
+
},
|
90
|
+
'access-token',
|
91
|
+
);
|
92
|
+
|
93
|
+
if (process.env.API_VERSION) {
|
94
|
+
documentBuilder.setVersion(process.env.API_VERSION);
|
95
|
+
}
|
96
|
+
|
97
|
+
const document = SwaggerModule.createDocument(app, documentBuilder.build());
|
98
|
+
const redocOptions: RedocOptions = {
|
99
|
+
title: `${process.env.DOCS_TITLE}`,
|
100
|
+
logo: {
|
101
|
+
url: `${process.env.DOCS_LOGO}`,
|
102
|
+
backgroundColor: '#d0e8c5',
|
103
|
+
altText: 'LOGO',
|
104
|
+
},
|
105
|
+
theme: {
|
106
|
+
typography: {
|
107
|
+
fontSize: '16px',
|
108
|
+
fontWeightBold: '900',
|
109
|
+
},
|
110
|
+
sidebar: {
|
111
|
+
backgroundColor: '#d0e8c5',
|
112
|
+
},
|
113
|
+
rightPanel: {
|
114
|
+
backgroundColor: '#01312b',
|
115
|
+
},
|
116
|
+
},
|
117
|
+
sortPropsAlphabetically: true,
|
118
|
+
sortOperationsAlphabetically: true,
|
119
|
+
hideDownloadButton: false,
|
120
|
+
hideHostname: false,
|
121
|
+
noAutoAuth: true,
|
122
|
+
pathInMiddlePanel: true,
|
123
|
+
auth: {
|
124
|
+
enabled: true,
|
125
|
+
user: 'admin',
|
126
|
+
password: `${process.env.DOCS_PASSWORD}`,
|
127
|
+
},
|
128
|
+
tagGroups: [
|
129
|
+
{
|
130
|
+
name: 'Core resources',
|
131
|
+
tags: ['authentication', 'user'],
|
132
|
+
},
|
133
|
+
],
|
134
|
+
};
|
135
|
+
await RedocModule.setup('docs', app, document, redocOptions);
|
136
|
+
console.info(
|
137
|
+
`Redoc Documentation: http://localhost:${process.env.PORT}/docs`,
|
138
|
+
);
|
139
|
+
}
|
140
|
+
```
|
141
|
+
|
142
|
+
In your main.ts, add this line before "await app.listen(3000);"
|
143
|
+
|
144
|
+
```
|
145
|
+
await setupRedoc(app);
|
146
|
+
```
|
147
|
+
|
148
|
+
## Available options
|
149
|
+
|
150
|
+
### Redoc Options
|
151
|
+
|
152
|
+
| Option | Description | Type | Note |
|
153
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------ |
|
154
|
+
| title | Web site title (e.g: ReDoc documentation) | string |
|
155
|
+
| favicon | Web site favicon URL | string | Fallbacks to the document title if not set |
|
156
|
+
| logo | Logo options | LogoOptions | See LogoOptions table |
|
157
|
+
| theme | Theme options | ThemeOptions | See ThemeOptions info |
|
158
|
+
| untrustedSpec | If set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS, by default is false | boolean |
|
159
|
+
| supressWarnings | If set, warnings are not rendered at the top of documentation (they are still logged to the console) | boolean |
|
160
|
+
| hideHostname | If set, the protocol and hostname won't be shown in the operation definition | boolean |
|
161
|
+
| expandResponses | Specify which responses to expand by default by response codes, values should be passed as comma-separated list without spaces (e.g: 200, 201, "all") | string |
|
162
|
+
| requiredPropsFirst | If set, show required properties first ordered in the same order as in required array | boolean |
|
163
|
+
| sortPropsAlphabetically | If set, propeties will be sorted alphabetically | boolean |
|
164
|
+
| sortOperationsAlphabetically | If set, operations will be sorted alphabetically | boolean |
|
165
|
+
| showExtensions | If set the fields starting with "x-" will be shown, can be a boolean or a string with names of extensions to display | boolean |
|
166
|
+
| noAutoAuth | If set, redoc won't inject authentication section automatically | boolean |
|
167
|
+
| pathInMiddlePanel | If set, path link and HTTP verb will be shown in the middle panel instead of the right one | boolean |
|
168
|
+
| hideLoading | If set, loading spinner animation won't be shown | boolean |
|
169
|
+
| nativeScrollbars | If set, a native scrollbar will be used instead of perfect-scroll, this can improve performance of the frontend for big specs | boolean |
|
170
|
+
| hideDownloadButton | This will hide the "Download spec" button, it only hides the button | boolean |
|
171
|
+
| disableSearch | If set, the search bar will be disabled | boolean |
|
172
|
+
| onlyRequiredInSamples | Shows only required fileds in request samples | boolean |
|
173
|
+
| auth | Auth options | AuthOptions | See AuthOptions info |
|
174
|
+
| AuthOptions info |
|
175
|
+
| enabled | If enabled, a prompt will pop out asking for authentication details, default: `false` | boolean |
|
176
|
+
| user | User name, default: `admin` | string |
|
177
|
+
| password | User password, default: `123` | string |
|
178
|
+
| tagGroups | Tag groups options | TagGroupOptions[] | See Tag Group options |
|
179
|
+
| Tag Group options info |
|
180
|
+
| name | Tag name | string |
|
181
|
+
| tags | Tag collection | string[] |
|
182
|
+
| redocVersion | Set an specific redoc version | string,number | By default it's "latest" |
|
183
|
+
|
184
|
+
**Note**: If you want to change your ReDoc theme settings, take a look at the official ReDoc documentation: <https://github.com/Redocly/redoc/blob/master/src/theme.ts>
|
185
|
+
|
186
|
+
Apply the properties defined in ResolvedThemeInterface to the key called "theme" in the redoc options
|
187
|
+
|
188
|
+
### Logo options
|
189
|
+
|
190
|
+
| Option | Description | Type | Example |
|
191
|
+
| --------------- | ------------------------------------------------------------------------------------- | ------ | -------- |
|
192
|
+
| url | The URL pointing to the spec Logo, must be in the format of a URL and an absolute URL | string |
|
193
|
+
| backgroundColor | Background color to be used, must be RGB color in hexadecimal format (e.g: #008080) | string | #F0F0F0 |
|
194
|
+
| altText | Alt tag for Logo | string | PetStore |
|
195
|
+
| href | href tag for Logo, it defaults to the host used for your API spec | string |
|
196
|
+
|
197
|
+
### Special Thanks
|
198
|
+
|
199
|
+
Forked from https://github.com/jozefazz/nestjs-redoc
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
17
|
+
__exportStar(require("./interfaces"), exports);
|
18
|
+
__exportStar(require("./redoc-module"), exports);
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
17
|
+
__exportStar(require("./redocDocument.interface"), exports);
|
18
|
+
__exportStar(require("./redocOptions.interface"), exports);
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { OpenAPIObject } from '@nestjs/swagger';
|
2
|
+
import { LogoOptions, TagGroupOptions } from './redocOptions.interface';
|
3
|
+
export interface RedocDocument extends Partial<OpenAPIObject> {
|
4
|
+
info: OpenAPIObject['info'] & {
|
5
|
+
'x-logo'?: LogoOptions;
|
6
|
+
};
|
7
|
+
'x-tagGroups': TagGroupOptions[];
|
8
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
export interface RedocOptions {
|
2
|
+
redocVersion?: string;
|
3
|
+
title?: string;
|
4
|
+
favicon?: string;
|
5
|
+
logo?: LogoOptions;
|
6
|
+
theme?: any;
|
7
|
+
untrustedSpec?: boolean;
|
8
|
+
supressWarnings?: boolean;
|
9
|
+
hideHostname?: boolean;
|
10
|
+
expandResponses?: string;
|
11
|
+
requiredPropsFirst?: boolean;
|
12
|
+
sortPropsAlphabetically?: boolean;
|
13
|
+
sortOperationsAlphabetically?: boolean;
|
14
|
+
showExtensions?: boolean | string;
|
15
|
+
noAutoAuth?: boolean;
|
16
|
+
pathInMiddlePanel?: boolean;
|
17
|
+
hideLoading?: boolean;
|
18
|
+
nativeScrollbars?: boolean;
|
19
|
+
hideDownloadButton?: boolean;
|
20
|
+
disableSearch?: boolean;
|
21
|
+
onlyRequiredInSamples?: boolean;
|
22
|
+
docName?: string;
|
23
|
+
auth?: {
|
24
|
+
enabled: boolean;
|
25
|
+
user: string;
|
26
|
+
password: string;
|
27
|
+
};
|
28
|
+
tagGroups?: TagGroupOptions[];
|
29
|
+
}
|
30
|
+
export interface LogoOptions {
|
31
|
+
url?: string;
|
32
|
+
backgroundColor?: string;
|
33
|
+
altText?: string;
|
34
|
+
href?: string;
|
35
|
+
}
|
36
|
+
export interface TagGroupOptions {
|
37
|
+
name: string;
|
38
|
+
tags: string[];
|
39
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './options.model';
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
|
+
};
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
17
|
+
__exportStar(require("./options.model"), exports);
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.schema = void 0;
|
4
|
+
const Joi = require("joi");
|
5
|
+
const schema = (document) => Joi.object().keys({
|
6
|
+
redocVersion: Joi.string().default('latest'),
|
7
|
+
title: Joi.string()
|
8
|
+
.optional()
|
9
|
+
.default(document.info ? document.info.title : 'Swagger documentation'),
|
10
|
+
favicon: Joi.string().optional(),
|
11
|
+
logo: {
|
12
|
+
url: Joi.string().optional().uri(),
|
13
|
+
backgroundColor: Joi.string()
|
14
|
+
.optional()
|
15
|
+
.regex(new RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')),
|
16
|
+
altText: Joi.string().optional(),
|
17
|
+
href: Joi.string().optional().uri(),
|
18
|
+
},
|
19
|
+
theme: Joi.any(),
|
20
|
+
untrustedSpec: Joi.boolean().optional().default(false),
|
21
|
+
supressWarnings: Joi.boolean().optional().default(true),
|
22
|
+
hideHostname: Joi.boolean().optional().default(false),
|
23
|
+
expandResponses: Joi.string().optional(),
|
24
|
+
requiredPropsFirst: Joi.boolean().optional().default(true),
|
25
|
+
sortPropsAlphabetically: Joi.boolean().optional().default(true),
|
26
|
+
sortOperationsAlphabetically: Joi.boolean().optional().default(true),
|
27
|
+
showExtensions: Joi.any().optional().default(false),
|
28
|
+
noAutoAuth: Joi.boolean().optional().default(true),
|
29
|
+
pathInMiddlePanel: Joi.boolean().optional().default(false),
|
30
|
+
hideLoading: Joi.boolean().optional().default(false),
|
31
|
+
nativeScrollbars: Joi.boolean().optional().default(false),
|
32
|
+
hideDownloadButton: Joi.boolean().optional().default(false),
|
33
|
+
disableSearch: Joi.boolean().optional().default(false),
|
34
|
+
onlyRequiredInSamples: Joi.boolean().optional().default(false),
|
35
|
+
docName: Joi.string().optional().default('swagger'),
|
36
|
+
auth: {
|
37
|
+
enabled: Joi.boolean().optional().default(false),
|
38
|
+
user: Joi.string().default('admin'),
|
39
|
+
password: Joi.string().default('123'),
|
40
|
+
},
|
41
|
+
tagGroups: Joi.array()
|
42
|
+
.items(Joi.object({
|
43
|
+
name: Joi.string(),
|
44
|
+
tags: Joi.array().items(Joi.string()),
|
45
|
+
}))
|
46
|
+
.optional(),
|
47
|
+
});
|
48
|
+
exports.schema = schema;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { INestApplication } from '@nestjs/common';
|
2
|
+
import { OpenAPIObject } from '@nestjs/swagger';
|
3
|
+
import { RedocOptions } from './interfaces';
|
4
|
+
export declare class RedocModule {
|
5
|
+
static setup(path: string, app: INestApplication, document: OpenAPIObject, options: RedocOptions): Promise<void>;
|
6
|
+
private static setupFastify;
|
7
|
+
private static validateOptionsObject;
|
8
|
+
private static setupExpress;
|
9
|
+
private static normalizePath;
|
10
|
+
private static addVendorExtensions;
|
11
|
+
}
|
@@ -0,0 +1,126 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
12
|
+
var t = {};
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
14
|
+
t[p] = s[p];
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
18
|
+
t[p[i]] = s[p[i]];
|
19
|
+
}
|
20
|
+
return t;
|
21
|
+
};
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
24
|
+
};
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
+
exports.RedocModule = void 0;
|
27
|
+
const express_basic_auth_1 = __importDefault(require("express-basic-auth"));
|
28
|
+
const express_handlebars_1 = require("express-handlebars");
|
29
|
+
const path_1 = __importDefault(require("path"));
|
30
|
+
const url_1 = require("url");
|
31
|
+
const options_model_1 = require("./model/options.model");
|
32
|
+
class RedocModule {
|
33
|
+
static setup(path, app, document, options) {
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
35
|
+
try {
|
36
|
+
const _options = yield this.validateOptionsObject(options, document);
|
37
|
+
const redocDocument = this.addVendorExtensions(_options, document);
|
38
|
+
const httpAdapter = app.getHttpAdapter();
|
39
|
+
if (httpAdapter &&
|
40
|
+
httpAdapter.constructor &&
|
41
|
+
httpAdapter.constructor.name === 'FastifyAdapter') {
|
42
|
+
return this.setupFastify();
|
43
|
+
}
|
44
|
+
return yield this.setupExpress(path, app, redocDocument, _options);
|
45
|
+
}
|
46
|
+
catch (error) {
|
47
|
+
throw error;
|
48
|
+
}
|
49
|
+
});
|
50
|
+
}
|
51
|
+
static setupFastify() {
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
53
|
+
throw new Error('Fastify is not implemented yet');
|
54
|
+
});
|
55
|
+
}
|
56
|
+
static validateOptionsObject(options, document) {
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
58
|
+
try {
|
59
|
+
return (0, options_model_1.schema)(document).validateAsync(options);
|
60
|
+
}
|
61
|
+
catch (error) {
|
62
|
+
throw new TypeError(error.message);
|
63
|
+
}
|
64
|
+
});
|
65
|
+
}
|
66
|
+
static setupExpress(path, app, document, options) {
|
67
|
+
return __awaiter(this, void 0, void 0, function* () {
|
68
|
+
const httpAdapter = app.getHttpAdapter();
|
69
|
+
const finalPath = this.normalizePath(path);
|
70
|
+
const resolvedPath = finalPath.slice(-1) !== '/' ? finalPath + '/' : finalPath;
|
71
|
+
const docUrl = (0, url_1.resolve)(resolvedPath, `${options.docName}.json`);
|
72
|
+
const hbs = (0, express_handlebars_1.create)({
|
73
|
+
helpers: {
|
74
|
+
toJSON: function (object) {
|
75
|
+
return JSON.stringify(object);
|
76
|
+
},
|
77
|
+
},
|
78
|
+
});
|
79
|
+
const { title, favicon, theme, redocVersion } = options, otherOptions = __rest(options, ["title", "favicon", "theme", "redocVersion"]);
|
80
|
+
const renderData = {
|
81
|
+
data: Object.assign({ title,
|
82
|
+
docUrl,
|
83
|
+
favicon,
|
84
|
+
redocVersion, options: otherOptions }, (theme && {
|
85
|
+
theme: Object.assign({}, theme),
|
86
|
+
})),
|
87
|
+
};
|
88
|
+
const redocFilePath = path_1.default.join(__dirname, '..', 'views', 'redoc.handlebars');
|
89
|
+
const redocHTML = yield hbs.render(redocFilePath, renderData);
|
90
|
+
httpAdapter.get(finalPath, (req, res) => __awaiter(this, void 0, void 0, function* () {
|
91
|
+
var _a;
|
92
|
+
const sendPage = () => {
|
93
|
+
res.setHeader('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; child-src * 'unsafe-inline' 'unsafe-eval' blob:; worker-src * 'unsafe-inline' 'unsafe-eval' blob:; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';");
|
94
|
+
res.send(redocHTML);
|
95
|
+
};
|
96
|
+
if ((_a = options.auth) === null || _a === void 0 ? void 0 : _a.enabled) {
|
97
|
+
const { user, password } = options.auth;
|
98
|
+
(0, express_basic_auth_1.default)({ users: { [user]: password }, challenge: true })(req, res, () => {
|
99
|
+
sendPage();
|
100
|
+
});
|
101
|
+
}
|
102
|
+
else {
|
103
|
+
sendPage();
|
104
|
+
}
|
105
|
+
}));
|
106
|
+
httpAdapter.get(docUrl, (req, res) => {
|
107
|
+
res.setHeader('Content-Type', 'application/json');
|
108
|
+
res.send(document);
|
109
|
+
});
|
110
|
+
});
|
111
|
+
}
|
112
|
+
static normalizePath(path) {
|
113
|
+
return path.charAt(0) !== '/' ? '/' + path : path;
|
114
|
+
}
|
115
|
+
static addVendorExtensions(options, document) {
|
116
|
+
if (options.logo) {
|
117
|
+
const logoOption = Object.assign({}, options.logo);
|
118
|
+
document.info['x-logo'] = logoOption;
|
119
|
+
}
|
120
|
+
if (options.tagGroups) {
|
121
|
+
document['x-tagGroups'] = options.tagGroups;
|
122
|
+
}
|
123
|
+
return document;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
exports.RedocModule = RedocModule;
|
package/jest.config.js
ADDED
package/package.json
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
{
|
2
|
+
"name": "@andrewronscki/nestjs-redoc",
|
3
|
+
"version": "2.0.3",
|
4
|
+
"description": "NestJS ReDoc (Swagger UI Alternative)",
|
5
|
+
"keywords": [
|
6
|
+
"nestjs",
|
7
|
+
"redoc",
|
8
|
+
"swagger",
|
9
|
+
"swaggerui",
|
10
|
+
"openapi"
|
11
|
+
],
|
12
|
+
"repository": {
|
13
|
+
"type": "git",
|
14
|
+
"url": "https://github.com/andrewronscki/nestjs-redoc.git"
|
15
|
+
},
|
16
|
+
"author": "André Wronscki",
|
17
|
+
"main": "dist/index.js",
|
18
|
+
"private": false,
|
19
|
+
"license": "MIT",
|
20
|
+
"scripts": {
|
21
|
+
"prebuild": "rimraf ./dist",
|
22
|
+
"build": "tsc -p ./tsconfig.build.json",
|
23
|
+
"format": "prettier --write \"./src/**/*.ts\"",
|
24
|
+
"format:check": "prettier --check \"./src/**/*.ts\"",
|
25
|
+
"lint": "eslint \"./src/**/*.ts\" --fix",
|
26
|
+
"test": "jest",
|
27
|
+
"test:watch": "jest --watch",
|
28
|
+
"sample": "ts-node ./sample/main"
|
29
|
+
},
|
30
|
+
"peerDependencies": {
|
31
|
+
"@nestjs/common": "^11.0.11",
|
32
|
+
"@nestjs/core": "^11.0.11",
|
33
|
+
"@nestjs/swagger": "^11.0.6",
|
34
|
+
"reflect-metadata": "^0.2.2"
|
35
|
+
},
|
36
|
+
"dependencies": {
|
37
|
+
"joi": "^17.13.3",
|
38
|
+
"@nestjs/common": "^11.0.11",
|
39
|
+
"@nestjs/swagger": "^11.0.6",
|
40
|
+
"express-basic-auth": "^1.2.1",
|
41
|
+
"express-handlebars": "^8.0.1"
|
42
|
+
},
|
43
|
+
"devDependencies": {
|
44
|
+
"@nestjs/core": "^11.0.11",
|
45
|
+
"@nestjs/platform-express": "^11.0.11",
|
46
|
+
"@nestjs/platform-fastify": "^11.0.11",
|
47
|
+
"@nestjs/testing": "^11.0.11",
|
48
|
+
"@types/express": "^5.0.0",
|
49
|
+
"@types/express-handlebars": "^5.3.1",
|
50
|
+
"@types/hapi__joi": "^17.1.15",
|
51
|
+
"@types/jest": "^29.5.14",
|
52
|
+
"@types/node": "^22.13.9",
|
53
|
+
"@types/supertest": "^6.0.2",
|
54
|
+
"@typescript-eslint/eslint-plugin": "^8.26.0",
|
55
|
+
"@typescript-eslint/parser": "^8.26.0",
|
56
|
+
"commitizen": "^4.3.1",
|
57
|
+
"eslint": "^9.21.0",
|
58
|
+
"eslint-config-prettier": "^10.0.2",
|
59
|
+
"eslint-plugin-prettier": "^5.2.3",
|
60
|
+
"jest": "^29.7.0",
|
61
|
+
"prettier": "^3.5.3",
|
62
|
+
"reflect-metadata": "^0.2.2",
|
63
|
+
"rimraf": "^6.0.1",
|
64
|
+
"rxjs": "^7.8.2",
|
65
|
+
"supertest": "^7.0.0",
|
66
|
+
"ts-jest": "^29.2.6",
|
67
|
+
"ts-node": "^10.9.2",
|
68
|
+
"typescript": "^5.8.2"
|
69
|
+
}
|
70
|
+
}
|
package/redoc.png
ADDED
Binary file
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
|
2
|
+
import {
|
3
|
+
ApiBearerAuth,
|
4
|
+
ApiConsumes,
|
5
|
+
ApiExtension,
|
6
|
+
ApiHeader,
|
7
|
+
ApiOperation,
|
8
|
+
ApiResponse,
|
9
|
+
ApiSecurity,
|
10
|
+
ApiTags,
|
11
|
+
} from '@nestjs/swagger';
|
12
|
+
import { CatsService } from './cats.service';
|
13
|
+
import { Cat } from './classes/cat.class';
|
14
|
+
import { CreateCatDto } from './dto/create-cat.dto';
|
15
|
+
import { PaginationQuery } from './dto/pagination-query.dto';
|
16
|
+
|
17
|
+
@ApiSecurity('basic')
|
18
|
+
@ApiBearerAuth()
|
19
|
+
@ApiTags('cats')
|
20
|
+
@ApiHeader({
|
21
|
+
name: 'header',
|
22
|
+
required: false,
|
23
|
+
description: 'Test',
|
24
|
+
schema: { default: 'test' },
|
25
|
+
})
|
26
|
+
@Controller('cats')
|
27
|
+
export class CatsController {
|
28
|
+
constructor(private readonly catsService: CatsService) {}
|
29
|
+
|
30
|
+
@ApiTags('create cats')
|
31
|
+
@Post()
|
32
|
+
@ApiOperation({ summary: 'Create cat' })
|
33
|
+
@ApiResponse({
|
34
|
+
status: 201,
|
35
|
+
description: 'The record has been successfully created.',
|
36
|
+
type: () => Cat,
|
37
|
+
})
|
38
|
+
@ApiResponse({ status: 403, description: 'Forbidden.' })
|
39
|
+
@ApiExtension('x-foo', { test: 'bar ' })
|
40
|
+
async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
|
41
|
+
return this.catsService.create(createCatDto);
|
42
|
+
}
|
43
|
+
|
44
|
+
@Get(':id')
|
45
|
+
@ApiResponse({
|
46
|
+
status: 200,
|
47
|
+
description: 'The found record',
|
48
|
+
type: Cat,
|
49
|
+
})
|
50
|
+
@ApiExtension('x-auth-type', 'NONE')
|
51
|
+
findOne(@Param('id') id: string): Cat {
|
52
|
+
return this.catsService.findOne(+id);
|
53
|
+
}
|
54
|
+
|
55
|
+
@Get()
|
56
|
+
findAll(@Query() paginationQuery: PaginationQuery) {}
|
57
|
+
|
58
|
+
@Get('bulk')
|
59
|
+
findAllBulk(@Query() paginationQuery: PaginationQuery[]) {}
|
60
|
+
|
61
|
+
@Post('bulk')
|
62
|
+
async createBulk(@Body() createCatDto: CreateCatDto[]): Promise<Cat> {
|
63
|
+
return null;
|
64
|
+
}
|
65
|
+
|
66
|
+
@ApiConsumes('application/x-www-form-urlencoded')
|
67
|
+
@Post('as-form-data')
|
68
|
+
@ApiOperation({ summary: 'Create cat' })
|
69
|
+
@ApiResponse({
|
70
|
+
status: 201,
|
71
|
+
description: 'The record has been successfully created.',
|
72
|
+
type: Cat,
|
73
|
+
})
|
74
|
+
@ApiResponse({ status: 403, description: 'Forbidden.' })
|
75
|
+
async createAsFormData(@Body() createCatDto: CreateCatDto): Promise<Cat> {
|
76
|
+
return this.catsService.create(createCatDto);
|
77
|
+
}
|
78
|
+
|
79
|
+
@Get('site*')
|
80
|
+
getSite() {}
|
81
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
2
|
+
import { Cat } from './classes/cat.class';
|
3
|
+
import { CreateCatDto } from './dto/create-cat.dto';
|
4
|
+
|
5
|
+
@Injectable()
|
6
|
+
export class CatsService {
|
7
|
+
private readonly cats: Cat[] = [];
|
8
|
+
|
9
|
+
create(cat: CreateCatDto): Cat {
|
10
|
+
this.cats.push(cat);
|
11
|
+
return cat;
|
12
|
+
}
|
13
|
+
|
14
|
+
findOne(id: number): Cat {
|
15
|
+
return this.cats[id];
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
2
|
+
import { LettersEnum } from '../dto/pagination-query.dto';
|
3
|
+
|
4
|
+
export class Cat {
|
5
|
+
@ApiProperty({ example: 'Kitty', description: 'The name of the Cat' })
|
6
|
+
name: string;
|
7
|
+
|
8
|
+
@ApiProperty({ example: 1, minimum: 0, description: 'The age of the Cat' })
|
9
|
+
age: number;
|
10
|
+
|
11
|
+
@ApiProperty({
|
12
|
+
example: 'Maine Coon',
|
13
|
+
description: 'The breed of the Cat',
|
14
|
+
})
|
15
|
+
breed: string;
|
16
|
+
|
17
|
+
@ApiProperty({
|
18
|
+
name: '_tags',
|
19
|
+
type: [String],
|
20
|
+
})
|
21
|
+
tags?: string[];
|
22
|
+
|
23
|
+
@ApiProperty()
|
24
|
+
createdAt: Date;
|
25
|
+
|
26
|
+
@ApiProperty({
|
27
|
+
type: String,
|
28
|
+
isArray: true,
|
29
|
+
})
|
30
|
+
urls?: string[];
|
31
|
+
|
32
|
+
@ApiProperty({
|
33
|
+
name: '_options',
|
34
|
+
type: 'array',
|
35
|
+
items: {
|
36
|
+
type: 'object',
|
37
|
+
properties: {
|
38
|
+
isReadonly: {
|
39
|
+
type: 'string',
|
40
|
+
},
|
41
|
+
},
|
42
|
+
},
|
43
|
+
})
|
44
|
+
options?: Record<string, any>[];
|
45
|
+
|
46
|
+
@ApiProperty({
|
47
|
+
enum: LettersEnum,
|
48
|
+
})
|
49
|
+
enum: LettersEnum;
|
50
|
+
|
51
|
+
@ApiProperty({
|
52
|
+
enum: LettersEnum,
|
53
|
+
isArray: true,
|
54
|
+
})
|
55
|
+
enumArr: LettersEnum;
|
56
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
|
2
|
+
import { ExtraModel } from './extra-model.dto';
|
3
|
+
import { LettersEnum } from './pagination-query.dto';
|
4
|
+
import { TagDto } from './tag.dto';
|
5
|
+
|
6
|
+
@ApiExtraModels(ExtraModel)
|
7
|
+
export class CreateCatDto {
|
8
|
+
@ApiProperty()
|
9
|
+
readonly name: string;
|
10
|
+
|
11
|
+
@ApiProperty({ minimum: 1, maximum: 200 })
|
12
|
+
readonly age: number;
|
13
|
+
|
14
|
+
@ApiProperty({ name: '_breed', type: String })
|
15
|
+
readonly breed: string;
|
16
|
+
|
17
|
+
@ApiProperty({
|
18
|
+
type: [String],
|
19
|
+
})
|
20
|
+
readonly tags?: string[];
|
21
|
+
|
22
|
+
@ApiProperty()
|
23
|
+
createdAt: Date;
|
24
|
+
|
25
|
+
@ApiProperty({
|
26
|
+
type: 'string',
|
27
|
+
isArray: true,
|
28
|
+
})
|
29
|
+
readonly urls?: string[];
|
30
|
+
|
31
|
+
@ApiProperty({
|
32
|
+
type: 'array',
|
33
|
+
items: {
|
34
|
+
type: 'object',
|
35
|
+
properties: {
|
36
|
+
isReadonly: {
|
37
|
+
type: 'string',
|
38
|
+
},
|
39
|
+
},
|
40
|
+
},
|
41
|
+
})
|
42
|
+
readonly options?: Record<string, any>[];
|
43
|
+
|
44
|
+
@ApiProperty({
|
45
|
+
enum: LettersEnum,
|
46
|
+
enumName: 'LettersEnum',
|
47
|
+
})
|
48
|
+
readonly enum: LettersEnum;
|
49
|
+
|
50
|
+
@ApiProperty({
|
51
|
+
enum: LettersEnum,
|
52
|
+
enumName: 'LettersEnum',
|
53
|
+
isArray: true,
|
54
|
+
})
|
55
|
+
readonly enumArr: LettersEnum;
|
56
|
+
|
57
|
+
@ApiProperty({ description: 'tag', required: false })
|
58
|
+
readonly tag: TagDto;
|
59
|
+
|
60
|
+
nested: {
|
61
|
+
first: string;
|
62
|
+
second: number;
|
63
|
+
};
|
64
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
2
|
+
|
3
|
+
export enum LettersEnum {
|
4
|
+
A = 'A',
|
5
|
+
B = 'B',
|
6
|
+
C = 'C',
|
7
|
+
}
|
8
|
+
|
9
|
+
export class PaginationQuery {
|
10
|
+
@ApiProperty({
|
11
|
+
minimum: 0,
|
12
|
+
maximum: 10000,
|
13
|
+
title: 'Page',
|
14
|
+
exclusiveMaximum: true,
|
15
|
+
exclusiveMinimum: true,
|
16
|
+
format: 'int32',
|
17
|
+
default: 0,
|
18
|
+
})
|
19
|
+
page: number;
|
20
|
+
|
21
|
+
@ApiProperty({
|
22
|
+
name: '_sortBy',
|
23
|
+
type: [String],
|
24
|
+
})
|
25
|
+
sortBy: string[];
|
26
|
+
|
27
|
+
@ApiProperty()
|
28
|
+
limit: number;
|
29
|
+
|
30
|
+
@ApiProperty({
|
31
|
+
enum: LettersEnum,
|
32
|
+
enumName: 'LettersEnum',
|
33
|
+
})
|
34
|
+
enum: LettersEnum;
|
35
|
+
|
36
|
+
@ApiProperty({
|
37
|
+
enum: LettersEnum,
|
38
|
+
enumName: 'LettersEnum',
|
39
|
+
isArray: true,
|
40
|
+
})
|
41
|
+
enumArr: LettersEnum;
|
42
|
+
}
|
package/sample/main.ts
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
import { NestFactory } from '@nestjs/core';
|
2
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
3
|
+
import { RedocModule, RedocOptions } from '../src';
|
4
|
+
import { AppModule } from './app.module';
|
5
|
+
|
6
|
+
async function bootstrap() {
|
7
|
+
const app = await NestFactory.create(AppModule);
|
8
|
+
const options = new DocumentBuilder()
|
9
|
+
.setTitle('Cats example')
|
10
|
+
.setDescription('The cats API description')
|
11
|
+
.setVersion('1.0')
|
12
|
+
.addTag('cats')
|
13
|
+
.addBasicAuth()
|
14
|
+
.addBearerAuth()
|
15
|
+
.addOAuth2()
|
16
|
+
.addApiKey()
|
17
|
+
.addCookieAuth()
|
18
|
+
.addSecurityRequirements('bearer')
|
19
|
+
.build();
|
20
|
+
const document = SwaggerModule.createDocument(app, options);
|
21
|
+
const redocOptions: RedocOptions = {
|
22
|
+
title: 'Redoc Module',
|
23
|
+
logo: {
|
24
|
+
url: 'https://redocly.github.io/redoc/petstore-logo.png',
|
25
|
+
backgroundColor: '#F0F0F0',
|
26
|
+
altText: 'PetStore Logo',
|
27
|
+
},
|
28
|
+
sortPropsAlphabetically: true,
|
29
|
+
hideDownloadButton: false,
|
30
|
+
hideHostname: false,
|
31
|
+
noAutoAuth: true,
|
32
|
+
pathInMiddlePanel: true,
|
33
|
+
auth: {
|
34
|
+
enabled: true,
|
35
|
+
user: 'admin',
|
36
|
+
password: '123',
|
37
|
+
},
|
38
|
+
tagGroups: [
|
39
|
+
{
|
40
|
+
name: 'Core resources',
|
41
|
+
tags: ['cats'],
|
42
|
+
},
|
43
|
+
],
|
44
|
+
};
|
45
|
+
await RedocModule.setup('docs', app, document, redocOptions);
|
46
|
+
await app.listen(8000);
|
47
|
+
}
|
48
|
+
bootstrap();
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
|
4
|
+
<head>
|
5
|
+
<title>{{ data.title }}</title>
|
6
|
+
<!-- needed for adaptive design -->
|
7
|
+
<meta charset="utf-8" />
|
8
|
+
{{#if data.favicon}}
|
9
|
+
<link rel="shortcut icon" type="image/x-icon" href="{{ data.favicon }}" />
|
10
|
+
{{/if}}
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
12
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
13
|
+
<!--
|
14
|
+
ReDoc doesn't change outer page styles
|
15
|
+
-->
|
16
|
+
<style>
|
17
|
+
body {
|
18
|
+
margin: 0;
|
19
|
+
padding: 0;
|
20
|
+
}
|
21
|
+
</style>
|
22
|
+
</head>
|
23
|
+
|
24
|
+
<body>
|
25
|
+
<!-- we provide is specification here -->
|
26
|
+
<div id="redoc_container"></div>
|
27
|
+
<script src="https://cdn.jsdelivr.net/npm/redoc@{{data.redocVersion}}/bundles/redoc.standalone.js"> </script>
|
28
|
+
<script>
|
29
|
+
let themeJSON = '{{{ toJSON data.theme }}}';
|
30
|
+
if (themeJSON === '') { themeJSON = undefined }
|
31
|
+
Redoc.init(
|
32
|
+
'{{ data.docUrl }}',
|
33
|
+
{
|
34
|
+
...(themeJSON && {
|
35
|
+
theme: {
|
36
|
+
...JSON.parse(themeJSON)
|
37
|
+
}
|
38
|
+
}),
|
39
|
+
...JSON.parse('{{{ toJSON data.options }}}')
|
40
|
+
},
|
41
|
+
document.getElementById("redoc_container")
|
42
|
+
);
|
43
|
+
</script>
|
44
|
+
</body>
|
45
|
+
|
46
|
+
</html>
|