Skip to content

Commit e4c7143

Browse files
committed
feat: public swagger
1 parent 462b9c2 commit e4c7143

7 files changed

Lines changed: 177 additions & 19 deletions

File tree

BackgroundProcessor/BackgroundServiceWorker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
3131
foreach (var processor in GetProcessors())
3232
{
3333
_logger.LogDebug("Creating processor {ProcessorName}", processor.FullName);
34+
using var loggingScope = _logger.BeginScope($"Background Processor: {processor.Name}");
3435

3536
var createdProcessor = (IBackgroundProcessor)ActivatorUtilities.CreateInstance(scope.ServiceProvider, processor);
3637

BackgroundProcessor/appsettings.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

WebApi/Middleware/JsonExceptionMiddleware.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace WebApi.Middleware;
1010
/// </summary>
1111
/// <param name="Message">The message of the error.</param>
1212
/// <param name="StackTrace">The stack trace of the error.</param>
13-
/// <param name="Data">Data included with the exception, if any.</param>
13+
/// <param name="Data">A dictionary of data included with the exception, if any.</param>
1414
/// <param name="InnerException">The inner exception of the same <see cref="CapturedException"/> type, recursive, or <c>null</c> if there isn't one.</param>
1515
internal record CapturedException(
1616
string Message,

WebApi/Program.cs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Core.AppSettings;
77
using Core.DbConnection;
88
using Microsoft.AspNetCore.Authorization;
9+
using Microsoft.AspNetCore.Mvc;
910
using SnooBrowser.Util;
1011
using Swashbuckle.AspNetCore.SwaggerUI;
1112
using WebApi.AuthHandlers;
@@ -21,7 +22,11 @@
2122
.AddAuthentication("ApiKey")
2223
.AddScheme<ApiKeyAuthSchemeOptions, ApiKeyAuthHandler>("ApiKey", _ => { });
2324

24-
builder.Services.AddControllers();
25+
builder.Services.AddControllers(c =>
26+
{
27+
c.Filters.Add(new ProducesAttribute("application/json")); // Only show application/json as the available Media Types in Swagger.
28+
});
29+
2530
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
2631
builder.Services.AddEndpointsApiExplorer();
2732
builder.Services.AddSwaggerGen(opts =>
@@ -37,16 +42,23 @@
3742
var app = builder.Build();
3843

3944
// Configure the HTTP request pipeline.
40-
if (app.Environment.IsDevelopment())
45+
app.UseSwagger(c =>
4146
{
42-
app.UseSwagger();
43-
app.UseSwaggerUI(opts =>
44-
{
45-
opts.SwaggerEndpoint("/swagger/v1/swagger.json", "A Centralized Mirror API: v1");
46-
opts.SupportedSubmitMethods(Array.Empty<SubmitMethod>()); // Disable the 'Try it out' button. All endpoints require API keys so it's useless anyway.
47-
opts.RoutePrefix = "docs";
48-
});
49-
}
47+
c.RouteTemplate = "/{documentname}/swagger.json";
48+
});
49+
50+
app.UseSwaggerUI(opts =>
51+
{
52+
opts.SwaggerEndpoint("/v1/swagger.json", "A Centralized Mirror API: v1");
53+
opts.SupportedSubmitMethods(Array.Empty<SubmitMethod>()); // Disable the 'Try it out' button. All endpoints require API keys so it's useless anyway.
54+
opts.RoutePrefix = "docs";
55+
opts.DocumentTitle = "A Centralized Mirror API Documentation";
56+
opts.EnableDeepLinking();
57+
opts.InjectStylesheet("/css/swagger.css");
58+
opts.IndexStream = () => Assembly.GetExecutingAssembly().GetManifestResourceStream("WebApi.Resources.DocsTemplate.html");
59+
});
60+
61+
app.UseStaticFiles();
5062

5163
app.UseHttpsRedirection();
5264

WebApi/Resources/DocsTemplate.html

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<!-- HTML for static distribution bundle build -->
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
<head>
5+
<meta charset="UTF-8">
6+
<title>%(DocumentTitle)</title>
7+
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
8+
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
9+
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
10+
<style>
11+
html {
12+
box-sizing: border-box;
13+
overflow: -moz-scrollbars-vertical;
14+
overflow-y: scroll;
15+
}
16+
17+
*,
18+
*:before,
19+
*:after {
20+
box-sizing: inherit;
21+
}
22+
23+
body {
24+
margin: 0;
25+
background: #fafafa;
26+
}
27+
</style>
28+
%(HeadContent)
29+
</head>
30+
31+
<body>
32+
<div id="swagger-ui"></div>
33+
34+
<div id="authSection">
35+
<h1>Authentication</h1>
36+
37+
<p>Users of this API are given an API key that must be used on all requests to the API. Want to integrate? <a href="https://amirror.link/lets-talk" target="_blank">Let's talk!</a> Once you have your API key, you must provide it in the <code>Authorization</code> HTTP header on all requests. The format of this key is as follows:</p>
38+
39+
<p><code>Authorization: Key myApiKeyHere</code></p>
40+
41+
<p>For example, if your API key were <code>THIS_IS_AN_EXAMPLE</code>, your authorization header would look like this: <code>Authorization: Key THIS_IS_AN_EXAMPLE</code></p>
42+
</div>
43+
44+
<!-- Workaround for https://github.com/swagger-api/swagger-editor/issues/1371 -->
45+
<script>
46+
if (window.navigator.userAgent.indexOf("Edge") > -1) {
47+
console.log("Removing native Edge fetch in favor of swagger-ui's polyfill")
48+
window.fetch = undefined;
49+
}
50+
</script>
51+
52+
<script src="./swagger-ui-bundle.js"></script>
53+
<script src="./swagger-ui-standalone-preset.js"></script>
54+
<script>
55+
/* Source: https://gist.github.com/lamberta/3768814
56+
* Parse a string function definition and return a function object. Does not use eval.
57+
* @param {string} str
58+
* @return {function}
59+
*
60+
* Example:
61+
* var f = function (x, y) { return x * y; };
62+
* var g = parseFunction(f.toString());
63+
* g(33, 3); //=> 99
64+
*/
65+
function parseFunction(str) {
66+
if (!str) return void (0);
67+
68+
var fn_body_idx = str.indexOf('{'),
69+
fn_body = str.substring(fn_body_idx + 1, str.lastIndexOf('}')),
70+
fn_declare = str.substring(0, fn_body_idx),
71+
fn_params = fn_declare.substring(fn_declare.indexOf('(') + 1, fn_declare.lastIndexOf(')')),
72+
args = fn_params.split(',');
73+
74+
args.push(fn_body);
75+
76+
function Fn() {
77+
return Function.apply(this, args);
78+
}
79+
Fn.prototype = Function.prototype;
80+
81+
return new Fn();
82+
}
83+
84+
window.onload = function () {
85+
var configObject = JSON.parse('%(ConfigObject)');
86+
var oauthConfigObject = JSON.parse('%(OAuthConfigObject)');
87+
88+
// Workaround for https://github.com/swagger-api/swagger-ui/issues/5945
89+
configObject.urls.forEach(function (item) {
90+
if (item.url.startsWith("http") || item.url.startsWith("/")) return;
91+
item.url = window.location.href.replace("index.html", item.url).split('#')[0];
92+
});
93+
94+
// If validatorUrl is not explicitly provided, disable the feature by setting to null
95+
if (!configObject.hasOwnProperty("validatorUrl"))
96+
configObject.validatorUrl = null
97+
98+
// If oauth2RedirectUrl isn't specified, use the built-in default
99+
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
100+
configObject.oauth2RedirectUrl = (new URL("oauth2-redirect.html", window.location.href)).href;
101+
102+
// Apply mandatory parameters
103+
configObject.dom_id = "#swagger-ui";
104+
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
105+
configObject.layout = "StandaloneLayout";
106+
107+
// Parse and add interceptor functions
108+
var interceptors = JSON.parse('%(Interceptors)');
109+
if (interceptors.RequestInterceptorFunction)
110+
configObject.requestInterceptor = parseFunction(interceptors.RequestInterceptorFunction);
111+
if (interceptors.ResponseInterceptorFunction)
112+
configObject.responseInterceptor = parseFunction(interceptors.ResponseInterceptorFunction);
113+
114+
// Begin Swagger UI call region
115+
116+
const ui = SwaggerUIBundle(configObject);
117+
118+
ui.initOAuth(oauthConfigObject);
119+
120+
// End Swagger UI call region
121+
122+
window.ui = ui
123+
124+
setTimeout(() => document.dispatchEvent(new Event("SwaggerUiLoaded")), 0);
125+
}
126+
</script>
127+
<script>
128+
const existenceChecker = setInterval(() => {
129+
if (!document.getElementById("swagger-ui").hasChildNodes())
130+
return;
131+
132+
const containers = document.getElementsByClassName("information-container wrapper");
133+
if (containers.length !== 1)
134+
throw new Error(`Unable to target container: ${containers.length} (${JSON.stringify(containers)})`);
135+
136+
const container = containers[0];
137+
const authSection = document.getElementById("authSection");
138+
container.appendChild(authSection);
139+
authSection.classList.add("visible");
140+
141+
clearInterval(existenceChecker);
142+
}, 10);
143+
</script>
144+
</body>
145+
</html>

WebApi/WebApi.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<Generator>ResXFileCodeGenerator</Generator>
3131
<LastGenOutput>Localization.Designer.cs</LastGenOutput>
3232
</EmbeddedResource>
33+
<EmbeddedResource Include="Resources\DocsTemplate.html" />
3334
</ItemGroup>
3435

3536
<ItemGroup>

WebApi/wwwroot/css/swagger.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#authSection {
2+
display: none;
3+
}
4+
5+
#authSection.visible {
6+
display: block;
7+
}

0 commit comments

Comments
 (0)