@5minds/node-red-contrib-processcube-tools 1.1.0-feature-975857-mg4vqpai → 1.2.0-develop-2eb127-mg68t7xt
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/swagger/swagger.js
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright 2015, 2016 IBM Corp.
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
**/
|
|
16
|
-
|
|
17
|
-
const DEFAULT_TEMPLATE = {
|
|
18
|
-
openapi: '3.0.0',
|
|
19
|
-
info: {
|
|
20
|
-
title: 'My Node-RED API',
|
|
21
|
-
version: '1.0.0',
|
|
22
|
-
description: 'A sample API',
|
|
23
|
-
},
|
|
24
|
-
servers: [
|
|
25
|
-
{
|
|
26
|
-
url: 'http://localhost:1880/',
|
|
27
|
-
description: 'Local server',
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
paths: {},
|
|
31
|
-
components: {
|
|
32
|
-
schemas: {},
|
|
33
|
-
responses: {},
|
|
34
|
-
parameters: {},
|
|
35
|
-
securitySchemes: {},
|
|
36
|
-
},
|
|
37
|
-
tags: [],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
module.exports = function (RED) {
|
|
41
|
-
'use strict';
|
|
42
|
-
|
|
43
|
-
const path = require('path');
|
|
44
|
-
|
|
45
|
-
const convToSwaggerPath = (x) => `/{${x.substring(2)}}`;
|
|
46
|
-
const trimAll = (ary) => ary.map((x) => x.trim());
|
|
47
|
-
const csvStrToArray = (csvStr) => (csvStr ? trimAll(csvStr.split(',')) : []);
|
|
48
|
-
const ensureLeadingSlash = (url) => (url.startsWith('/') ? url : '/' + url);
|
|
49
|
-
const stripTerminalSlash = (url) => (url.length > 1 && url.endsWith('/') ? url.slice(0, -1) : url);
|
|
50
|
-
const regexColons = /\/:\w*/g;
|
|
51
|
-
|
|
52
|
-
// Helper function to convert collection format to OpenAPI 3.0 style
|
|
53
|
-
const getStyleFromCollectionFormat = (collectionFormat) => {
|
|
54
|
-
const formatMap = {
|
|
55
|
-
csv: 'simple',
|
|
56
|
-
ssv: 'spaceDelimited',
|
|
57
|
-
tsv: 'pipeDelimited',
|
|
58
|
-
pipes: 'pipeDelimited',
|
|
59
|
-
multi: 'form',
|
|
60
|
-
};
|
|
61
|
-
return formatMap[collectionFormat] || 'simple';
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
RED.httpNode.get('/http-api/swagger.json', (req, res) => {
|
|
65
|
-
try {
|
|
66
|
-
const { httpNodeRoot, openapi: { template = {}, parameters: additionalParams = [] } = {} } = RED.settings;
|
|
67
|
-
|
|
68
|
-
const resp = { ...DEFAULT_TEMPLATE, ...template };
|
|
69
|
-
resp.paths = {};
|
|
70
|
-
|
|
71
|
-
// Update server URL to include the httpNodeRoot
|
|
72
|
-
if (httpNodeRoot && httpNodeRoot !== '/') {
|
|
73
|
-
resp.servers = resp.servers.map((server) => ({
|
|
74
|
-
...server,
|
|
75
|
-
url: server.url.replace(/\/$/, '') + httpNodeRoot,
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
RED.nodes.eachNode((node) => {
|
|
80
|
-
const { name, type, method, swaggerDoc, url } = node;
|
|
81
|
-
|
|
82
|
-
if (type === 'http in' && swaggerDoc) {
|
|
83
|
-
const swaggerDocNode = RED.nodes.getNode(swaggerDoc);
|
|
84
|
-
|
|
85
|
-
if (swaggerDocNode) {
|
|
86
|
-
// Convert Node-RED path parameters to OpenAPI format
|
|
87
|
-
const endPoint = stripTerminalSlash(
|
|
88
|
-
ensureLeadingSlash(url.replace(regexColons, convToSwaggerPath)),
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
if (!resp.paths[endPoint]) resp.paths[endPoint] = {};
|
|
92
|
-
|
|
93
|
-
const {
|
|
94
|
-
summary = name || `${method.toUpperCase()} ${endPoint}`,
|
|
95
|
-
description = '',
|
|
96
|
-
tags = '',
|
|
97
|
-
deprecated = false,
|
|
98
|
-
parameters = [],
|
|
99
|
-
requestBody = null,
|
|
100
|
-
responses = {},
|
|
101
|
-
} = swaggerDocNode;
|
|
102
|
-
|
|
103
|
-
const aryTags = csvStrToArray(tags);
|
|
104
|
-
|
|
105
|
-
const operation = {
|
|
106
|
-
summary,
|
|
107
|
-
description,
|
|
108
|
-
tags: aryTags,
|
|
109
|
-
deprecated,
|
|
110
|
-
parameters: [...parameters, ...additionalParams].map((param) => {
|
|
111
|
-
const paramDef = {
|
|
112
|
-
name: param.name,
|
|
113
|
-
in: param.in,
|
|
114
|
-
required: param.required || false,
|
|
115
|
-
description: param.description || '',
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// Handle parameter schema - preserve the original schema structure
|
|
119
|
-
if (param.schema) {
|
|
120
|
-
// If there's already a schema object, use it
|
|
121
|
-
paramDef.schema = param.schema;
|
|
122
|
-
} else if (param.type) {
|
|
123
|
-
// Build schema from individual type properties
|
|
124
|
-
paramDef.schema = { type: param.type };
|
|
125
|
-
if (param.format) {
|
|
126
|
-
paramDef.schema.format = param.format;
|
|
127
|
-
}
|
|
128
|
-
if (param.type === 'array' && param.items) {
|
|
129
|
-
paramDef.schema.items = param.items;
|
|
130
|
-
}
|
|
131
|
-
if (param.collectionFormat && param.type === 'array') {
|
|
132
|
-
paramDef.style = getStyleFromCollectionFormat(param.collectionFormat);
|
|
133
|
-
paramDef.explode = param.collectionFormat === 'multi';
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return paramDef;
|
|
138
|
-
}),
|
|
139
|
-
responses: {},
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Add request body if it exists
|
|
143
|
-
if (requestBody && Object.keys(requestBody.content || {}).length > 0) {
|
|
144
|
-
const content = requestBody.content;
|
|
145
|
-
Object.keys(content).forEach(contentType => {
|
|
146
|
-
if (contentType.includes('xml') && content[contentType].example) {
|
|
147
|
-
content['text/plain'] = {
|
|
148
|
-
schema: { type: 'string' },
|
|
149
|
-
example: content[contentType].example.replace(/\\n/g, '\n')
|
|
150
|
-
};
|
|
151
|
-
delete content[contentType];
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
operation.requestBody = requestBody;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Process responses
|
|
158
|
-
if (responses && typeof responses === 'object') {
|
|
159
|
-
Object.keys(responses).forEach((status) => {
|
|
160
|
-
const responseDetails = responses[status];
|
|
161
|
-
operation.responses[status] = {
|
|
162
|
-
description: responseDetails.description || 'No description',
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// Add content if schema exists
|
|
166
|
-
if (responseDetails.schema) {
|
|
167
|
-
operation.responses[status].content = {
|
|
168
|
-
'application/json': {
|
|
169
|
-
schema: responseDetails.schema,
|
|
170
|
-
},
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Ensure at least one response exists
|
|
177
|
-
if (Object.keys(operation.responses).length === 0) {
|
|
178
|
-
operation.responses['200'] = {
|
|
179
|
-
description: 'Successful response',
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
resp.paths[endPoint][method.toLowerCase()] = operation;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Clean up empty sections
|
|
189
|
-
cleanupOpenAPISpec(resp);
|
|
190
|
-
res.json(resp);
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.error('Error generating Swagger JSON:', error);
|
|
193
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
function cleanupOpenAPISpec(spec) {
|
|
198
|
-
// Clean up components
|
|
199
|
-
if (spec.components) {
|
|
200
|
-
['schemas', 'responses', 'parameters', 'securitySchemes'].forEach((key) => {
|
|
201
|
-
if (spec.components[key] && Object.keys(spec.components[key]).length === 0) {
|
|
202
|
-
delete spec.components[key];
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// If all components are empty, remove the components object itself
|
|
207
|
-
if (Object.keys(spec.components).length === 0) {
|
|
208
|
-
delete spec.components;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Clean up empty tags array
|
|
213
|
-
if (Array.isArray(spec.tags) && spec.tags.length === 0) {
|
|
214
|
-
delete spec.tags;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function SwaggerDoc(n) {
|
|
219
|
-
RED.nodes.createNode(this, n);
|
|
220
|
-
this.summary = n.summary;
|
|
221
|
-
this.description = n.description;
|
|
222
|
-
this.tags = n.tags;
|
|
223
|
-
this.parameters = n.parameters || [];
|
|
224
|
-
this.responses = n.responses || {};
|
|
225
|
-
this.requestBody = n.requestBody || null;
|
|
226
|
-
this.deprecated = n.deprecated || false;
|
|
227
|
-
}
|
|
228
|
-
RED.nodes.registerType('swagger-doc', SwaggerDoc);
|
|
229
|
-
|
|
230
|
-
// Serve the main Swagger UI HTML file
|
|
231
|
-
RED.httpAdmin.get('/swagger-ui/swagger-ui.html', (req, res) => {
|
|
232
|
-
const filename = path.join(__dirname, 'swagger-ui', 'swagger-ui.html');
|
|
233
|
-
sendFile(res, filename);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Serve Swagger UI assets
|
|
237
|
-
RED.httpAdmin.get('/swagger-ui/*', (req, res, next) => {
|
|
238
|
-
let filename = req.params[0];
|
|
239
|
-
|
|
240
|
-
// Skip if it's the HTML file (handled by specific route above)
|
|
241
|
-
if (filename === 'swagger-ui.html') {
|
|
242
|
-
return next();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
const swaggerUiPath = require('swagger-ui-dist').getAbsoluteFSPath();
|
|
247
|
-
const filePath = path.join(swaggerUiPath, filename);
|
|
248
|
-
sendFile(res, filePath);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
console.error('Error serving Swagger UI asset:', err);
|
|
251
|
-
res.status(404).send('File not found');
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Serve localization files
|
|
256
|
-
RED.httpAdmin.get('/swagger-ui/nls/*', (req, res) => {
|
|
257
|
-
const filename = path.join(__dirname, 'locales', req.params[0]);
|
|
258
|
-
sendFile(res, filename);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// Generic function to send files
|
|
262
|
-
function sendFile(res, filePath) {
|
|
263
|
-
const fs = require('fs');
|
|
264
|
-
|
|
265
|
-
// Check if file exists
|
|
266
|
-
if (!fs.existsSync(filePath)) {
|
|
267
|
-
return res.status(404).send('File not found');
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
res.sendFile(path.resolve(filePath), (err) => {
|
|
271
|
-
if (err) {
|
|
272
|
-
console.error('Error sending file:', err);
|
|
273
|
-
res.status(err.status || 500).send('Error sending file');
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
};
|