1
0
Fork 0

feat: init v.0.0.1

main
Евгений Сугоняко 2023-12-06 13:47:03 +03:00
commit 76517ea608
6 changed files with 774 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
package-lock.json

7
app.js Normal file
View File

@ -0,0 +1,7 @@
const postmanToPdf = require("./utils/postman-to-pdf");
const inputFilePath = "./input/collection.json";
const apiRoute = "https://example.com/api"
const outputPath = "./output"
postmanToPdf(inputFilePath, outputPath, apiRoute);

14
package.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "postmantopdf",
"version": "1.0.0",
"description": "Converts Postman API collection v2.1 into a proper documentated PDF/html file",
"main": "app.js",
"scripts": {
"start": "node ./"
},
"author": "EvgeniySugonyako",
"license": "ISC",
"dependencies": {
"html-pdf": "3.0.1"
}
}

487
utils/html-generator.js Normal file
View File

@ -0,0 +1,487 @@
const fs = require('fs');
function parseExampleRes(data) {
let html = '';
let header_html = "";
if (data.response.headers.length > 0) {
data.response.headers.forEach(elem => {
if (elem.key === "Content-Type") {
header_html += "<p style='margin-left: 27px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
return;
}
});
}
if (data.response.bodyOptions && data.response.bodyOptions !== "") {
if (header_html === "") {
Object.keys(data.response.bodyOptions).forEach(function(typeName) {
if (typeName === "language") {
header_html += `
<p style='margin-left: 20px; word-wrap: break-word;'>
"Content-type" : "${data.response.bodyOptions[typeName]}"
</p>`;
return;
}
});
}
}
if (header_html !== "") {
header_html = `
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>Headers :&nbsp;</strong>
</p>
${header_html}
`;
html += header_html;
}
if (data.response.cookies.length > 0) {
html += `
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>Cookies :&nbsp;</strong>
</p>
`;
data.response.cookies.forEach(elem => {
html += "<p style='margin-left: 27px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
});
}
if (data.response.body !== "") {
let body = `
<p style='margin-left: 25px; word-wrap: break-word;'>
<b>Body :&nbsp;</b>
</p>`;
switch (typeof data.response.body) {
case 'string':
body += `<pre style='font-size: 16px; margin-left: 27px; word-wrap: break-word;'>${data.response.body}</pre>`;
break;
case 'object':
body = "";
data.response.body.forEach(item => {
body += `<pre style='font-size: 16px; margin-left: 27px; word-wrap: break-word;'>${JSON.stringify(item, null, 2)}</pre>`;
});
break;
default:
body = "";
break;
}
html += body;
}
return html;
}
function parseExampleReq(data) {
let html = '';
let header_html = "";
if (data.request.header.length > 0) {
data.request.header.forEach(elem => {
if (elem.key === "Content-Type") {
header_html += "<p style='margin-left: 27px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
return;
}
});
}
if (data.request.bodyOptions && data.request.bodyOptions !== "") {
if (header_html === "") {
Object.keys(data.request.bodyOptions).forEach(function(typeName) {
if (typeName === "language") {
header_html += `
<p style='margin-left: 27px; word-wrap: break-word;'>
"Content-type" : "${data.request.bodyOptions[typeName]}"
</p>`;
return;
}
});
}
}
if (header_html !== "") {
header_html = `
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>Headers :&nbsp;</strong>
</p>
${header_html}
`;
html += header_html;
}
if(data.request.auth.length > 0) {
html += "<p style='margin-left: 25px; word-wrap: break-word;'><strong>Auth :&nbsp;</strong></p>";
data.request.auth.forEach(elem => {
html += "<p style='margin-left: 27px; word-wrap: break-word;'>key : "+elem.key+", value : "+elem.value+", type : "+elem.type+"</p>";
});
}
if (data.request.variables.length > 0) {
html += `
<p style='margin-left: 25px; word-wrap: break-word;'>
<b>Path Variables:&nbsp;</b>
</p>
`;
data.request.variables.forEach(elem => {
html += "<p style='margin-left: 27px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
});
}
if (data.request.queryes.length > 0) {
html += `
<p style='margin-left: 25px; word-wrap: break-word;'>
<b>Query Parameters :&nbsp;</b>
</p>
`;
data.request.queryes.forEach(elem => {
html += "<p style='margin-left: 27px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
});
}
if (data.request.body !== "") {
let body = `
<p style='margin-left: 25px; word-wrap: break-word;'>
<b>Body :&nbsp;</b>
</p>`;
switch (typeof data.request.body) {
case 'string':
body += `<pre style='font-size: 16px; margin-left: 27px; word-wrap: break-word;'>${data.request.body}</pre>`;
break;
case 'object':
data.request.body.forEach(item => {
body += `<pre style='font-size: 16px; margin-left: 27px; word-wrap: break-word;'>${JSON.stringify(item, null, 2)}</pre>`;
});
break;
default:
body = "";
break;
}
html += body;
}
return html;
}
function generateExamples(data) {
let html = `
<p style='margin-left: 20px;'>
<b><u>Success : </u></b>
</p>
`;
if (data.success.length > 0) {
data.success.forEach(function(item) {
let desc = "";
if (item.desc && item.desc !== "") {
desc = `
<p style='margin-left: 22px;'>
<strong>Description :&nbsp;</strong>
<span style='white-space: pre;'>${item.desc.replace(/ /g, "&nbsp;")}</span>
</p>
`;
}
html += `
<p style='margin-left: 22px;'>
<b>Request : </b>${item.name}
</p>
${desc}
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>URL :&nbsp;</strong>
${item.request.url}
</p>
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>Method :&nbsp;</strong>
${item.request.method}
</p>
${parseExampleReq(item)}
<p style='margin-left: 22px;'>
<b>Response : </b>
</p>
<p style='margin-left: 25px;'>
<b>Status : </b> ${item.response.status}
</p>
<p style='margin-left: 25px;'>
<b>Code : </b> ${item.response.code}
</p>
${parseExampleRes(item)}
<br>`;
});
}
else {
html += `<p style='margin-left: 25px; word-wrap: break-word;'>No examples</p>`;
}
html += `
<p style='margin-left: 20px;'>
<b><u>Exceptions : </u></b>
</p>
`;
if (data.exceptions.length > 0) {
data.exceptions.forEach(function(item){
let desc = "";
if (item.desc && item.desc !== "") {
desc = `
<p style='margin-left: 22px;'>
<strong>Description :&nbsp;</strong>
<span style='white-space: pre;'>${item.desc.replace(/ /g, "&nbsp;")}</span>
</p>
`;
}
html += `
<p style='margin-left: 22px;'>
<b>Request : </b>${item.name}
</p>
${desc}
</p>
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>URL :&nbsp;</strong>
${item.request.url}
</p>
<p style='margin-left: 25px; word-wrap: break-word;'>
<strong>Method :&nbsp;</strong>
${item.request.method}
</p>
${parseExampleReq(item)}
<p style='margin-left: 22px;'>
<b>Response : </b>
</p>
<p style='margin-left: 25px;'>
<b>Code : </b> ${item.response.code}
</p>
<p style='margin-left: 25px;'>
<b>Status : </b> ${item.response.status}
</p>
${parseExampleRes(item)}
<br>`;
});
}
else {
html += `<p style='margin-left: 25px; word-wrap: break-word;'>No examples</p>`;
}
return html;
}
function generateApiHtml(data) {
let html = `
<h3 style='margin-left: 10px;'>
<b>${data.name}</b>
</h3>
`;
let header_html = "";
let auth_html = "";
if (data.header.length > 0) {
header_html = "";
data.header.forEach(elem => {
header_html += "<p style='margin-left: 20px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
});
}
if(data.auth.length > 0) {
auth_html="<p style='margin-left: 15px; word-wrap: break-word;'><strong>Auth :&nbsp;</strong></p>";
data.auth.forEach(elem => {
auth_html += "<p style='margin-left: 20px; word-wrap: break-word;'>key : "+elem.key+", value : "+elem.value+", type : "+elem.type+"</p>";
});
}
let body = "<pre style='font-size: 16px; margin-left: 20px; word-wrap: break-word;'>not required, leave blank</pre>";
switch (typeof data.body) {
case 'string':
if (data.body !== "") {
body = `<pre style='font-size: 16px; margin-left: 20px; word-wrap: break-word;'>${data.body}</pre>`;
}
break;
case 'object':
body = "";
data.body.forEach(item => {
body += `<pre style='font-size: 16px; margin-left: 20px; word-wrap: break-word;'>${JSON.stringify(item, null, 2)}</pre>`;
});
break;
default:
body = "<pre style='font-size: 16px; margin-left: 20px; word-wrap: break-word;'>not required, leave blank</pre>";
break;
}
if (data.bodyOptions && data.bodyOptions !== "") {
Object.keys(data.bodyOptions).forEach(function(typeName) {
if (typeName === "language") {
header_html += `
<p style='margin-left: 20px; word-wrap: break-word;'>
"Content-type" : "${data.bodyOptions[typeName]}"
</p>`;
}
});
}
let variables = "<p style='margin-left: 20px; word-wrap: break-word;'>not required, leave blank</p>";
if (data.variables.length > 0) {
variables = "";
data.variables.forEach(elem => {
variables += "<p style='margin-left: 20px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
});
}
let queryes = "<p style='margin-left: 20px; word-wrap: break-word;'>not required, leave blank</p>";
if (data.queryes.length > 0) {
queryes = "";
data.queryes.forEach(elem => {
queryes += "<p style='margin-left: 20px; word-wrap: break-word;'>&quot;"+elem.key+"&quot; : &quot;"+elem.value+"&quot;</p>";
});
}
if (header_html === "") {
header_html = "<p style='margin-left: 20px; word-wrap: break-word;'>not required, leave blank</p>";
}
let desc = "";
if (data.desc && data.desc !== "") {
desc = `
<p style='margin-left: 15px;'>
<strong>Description :&nbsp;</strong>
<span style='white-space: pre;'>${data.desc.replace(/ /g, "&nbsp;")}</span>
</p>
`;
}
html +=
`${desc}
<p style='margin-left: 15px; word-wrap: break-word;'>
<strong>URL :&nbsp;</strong>
${data.url}
</p>
<p style='margin-left: 15px; word-wrap: break-word;'>
<strong>Method :&nbsp;</strong>
${data.method}
</p>
<p style='margin-left: 15px; word-wrap: break-word;'>
<strong>Headers :&nbsp;</strong>
</p>
${header_html+auth_html}
<p style='margin-left: 15px; word-wrap: break-word;'>
<b>Path Variables:&nbsp;</b>
</p>
${variables}
<p style='margin-left: 15px; word-wrap: break-word;'>
<b>Query Parameters :&nbsp;</b>
</p>
${queryes}
<p style='margin-left: 15px; word-wrap: break-word;'>
<b>Body :&nbsp;</b>
</p>
${body}
<br>
<p style='margin-left: 15px;'>
<b>Examples :&nbsp;</b>
</p>
${generateExamples(data.examples)}
<br><hr>`;
return html;
}
module.exports = function htmlGenerator(collection_name, data, outputPath) {
console.log("Start parse");
let html =
`<html>
<head>
<style>
* {
padding: 0;
margin: 2px 0;
}
h2, h3 {
margin: 10px 0;
}
.page {
page-break-after: always;
counter-increment: page;
}
.content {
font-family: Courier New, monospace;
word-wrap: break-word;
page-break-inside: avoid;
}
@page {
size: A4;
margin: 0.5cm;
}
</style>
</head>
<body>
<div class="page">
<div class="content">
<h1 style='text-align: center;'>
<u>${collection_name} Documentation</u>
</h1>
</div>
</div>
`;
Object.keys(data).forEach(function(scope) {
if (!data[scope].apis) {
let trigger = false;
Object.keys(data[scope]).forEach(function(subScope) {
if (trigger === false) {
html += `
<div class="page">
<div class="content">
<h2 style='text-align: center;'>
<u>${scope}</u>
</h2>
<h3 style='margin-left: 5px;'>
<u>${subScope}</u>
</h3>
<hr>
`;
trigger = true;
}
else {
html += `
<div class="page">
<div class="content">
<h3 style='margin-left: 5px;'>
<u>${subScope}</u>
</h3>
<hr>
`;
}
data[scope][subScope].apis.forEach(api => {
html += `
<div class="page">
<div class="content">
`;
html += generateApiHtml(api);
html += `
</div>
</div>`;
});
html += `
</div>
</div>`;
});
}
else {
html += `
<div class="page">
<div class="content">
<h2 style='margin-left: 5px;'>
<u>${scope}</u>
</h2>
<hr>
`;
data[scope].apis.forEach(api => {
html += `
<div class="page">
<div class="content">
`;
html += generateApiHtml(api);
html += `
</div>
</div>`;
});
html += `
</div>
</div>
`;
}
});
html += "</body></html>";
fs.writeFile(`${outputPath}/${collection_name}.html`, html, 'utf8', (err) => {
if (err) {
console.log(err);
}
console.log(`${outputPath}/${collection_name}.html`);
});
console.log("End parse");
return html;
};

26
utils/pdf-generator.js Normal file
View File

@ -0,0 +1,26 @@
const pdf = require('html-pdf');
module.exports = function pdfGenerator(html, file, outputPath) {
const filepath = outputPath + "/" + file;
const options = {
format: 'A4',
timeout: 60000,
"border": "5mm",
"height": '297mm',
"footer": {
"height": "5mm",
"contents": {
default: '<span style="display: block; color: #444; font-size: 15px; text-align: right; width: 100%;">{{page}} / {{pages}}</span>',
}
},
};
console.log("Generate PDF");
pdf.create(html, options).toFile(filepath, function(err, res) {
if (err) {
console.log(err);
}
if (res) {
console.log("End generation PDF");
}
});
};

238
utils/postman-to-pdf.js Normal file
View File

@ -0,0 +1,238 @@
const fs = require('fs');
const generatePdf = require('./pdf-generator');
const generateHtml = require('./html-generator');
function getExamples(data, scopeAuth, apiRoute) {
const examples = {
success: [],
exceptions: []
};
data.forEach(function(item) {
const method = item.originalRequest.method;
const headers = [];
const auth = [];
const variables = [];
const queryes = [];
item.originalRequest.header.forEach(headeritem => {
const header_obj = {
key: headeritem.key,
value: headeritem.value
};
headers.push(header_obj);
});
if(item.originalRequest.auth) {
const auth_type = item.originalRequest.auth.type;
item.originalRequest.auth[auth_type].forEach(authitem=> {
const auth_obj = {
key: authitem.key,
value: authitem.value,
type: authitem.type
};
auth.push(auth_obj);
});
}
else {
if (scopeAuth !== null) {
const auth_type = scopeAuth.type;
scopeAuth[auth_type].forEach(authitem=> {
const auth_obj = {
key: authitem.key,
value: authitem.value,
type: authitem.type
};
req_auth.push(auth_obj);
});
}
}
let body = "";
let mode = "";
let options = "";
if(item.originalRequest.body) {
body = item.originalRequest.body[item.originalRequest.body.mode];
mode = item.originalRequest.body.mode;
if (item.originalRequest.body.options) {
options = item.originalRequest.body.options[item.originalRequest.body.mode];
}
}
const url = item.originalRequest.url.raw.replace("{{api_url}}", apiRoute);
if (item.originalRequest.url.variable) {
item.originalRequest.url.variable.forEach(item=> {
variables.push(item);
});
}
if (item.originalRequest.url.query) {
item.originalRequest.url.query.forEach(item=> {
queryes.push(item);
});
}
const result = {
name: item.name,
request: {
header: headers,
auth: auth,
method: method,
body: body,
bodyMode: mode,
bodyOptions: options,
url: url,
variables: variables,
queryes: queryes,
desc: item.originalRequest.description || ""
},
response: {
status: item.status,
code: item.code,
language: item._postman_previewlanguage,
headers: item.header,
cookies: item.cookie,
body: item.body
}
};
if (item.code === 200) {
examples.success.push(result);
}
else {
examples.exceptions.push(result);
}
});
return examples;
}
function parseData(data, scopeAuth, apiRoute) {
const req_name = data.name;
const req_method = data.request.method;
const req_headers = [];
const req_auth = [];
const variables = [];
const queryes = [];
let examples;
if (data.response && data.response.length > 0) {
examples = getExamples(data.response, scopeAuth, apiRoute);
}
data.request.header.forEach(headeritem => {
const header_obj = {
key: headeritem.key,
value: headeritem.value
};
if (header_obj.key !== 'Authorization') {
req_headers.push(header_obj);
}
});
if(data.request.auth) {
const auth_type = data.request.auth.type;
data.request.auth[auth_type].forEach(authitem=> {
const auth_obj = {
key: authitem.key,
value: authitem.value,
type: authitem.type
};
req_auth.push(auth_obj);
});
}
else {
if (scopeAuth !== null) {
const auth_type = scopeAuth.type;
scopeAuth[auth_type].forEach(authitem=> {
const auth_obj = {
key: authitem.key,
value: authitem.value,
type: authitem.type
};
req_auth.push(auth_obj);
});
}
}
let req_body = "";
let req_mode = "";
let req_options = "";
if(data.request.body) {
req_body = data.request.body[data.request.body.mode];
req_mode = data.request.body.mode;
if (data.request.body.options) {
req_options = data.request.body.options[data.request.body.mode];
}
}
const reqUrl = data.request.url.raw.replace("{{api_url}}", apiRoute);
if (reqUrl.includes('public')) {
req_headers.push({key: 'Authorization (optional)', value: 'Bearer |token|'});
}
if (reqUrl.includes('user')) {
req_headers.push({key: 'Authorization (required)', value: 'Bearer |token|'});
}
if (reqUrl.includes('admin')) {
req_headers.push({key: 'Authorization (required)', value: 'Bearer |token|'});
}
if (data.request.url.variable) {
data.request.url.variable.forEach(item=> {
variables.push(item);
});
}
if (data.request.url.query) {
data.request.url.query.forEach(item=> {
queryes.push(item);
});
}
return {
name: req_name,
header: req_headers,
auth: req_auth,
method: req_method,
body: req_body,
bodyMode: req_mode,
bodyOptions: req_options,
url: reqUrl,
variables: variables,
queryes: queryes,
desc: data.request.description || "",
examples: examples || {success: [],exceptions: []}
};
}
module.exports = function filereader(filepath, outputPath, apiRoute) {
fs.readFile(filepath, 'utf-8', (err, data) => {
if(err) {
console.log(err);
}
const jsonData = JSON.parse(data);
const collection_name = jsonData.info.name;
const resultData = {};
jsonData.item.forEach(scope => {
if(scope.item) {
let auth = null;
if (scope.auth && scope.auth.length > 0) {
auth = scope.auth;
}
resultData[scope.name] = {};
scope.item.forEach(subscope => {
if(subscope.item) {
resultData[scope.name][subscope.name] = {};
resultData[scope.name][subscope.name].apis = [];
subscope.item.forEach(reqitem => {
resultData[scope.name][subscope.name].apis.push(parseData(reqitem, auth, apiRoute));
});
}
else {
if(!resultData[scope.name].apis) {
resultData[scope.name].apis = [];
}
resultData[scope.name].apis.push(parseData(subscope, auth, apiRoute));
}
});
}
});
generatePdf(generateHtml(collection_name, resultData, outputPath),`${collection_name}.pdf`, outputPath);
});
};