Skip to content

Commit 3e35e91

Browse files
committed
Basic working example compliant with openapi3
1 parent 3a23fb3 commit 3e35e91

File tree

2 files changed

+269
-25
lines changed

2 files changed

+269
-25
lines changed

swagger/swagger-ui/swagger-ui.html

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
<html>
22
<head>
33
<title>Swagger UI</title>
4+
<!--
45
<link href='reqs/css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
56
<link href='reqs/css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
67
<link href='reqs/css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
78
<link href='reqs/css/reset.css' media='print' rel='stylesheet' type='text/css'/>
89
<link href='reqs/css/screen.css' media='print' rel='stylesheet' type='text/css'/>
910
<link href='reqs/css/print.css' media='print' rel='stylesheet' type='text/css'/>
10-
<link rel="stylesheet" href="../vendor/font-awesome/css/font-awesome.min.css">
11+
1112
<script src='reqs/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
1213
<script src='reqs/lib/jquery.slideto.min.js' type='text/javascript'></script>
1314
<script src='reqs/lib/jquery.wiggle.min.js' type='text/javascript'></script>
1415
<script src='reqs/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
1516
<script src='reqs/lib/handlebars-2.0.0.js' type='text/javascript'></script>
1617
<script src='reqs/lib/underscore-min.js' type='text/javascript'></script>
1718
<script src='reqs/lib/backbone-min.js' type='text/javascript'></script>
18-
<script src='reqs/swagger-ui.js' type='text/javascript'></script>
19+
1920
<script src='reqs/lib/highlight.7.3.pack.js' type='text/javascript'></script>
2021
<script src='reqs/lib/jsoneditor.min.js' type='text/javascript'></script>
2122
<script src='reqs/lib/marked.js' type='text/javascript'></script>
22-
<script src='reqs/i18next.min.js' type='text/javascript'></script>
23+
24+
-->
25+
<link rel="stylesheet" href="../vendor/font-awesome/css/font-awesome.min.css">
26+
27+
28+
<script src='i18next.min.js' type='text/javascript'></script>
29+
30+
<script src='swagger-ui.js' type='text/javascript'></script>
31+
32+
2333
<script type="text/javascript">
2434
var url = parent.swaggerDocUrl;
2535
window.swaggerUi = new SwaggerUi({

swagger/swagger.js

Lines changed: 256 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,35 @@
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
15-
**/
15+
**/
16+
17+
18+
//const swaggerUiDistPath = require('swagger-ui-dist').getAbsoluteFSPath();
19+
1620

1721
const DEFAULT_TEMPLATE = {
18-
swagger: "2.0",
22+
openapi: "3.0.0",
1923
info: {
2024
title: "My Node-RED API",
21-
version: "0.0.1"
22-
}
25+
version: "1.0.0",
26+
description: "A sample API",
27+
// You can also add 'termsOfService', 'contact', and 'license' information here
28+
},
29+
servers: [
30+
{
31+
url: 'http://localhost:1880/',
32+
description: 'Local server'
33+
},
34+
],
35+
paths: {},
36+
components: {
37+
schemas: {},
38+
responses: {},
39+
parameters: {},
40+
securitySchemes: {}
41+
},
42+
tags: [],
43+
// Add more properties as needed
2344
};
2445

2546
module.exports = function(RED) {
@@ -35,6 +56,7 @@ module.exports = function(RED) {
3556
url.length > 1 && url.endsWith("/") ? url.slice(0, -1) : url;
3657
const regexColons = /\/:\w*/g;
3758

59+
/*
3860
RED.httpNode.get("/http-api/swagger.json", (req, res) => {
3961
const {
4062
httpNodeRoot,
@@ -86,6 +108,159 @@ module.exports = function(RED) {
86108
res.json(resp);
87109
});
88110
111+
RED.httpNode.get("/http-api/swagger.json", (req, res) => {
112+
// Use the default template as the base for the response
113+
const openApiSpec = { ...DEFAULT_TEMPLATE };
114+
115+
// Iterate over each node that could have associated API documentation
116+
RED.nodes.eachNode(node => {
117+
if (node.type === "http in") {
118+
// Get the Swagger documentation node associated with this http in node
119+
const swaggerDocNode = RED.nodes.getNode(node.swaggerDoc);
120+
121+
if (swaggerDocNode) {
122+
// Construct the path for the endpoint
123+
const path = ensureLeadingSlash(node.url.replace(regexColons, convToSwaggerPath));
124+
125+
// Initialize the path object if not already initialized
126+
if (!openApiSpec.paths[path]) {
127+
openApiSpec.paths[path] = {};
128+
}
129+
130+
// Define the operation object for this method and path
131+
const operation = {
132+
summary: swaggerDocNode.summary || `${node.method.toUpperCase()} ${path}`,
133+
description: swaggerDocNode.description || '',
134+
tags: csvStrToArray(swaggerDocNode.tags),
135+
parameters: [], // This will need to be populated based on your parameter definitions
136+
responses: {} // This will need to be populated based on your response definitions
137+
};
138+
139+
// Populate parameters array
140+
// You'll need to construct your parameter objects according to OpenAPI 3.0
141+
// For example:
142+
swaggerDocNode.parameters.forEach(param => {
143+
operation.parameters.push({
144+
name: param.name,
145+
in: param.in, // 'query', 'header', 'path' or 'cookie'
146+
description: param.description,
147+
required: param.required,
148+
schema: {
149+
type: param.type
150+
// More schema properties can be set here
151+
}
152+
});
153+
});
154+
155+
// Populate responses object
156+
// You'll need to map your response objects to status codes
157+
// For example:
158+
console.log(swaggerDocNode); // Should log true if it's an array
159+
160+
swaggerDocNode.responses.forEach(resp => {
161+
operation.responses[resp.code] = {
162+
description: resp.description,
163+
content: {
164+
'application/json': {
165+
schema: {
166+
// Define the schema for the response
167+
}
168+
}
169+
}
170+
};
171+
});
172+
173+
// Add the operation to the path in the spec
174+
openApiSpec.paths[path][node.method.toLowerCase()] = operation;
175+
}
176+
}
177+
});
178+
179+
// Send the OpenAPI Specification as JSON
180+
res.json(openApiSpec);
181+
});
182+
*/
183+
184+
RED.httpNode.get("/http-api/swagger.json", (req, res) => {
185+
const {
186+
httpNodeRoot,
187+
swagger: { parameters: additionalParams = [], template: resp = { ...DEFAULT_TEMPLATE } } = {}
188+
} = RED.settings;
189+
const { basePath = httpNodeRoot } = resp;
190+
191+
resp.paths = {};
192+
193+
RED.nodes.eachNode(node => {
194+
const { name, type, method, swaggerDoc, url } = node;
195+
196+
if (type === "http in") {
197+
const swaggerDocNode = RED.nodes.getNode(swaggerDoc);
198+
199+
if (swaggerDocNode) {
200+
const endPoint = ensureLeadingSlash(url.replace(regexColons, convToSwaggerPath));
201+
if (!resp.paths[endPoint]) resp.paths[endPoint] = {};
202+
203+
const {
204+
summary = swaggerDocNode.summary || name || method + " " + endPoint,
205+
description = swaggerDocNode.description || '',
206+
tags = swaggerDocNode.tags || '',
207+
deprecated = swaggerDocNode.deprecated || false,
208+
parameters = swaggerDocNode.parameters || []
209+
} = swaggerDocNode;
210+
211+
const aryTags = csvStrToArray(tags);
212+
213+
const operation = {
214+
summary,
215+
description,
216+
tags: aryTags,
217+
deprecated,
218+
parameters: [...parameters, ...additionalParams].map(param => {
219+
return {
220+
name: param.name,
221+
in: param.in,
222+
required: param.required,
223+
schema: {
224+
type: param.type,
225+
// Add other schema properties here
226+
},
227+
description: param.description
228+
};
229+
}),
230+
responses: {}
231+
};
232+
233+
// Check if responses is an object and not null or undefined
234+
if (swaggerDocNode && typeof swaggerDocNode.responses === 'object' && swaggerDocNode.responses !== null) {
235+
Object.keys(swaggerDocNode.responses).forEach(status => {
236+
const responseDetails = swaggerDocNode.responses[status];
237+
operation.responses[status] = {
238+
description: responseDetails.description || '',
239+
content: {
240+
'application/json': {
241+
schema: {
242+
// Define or reference your schema here
243+
}
244+
}
245+
}
246+
};
247+
});
248+
} else {
249+
console.error('swaggerDocNode.responses is not an object or is null:', swaggerDocNode.responses);
250+
}
251+
252+
// Add the operation to the path in the spec
253+
resp.paths[endPoint][method.toLowerCase()] = operation;
254+
} else {
255+
console.error('No Swagger Documentation node found for HTTP In node:', node.id);
256+
}
257+
}
258+
});
259+
260+
res.json(resp);
261+
});
262+
263+
89264
function SwaggerDoc(n) {
90265
RED.nodes.createNode(this, n);
91266
this.summary = n.summary;
@@ -98,7 +273,7 @@ module.exports = function(RED) {
98273
this.deprecated = n.deprecated;
99274
}
100275
RED.nodes.registerType("swagger-doc", SwaggerDoc);
101-
276+
/*
102277
function sendFile(res, filename) {
103278
// Use the right function depending on Express 3.x/4.x
104279
if (res.sendFile) {
@@ -107,23 +282,82 @@ module.exports = function(RED) {
107282
res.sendfile(filename);
108283
}
109284
}
110-
111-
RED.httpAdmin.get("/swagger-ui/reqs/i18next.min.js", (req, res) => {
112-
const basePath = require.resolve("i18next-client").replace(/[\\/]i18next.js$/, "");
113-
const filename = path.join(basePath, "i18next.min.js");
114-
sendFile(res, filename);
115-
});
116-
RED.httpAdmin.get("/swagger-ui/reqs/*", (req, res) => {
117-
const basePath = require.resolve("swagger-ui").replace(/[\\/]swagger-ui.js$/, "");
118-
const filename = path.join(basePath, req.params[0]);
119-
sendFile(res, filename);
120-
});
121-
RED.httpAdmin.get("/swagger-ui/nls/*", (req, res) => {
122-
const filename = path.join(__dirname, "locales", req.params[0]);
123-
sendFile(res, filename);
285+
/*
286+
RED.httpAdmin.get('/swagger-ui/*', (req, res) => {
287+
const filename = req.params[0];
288+
const filePath = path.join(swaggerUiDistPath, filename);
289+
sendFile(res, filePath);
124290
});
125-
RED.httpAdmin.get("/swagger-ui/*", (req, res) => {
126-
const filename = path.join(__dirname, "swagger-ui", req.params[0]);
127-
sendFile(res, filename);
291+
292+
function sendFile(res, filePath) {
293+
res.sendFile(filePath, (err) => {
294+
if (err) {
295+
console.error('Error sending file:', err);
296+
res.status(500).send('Error sending file.');
297+
}
298+
});
299+
}
300+
*/
301+
302+
// Serve the main Swagger UI HTML file
303+
RED.httpAdmin.get("/swagger-ui/swagger-ui.html", (req, res) => {
304+
// Correct the path to point directly to the 'swagger-ui.html' file
305+
const filename = path.join(__dirname, "swagger-ui/swagger-ui.html");
306+
sendFile(res, filename);
307+
});
308+
309+
310+
// Serve i18next localization files
311+
RED.httpAdmin.get("/swagger-ui/i18next.min.js", (req, res) => {
312+
const filename = path.join(__dirname, '..', 'node_modules', 'i18next', 'i18next.min.js');
313+
sendFile(res, filename);
314+
});
315+
316+
317+
// Serve Swagger UI assets like CSS and JS from swagger-ui-dist
318+
RED.httpAdmin.get("/swagger-ui/*", (req, res, next) => {
319+
// Extract the actual file name from the request params
320+
let filename = req.params[0];
321+
322+
// If the filename is 'swagger-ui.html', redirect to the correct handler
323+
if (filename === 'swagger-ui.html') {
324+
return next();
325+
}
326+
327+
// Serve the file from swagger-ui-dist
328+
try {
329+
const basePath = require('swagger-ui-dist').getAbsoluteFSPath();
330+
const filePath = path.join(basePath, filename);
331+
sendFile(res, filePath);
332+
} catch (err) {
333+
console.error(err);
334+
res.status(404).send("File not found");
335+
}
336+
}, (req, res) => {
337+
// Fallback handler for 'swagger-ui.html', in case the above handler is triggered
338+
// due to the way Express handles wildcard routes
339+
const filename = path.join(__dirname, "swagger", "swagger-ui.html");
340+
sendFile(res, filename);
341+
});
342+
343+
// Serve any other localization files
344+
RED.httpAdmin.get("/swagger-ui/nls/*", (req, res) => {
345+
const filename = path.join(__dirname, "locales", req.params[0]);
346+
sendFile(res, filename);
347+
});
348+
349+
// Generic function to send files
350+
function sendFile(res, filePath) {
351+
// Implement the logic to send the file
352+
// For example, using Express' res.sendFile:
353+
res.sendFile(filePath, (err) => {
354+
if (err) {
355+
console.error('Error sending file:', err);
356+
res.status(err.status || 500).send('Error sending file.');
357+
}
128358
});
359+
}
360+
361+
362+
129363
};

0 commit comments

Comments
 (0)