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
1721const 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
2546module . 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 ( / [ \\ / ] i 1 8 n e x t .j s $ / , "" ) ;
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 ( / [ \\ / ] s w a g g e r - u i .j s $ / , "" ) ;
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