Skip to content

Commit 90bfa38

Browse files
committed
feat(swagger): add link to show openapi spec
1 parent f92d716 commit 90bfa38

7 files changed

+124
-66
lines changed

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ yarn-error.log*
3434

3535
!apps/docs
3636
!apps/website/app/build
37+
38+
apps/api/src/swagger-custom-ui.js

apps/api/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"siwe": "^1.1.6",
3434
"sqlite3": "^5.1.4",
3535
"typeorm": "^0.3.12",
36-
"uuid": "^9.0.0"
36+
"uuid": "^9.0.0",
37+
"yaml": "^2.7.0"
3738
},
3839
"devDependencies": {
3940
"@nestjs/cli": "^9.0.0",

apps/api/src/main.ts

+13-63
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { Logger, ValidationPipe } from "@nestjs/common"
22
import { NestFactory } from "@nestjs/core"
33
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"
44
import { ironSession } from "iron-session/express"
5+
import { stringify } from "yaml"
6+
import { readFileSync } from "fs"
7+
import { join } from "path"
58
import { AppModule } from "./app/app.module"
69
import { GroupsService } from "./app/groups/groups.service"
710

@@ -51,80 +54,27 @@ async function bootstrap() {
5154

5255
const document = SwaggerModule.createDocument(app, config)
5356

57+
const customJsStr = readFileSync(
58+
join(__dirname, "swagger-custom-ui.js"),
59+
"utf8"
60+
)
61+
5462
const configUI = {
5563
swaggerOptions: { defaultModelsExpandDepth: -1 },
5664
customfavIcon:
5765
"https://raw.githubusercontent.com/bandada-infra/bandada/main/apps/dashboard/src/assets/favicon.ico",
5866
customSiteTitle: "Bandada API Docs",
5967
customCss: `.topbar-wrapper img {content:url('https://raw.githubusercontent.com/bandada-infra/bandada/d5268274cbb93f73a1960e131bff0d2bf1eacea9/apps/dashboard/src/assets/icon1.svg'); width:60px; height:auto;}
6068
.swagger-ui .topbar { background-color: transparent; } small.version-stamp { display: none !important; }`,
61-
customJsStr: `
62-
// Add a custom title to the right side of the Swagger UI page
63-
document.addEventListener('DOMContentLoaded', function() {
64-
const customTitle = document.createElement('div');
65-
customTitle.style.position = 'absolute';
66-
customTitle.style.top = '27px';
67-
customTitle.style.padding = '10px';
68-
customTitle.style.color = 'black';
69-
customTitle.style.fontSize = '18px';
70-
customTitle.style.padding = '0 20px';
71-
customTitle.style.maxWidth = '1460px';
72-
customTitle.style.display = 'flex';
73-
customTitle.style.justifyContent = 'end';
74-
customTitle.style.width = '100%';
75-
76-
77-
// Create a hyperlink element
78-
const link = document.createElement('a');
79-
link.href = 'https://github.com/bandada-infra/bandada';
80-
link.rel = 'noreferrer noopener nofollow';
81-
link.target = '_blank'
82-
link.style.color = 'grey';
83-
link.style.display = 'flex';
84-
85-
// Create a text node for the link text
86-
const linkText = document.createTextNode('Github');
87-
88-
// Append the text node to the link
89-
link.appendChild(linkText);
90-
91-
// Create an SVG element for the GitHub icon
92-
const githubIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
93-
githubIcon.setAttribute('width', '24');
94-
githubIcon.setAttribute('height', '24');
95-
githubIcon.setAttribute('viewBox', '0 0 20 20');
96-
githubIcon.setAttribute('fill', 'currentColor');
97-
98-
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
99-
path.setAttribute('d', 'M8 .153C3.589.153 0 3.742 0 8.153c0 3.436 2.223 6.358 5.307 7.408.387.071.53-.168.53-.374 0-.185-.007-.674-.01-1.322-2.039.445-2.47-.979-2.47-.979-.334-.849-.815-1.075-.815-1.075-.667-.457.05-.448.05-.448.739.052 1.13.76 1.13.76.656 1.124 1.719.799 2.134.61.067-.478.256-.8.466-.98-1.63-.184-3.34-.815-3.34-3.627 0-.8.287-1.457.754-1.969-.076-.185-.327-.932.072-1.943 0 0 .618-.198 2.03.752a6.74 6.74 0 0 1 1.8-.245c.61.003 1.226.082 1.8.245 1.41-.95 2.027-.752 2.027-.752.4 1.011.148 1.758.073 1.943.47.512.754 1.17.754 1.969 0 2.82-1.712 3.44-3.35 3.623.264.227.497.672.497 1.356 0 .977-.009 1.764-.009 2.004 0 .207.141.449.544.373C13.775 14.511 16 11.59 16 8.154 16 3.743 12.411 .154 8 .154z');
100-
101-
// Append the path to the GitHub icon
102-
githubIcon.appendChild(path);
103-
104-
// Append the GitHub icon to the link
105-
link.insertBefore(githubIcon, link.firstChild);
106-
107-
// Apply some padding to create space between the icon and the text
108-
link.style.paddingLeft = '8px';
109-
110-
// Append the link to the custom title
111-
customTitle.appendChild(link);
112-
113-
const parentDiv = document.createElement('div');
114-
parentDiv.style.display = 'flex';
115-
parentDiv.style.justifyContent = 'center';
116-
parentDiv.style.width = 'auto';
117-
118-
119-
parentDiv.appendChild(customTitle)
120-
121-
document.body.appendChild(parentDiv);
122-
});
123-
`
69+
customJsStr
12470
}
12571

12672
SwaggerModule.setup("/", app, document, configUI)
12773

74+
app.getHttpAdapter().get("/openapi.yml", (req, res) => {
75+
res.type("text/yaml").send(stringify(document))
76+
})
77+
12878
await app.listen(port)
12979

13080
Logger.log(`🚀 Application is running on: http://localhost:${port}`)

apps/api/src/swagger-custom-ui.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
document.addEventListener("DOMContentLoaded", function () {
2+
const linkStyles = {
3+
color: "grey",
4+
textDecoration: "underline",
5+
fontSize: "14px",
6+
display: "flex",
7+
alignItems: "center",
8+
gap: "5px"
9+
}
10+
11+
const linkAttributes = {
12+
rel: "noreferrer noopener nofollow",
13+
target: "_blank"
14+
}
15+
16+
function El({ tag, id, href, text, attrs = {}, styles = {} }) {
17+
const el = document.createElement(tag)
18+
if (id) el.id = id
19+
if (href) el.href = href
20+
if (text) el.innerText = text
21+
Object.entries(attrs).forEach(([key, value]) =>
22+
el.setAttribute(key, value)
23+
)
24+
Object.assign(el.style, styles)
25+
return el
26+
}
27+
28+
const A = ({ id, href, text }) =>
29+
El({
30+
id,
31+
href,
32+
text,
33+
tag: "a",
34+
styles: linkStyles,
35+
attrs: linkAttributes
36+
})
37+
38+
function addCustomLinks() {
39+
const topbar = document.querySelector(".topbar")
40+
if (!topbar) return
41+
42+
if (document.getElementById("ghLink")) return
43+
44+
const customContainer = El({
45+
tag: "div",
46+
styles: {
47+
display: "flex",
48+
flexDirection: "column",
49+
alignItems: "end",
50+
position: "absolute",
51+
top: "20px",
52+
right: "40px",
53+
gap: "8px"
54+
}
55+
})
56+
57+
const ghLink = A({
58+
id: "ghLink",
59+
href: "https://github.com/bandada-infra/bandada",
60+
text: ""
61+
})
62+
63+
const githubIcon = document.createElement("span")
64+
githubIcon.innerHTML = `
65+
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 98 96"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>
66+
`
67+
ghLink.prepend(githubIcon)
68+
69+
const yamlLink = A({
70+
id: "yamlLink",
71+
href: "/openapi.yml",
72+
text: "openapi.yml"
73+
})
74+
75+
customContainer.appendChild(ghLink)
76+
customContainer.appendChild(yamlLink)
77+
topbar.appendChild(customContainer)
78+
}
79+
80+
// Handle late-loading Swagger UI
81+
const observer = new MutationObserver((mutationsList, observer) => {
82+
for (const mutation of mutationsList) {
83+
if (mutation.type === "childList") {
84+
addCustomLinks()
85+
}
86+
}
87+
})
88+
89+
observer.observe(document.body, { childList: true, subtree: true })
90+
91+
// Run once in case .topbar is already present
92+
addCustomLinks()
93+
})

apps/api/tsconfig.build.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"extends": "./tsconfig.json",
3-
"exclude": ["node_modules", "dist", "**/*spec.ts", "**/*.test.ts"]
3+
"exclude": ["node_modules", "dist", "**/*spec.ts", "**/*.test.ts"],
4+
"include": ["src", "types"]
45
}

apps/api/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"noImplicitAny": false,
1818
"strictBindCallApply": false,
1919
"forceConsistentCasingInFileNames": false,
20-
"noFallthroughCasesInSwitch": false
20+
"noFallthroughCasesInSwitch": false,
21+
"allowJs": true
2122
}
2223
}

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -12206,6 +12206,7 @@ __metadata:
1220612206
typeorm: "npm:^0.3.12"
1220712207
typescript: "npm:^4.7.4"
1220812208
uuid: "npm:^9.0.0"
12209+
yaml: "npm:^2.7.0"
1220912210
languageName: unknown
1221012211
linkType: soft
1221112212

@@ -32757,6 +32758,15 @@ __metadata:
3275732758
languageName: node
3275832759
linkType: hard
3275932760

32761+
"yaml@npm:^2.7.0":
32762+
version: 2.7.0
32763+
resolution: "yaml@npm:2.7.0"
32764+
bin:
32765+
yaml: bin.mjs
32766+
checksum: 10/c8c314c62fbd49244a6a51b06482f6d495b37ab10fa685fcafa1bbaae7841b7233ee7d12cab087bcca5a0b28adc92868b6e437322276430c28d00f1c1732eeec
32767+
languageName: node
32768+
linkType: hard
32769+
3276032770
"yargs-parser@npm:13.1.2, yargs-parser@npm:^13.1.2":
3276132771
version: 13.1.2
3276232772
resolution: "yargs-parser@npm:13.1.2"

0 commit comments

Comments
 (0)