Skip to content

Commit 5aad5e6

Browse files
committed
feat: Enhance authentication and article management features
- Updated AuthController to include sample login credentials in the documentation. - Added example JSON for LoginRequest model to improve clarity. - Integrated markdown rendering capabilities into the application by adding markdownRenderer.js. - Updated build scripts to include markdownRenderer.js for proper functionality. - Enhanced mainContentBuilder.js to support rendering descriptions with markdown. - Refactored swagger.json to define a new Articles API with endpoints for managing articles. - Created new schemas for articles, including Article, CreateArticleRequest, and UpdateArticleRequest. - Implemented authorization and authentication mechanisms in the API. - Added AuthorizeOperationFilter to manage authorization for API operations.
1 parent 131d88b commit 5aad5e6

File tree

8 files changed

+244
-7
lines changed

8 files changed

+244
-7
lines changed

src/c-sharp/JakubKozera.OpenApiUi.Sample/Controllers/AuthController.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ public AuthController(UserService userService, TokenService tokenService)
2626
/// </summary>
2727
/// <param name="request">Login credentials</param>
2828
/// <returns>JWT token if credentials are valid</returns>
29+
/// <remarks>
30+
/// Sample login credentials for testing:
31+
///
32+
/// **Administrator:**
33+
/// - Login: admin
34+
/// - Password: admin123
35+
///
36+
/// **Regular Users:**
37+
/// - Login: john.doe, Password: password123
38+
/// - Login: jane.smith, Password: secret456
39+
///
40+
/// </remarks>
2941
/// <response code="200">Login successful, returns JWT token</response>
3042
/// <response code="400">Invalid request format</response>
3143
/// <response code="401">Invalid credentials</response>

src/c-sharp/JakubKozera.OpenApiUi.Sample/Models/Requests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@ namespace JakubKozera.OpenApiUi.Sample.Models;
55
/// <summary>
66
/// Request model for user login
77
/// </summary>
8+
/// <example>
9+
/// {
10+
/// "login": "admin",
11+
/// "password": "admin123"
12+
/// }
13+
/// </example>
814
public class LoginRequest
915
{
1016
/// <summary>
1117
/// User login
1218
/// </summary>
19+
/// <example>admin</example>
1320
[Required]
1421
public required string Login { get; set; }
1522

1623
/// <summary>
1724
/// User password
1825
/// </summary>
26+
/// <example>admin123</example>
1927
[Required]
2028
public required string Password { get; set; }
2129
}

src/core/build.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const jsFiles = [
2626
"js/config.js",
2727
"js/themeManager.js",
2828
"js/utils.js",
29+
"js/markdownRenderer.js",
2930
"js/tooltip.js",
3031
"js/monacoSetup.js",
3132
"js/codeSnippets.js",

src/core/demo-build.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const jsFiles = [
2626
"js/config.js",
2727
"js/themeManager.js",
2828
"js/utils.js",
29+
"js/markdownRenderer.js",
2930
"js/tooltip.js",
3031
"js/monacoSetup.js",
3132
"js/codeSnippets.js",

src/core/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ <h2 class="text-xl font-semibold text-gray-800 dark:text-white">Code Snippet</h2
572572
<script src="js/config.js"></script>
573573
<script src="js/themeManager.js"></script>
574574
<script src="js/utils.js"></script>
575+
<script src="js/markdownRenderer.js"></script>
575576
<script src="js/tooltip.js"></script>
576577
<script src="js/monacoSetup.js"></script>
577578
<script src="js/codeSnippets.js"></script>

src/core/js/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
// API Base URL and other state variables
44
let baseUrl = window.baseUrl;
5+
if (baseUrl === "#base_url#") {
6+
baseUrl = window.location.origin;
7+
}
58
window.swaggerData = null;
69
let currentPath = null; // Store the current path
710
let currentMethod = null; // Store the current method

src/core/js/mainContentBuilder.js

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
// Functions to build main content area
22

3+
// Helper function to render description with markdown support
4+
function renderDescriptionWithMarkdown(description) {
5+
if (!description) return "";
6+
7+
if (
8+
window.markdownRenderer &&
9+
typeof window.markdownRenderer.renderSafe === "function"
10+
) {
11+
try {
12+
const rendered = window.markdownRenderer.renderSafe(description);
13+
return rendered;
14+
} catch (error) {
15+
return `<p class="text-gray-700 mb-4">${description}</p>`;
16+
}
17+
} else {
18+
return `<p class="text-gray-700 mb-4">${description}</p>`;
19+
}
20+
}
21+
322
// Helper function to format type names, especially for arrays
423
function formatTypeDisplay(schema) {
524
// Use the enhanced version if available, fallback to basic version
@@ -420,13 +439,9 @@ function buildMainContent() {
420439
operation.summary
421440
? `<p class="text-gray-600 mb-2 text-sm mt-2">${operation.summary}</p>`
422441
: ""
423-
}
424-
<div class="endpoint-content hidden">
425-
${
426-
operation.description
427-
? `<p class="text-gray-700 mb-4">${operation.description}</p>`
428-
: ""
429-
}`; // Add parameters sections
442+
} <div class="endpoint-content hidden">
443+
${renderDescriptionWithMarkdown(operation.description)}
444+
`; // Add parameters sections
430445
if (operation.parameters && operation.parameters.length > 0) {
431446
const pathParams = operation.parameters.filter((p) => p.in === "path");
432447
const queryParams = operation.parameters.filter(
@@ -1204,6 +1219,28 @@ function buildResponsesSection(operation, sectionId) {
12041219
return sectionHTML;
12051220
}
12061221

1222+
// Helper function to safely render markdown with fallback
1223+
function renderDescription(description) {
1224+
if (!description) {
1225+
return "";
1226+
}
1227+
1228+
// Check if markdown renderer is available
1229+
if (
1230+
window.markdownRenderer &&
1231+
typeof window.markdownRenderer.renderSafe === "function"
1232+
) {
1233+
try {
1234+
const rendered = window.markdownRenderer.renderSafe(description);
1235+
return rendered;
1236+
} catch (error) {
1237+
return `<p class="text-gray-700 mb-4">${description}</p>`;
1238+
}
1239+
} else {
1240+
return `<p class="text-gray-700 mb-4">${description}</p>`;
1241+
}
1242+
}
1243+
12071244
// Utility function to toggle endpoints section
12081245
function toggleEndpointsSection(endpointsContainer, arrow) {
12091246
const isCollapsed = endpointsContainer.style.maxHeight === "0px";

src/core/js/markdownRenderer.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* Markdown Renderer Utility
3+
* Converts Markdown text to HTML with proper sanitization
4+
*/
5+
6+
class MarkdownRenderer {
7+
constructor() {
8+
// Initialize the renderer
9+
}
10+
/**
11+
* Convert Markdown to HTML
12+
* @param {string} markdownText - The markdown text to convert
13+
* @returns {string} - HTML string
14+
*/
15+
render(markdownText) {
16+
if (!markdownText || typeof markdownText !== "string") {
17+
return "";
18+
}
19+
20+
let html = markdownText;
21+
22+
// Normalize line endings (convert \r\n to \n)
23+
html = html.replace(/\r\n/g, "\n");
24+
25+
// Convert headers
26+
html = html.replace(
27+
/^### (.*$)/gm,
28+
'<h3 class="text-lg font-semibold text-gray-800 mt-4 mb-2">$1</h3>'
29+
);
30+
html = html.replace(
31+
/^## (.*$)/gm,
32+
'<h2 class="text-xl font-semibold text-gray-800 mt-4 mb-3">$1</h2>'
33+
);
34+
html = html.replace(
35+
/^# (.*$)/gm,
36+
'<h1 class="text-2xl font-bold text-gray-800 mt-4 mb-3">$1</h1>'
37+
);
38+
39+
// Convert code blocks FIRST (before any other backtick processing)
40+
html = html.replace(/```(\w+)?\s*([\s\S]*?)```/g, (match, lang, code) => {
41+
const language = lang ? ` data-language="${lang}"` : "";
42+
return `<pre class="bg-gray-50 border border-gray-200 rounded-md p-3 my-3 overflow-x-auto"><code class="text-sm font-mono text-gray-800"${language}>${this.escapeHtml(
43+
code.trim()
44+
)}</code></pre>`;
45+
});
46+
47+
// Convert inline code AFTER code blocks
48+
html = html.replace(
49+
/`([^`]+)`/g,
50+
'<code class="bg-gray-100 px-1 py-0.5 rounded text-sm font-mono text-gray-800">$1</code>'
51+
);
52+
53+
// Convert bold text
54+
html = html.replace(
55+
/\*\*(.*?)\*\*/g,
56+
'<strong class="font-semibold">$1</strong>'
57+
);
58+
html = html.replace(
59+
/__(.*?)__/g,
60+
'<strong class="font-semibold">$1</strong>'
61+
);
62+
63+
// Convert italic text
64+
html = html.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>');
65+
html = html.replace(/_(.*?)_/g, '<em class="italic">$1</em>');
66+
67+
// Convert links
68+
html = html.replace(
69+
/\[([^\]]+)\]\(([^)]+)\)/g,
70+
'<a href="$2" class="text-blue-600 hover:text-blue-800 underline" target="_blank" rel="noopener noreferrer">$1</a>'
71+
);
72+
73+
// Convert unordered lists
74+
html = html.replace(
75+
/^[\s]*[-\*\+][\s]+(.*$)/gm,
76+
'<li class="ml-4 list-disc">$1</li>'
77+
);
78+
html = html.replace(
79+
/(<li[^>]*>.*<\/li>)/s,
80+
'<ul class="my-2 space-y-1">$1</ul>'
81+
);
82+
83+
// Convert ordered lists
84+
html = html.replace(
85+
/^[\s]*\d+\.[\s]+(.*$)/gm,
86+
'<li class="ml-4 list-decimal">$1</li>'
87+
);
88+
html = html.replace(
89+
/(<li[^>]*class="[^"]*list-decimal[^"]*"[^>]*>.*<\/li>)/s,
90+
'<ol class="my-2 space-y-1">$1</ol>'
91+
);
92+
93+
// Convert line breaks
94+
html = html.replace(/\n\n/g, '</p><p class="text-gray-700 mb-4">');
95+
96+
// Convert blockquotes
97+
html = html.replace(
98+
/^> (.*$)/gm,
99+
'<blockquote class="border-l-4 border-gray-300 pl-4 italic text-gray-600 my-2">$1</blockquote>'
100+
);
101+
102+
// Convert horizontal rules
103+
html = html.replace(/^---$/gm, '<hr class="my-4 border-gray-300">');
104+
105+
// Wrap in paragraph if not already wrapped
106+
if (html && !html.trim().startsWith("<")) {
107+
html = `<p class="text-gray-700 mb-4">${html}</p>`;
108+
}
109+
110+
return html;
111+
}
112+
113+
/**
114+
* Escape HTML characters to prevent XSS
115+
* @param {string} text - Text to escape
116+
* @returns {string} - Escaped text
117+
*/
118+
escapeHtml(text) {
119+
const div = document.createElement("div");
120+
div.textContent = text;
121+
return div.innerHTML;
122+
}
123+
124+
/**
125+
* Sanitize HTML content
126+
* @param {string} html - HTML to sanitize
127+
* @returns {string} - Sanitized HTML
128+
*/
129+
sanitize(html) {
130+
// Create a temporary element to parse HTML
131+
const temp = document.createElement("div");
132+
temp.innerHTML = html;
133+
134+
// Remove script tags and event handlers
135+
const scripts = temp.querySelectorAll("script");
136+
scripts.forEach((script) => script.remove());
137+
138+
// Remove potentially dangerous attributes
139+
const allElements = temp.querySelectorAll("*");
140+
allElements.forEach((element) => {
141+
// Remove event handlers
142+
Array.from(element.attributes).forEach((attr) => {
143+
if (attr.name.startsWith("on")) {
144+
element.removeAttribute(attr.name);
145+
}
146+
});
147+
148+
// Remove javascript: links
149+
if (element.href && element.href.startsWith("javascript:")) {
150+
element.removeAttribute("href");
151+
}
152+
});
153+
154+
return temp.innerHTML;
155+
}
156+
157+
/**
158+
* Render and sanitize markdown content
159+
* @param {string} markdownText - The markdown text to convert
160+
* @returns {string} - Safe HTML string
161+
*/
162+
renderSafe(markdownText) {
163+
const html = this.render(markdownText);
164+
return this.sanitize(html);
165+
}
166+
}
167+
168+
// Create a global instance
169+
window.markdownRenderer = new MarkdownRenderer();
170+
171+
// Export for module usage
172+
if (typeof module !== "undefined" && module.exports) {
173+
module.exports = MarkdownRenderer;
174+
}

0 commit comments

Comments
 (0)