Compare commits

..

3 Commits

Author SHA1 Message Date
23d3950695
chore: add launchSettings.json to make development not suck
This workflow allows the possibility to reload a changed page without reloading the entire app. I hate launchSettings.json, but whatever.
2024-05-05 21:10:23 +01:00
83beffe685
perf: remove redundant StatusCode set 2024-05-05 20:55:32 +01:00
f08a3d3607
feat: add real http status pages 2024-05-05 20:55:13 +01:00
25 changed files with 257 additions and 69 deletions

View File

@ -33,7 +33,6 @@
<PackageReference Include="MailKit" Version="4.4.0"/> <PackageReference Include="MailKit" Version="4.4.0"/>
<PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/> <PackageReference Include="MailKitSimplified.Sender" Version="2.9.0"/>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3"/>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="8.0.3"/>
<PackageReference Include="NetBarcode" Version="1.7.0"/> <PackageReference Include="NetBarcode" Version="1.7.0"/>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>

View File

@ -79,7 +79,6 @@ public class Article : PageModel
var date = new DateOnly(year, month, day); var date = new DateOnly(year, month, day);
if (!_blogPostService.TryGetPost(date, slug, out IBlogPost? post)) if (!_blogPostService.TryGetPost(date, slug, out IBlogPost? post))
{ {
Response.StatusCode = 404;
return NotFound(); return NotFound();
} }

View File

@ -1,34 +0,0 @@
@page "/error/{code:int?}"
@model OliverBooth.Pages.ErrorModel
@{
Layout = "_MinimalLayout";
ViewData["Title"] = "Error";
}
<h2 class="text-danger">
@switch (Model.HttpStatusCode)
{
case 403:
<span>403 Forbidden</span>
break;
case 404:
<span>404 Page not found</span>
break;
case 500:
<span>Internal server error</span>
break;
default:
<span>Something went wrong</span>
break;
}
</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

View File

@ -1,28 +0,0 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace OliverBooth.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public int HttpStatusCode { get; private set; }
public IActionResult OnGet(int? code = null)
{
HttpStatusCode = code ?? HttpContext.Response.StatusCode;
if (HttpStatusCode == 200)
{
return RedirectToPage("/Index");
}
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
return Page();
}
}

View File

@ -0,0 +1,17 @@
@page "/error/400"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">400 Bad Request</h1>
</div>
<div class="p-2">
<p class="text-center">Received invalid request message. Check your request and try again.</p>
</div>
</div>
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/400-bad-request.png">
</div>
</div>
</article>

View File

@ -0,0 +1,15 @@
@page "/error/403"
<article>
<div class="text-center d-flex flex-column align-items-center">
<div class="p-2">
<h1 class="text-center">403 Forbidden</h1>
</div>
<div class="p-2">
<img class="img-fluid" src="~/img/error/403-forbidden.png">
</div>
<div class="p-2">
<p class="text-center">Access to the requested page is forbidden.</p>
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/504"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/504-gateway-timeout.png">
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">504 Gateway Timeout</h1>
</div>
<div class="p-2">
<p class="text-center">The server is slacking. Give it more coffee.</p>
</div>
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/400"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">410 Gone</h1>
</div>
<div class="p-2">
<p class="text-center">The requested page has mysteriously disappeared.</p>
</div>
</div>
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/410-gone.png">
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/418"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/418-im-a-teapot.png">
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">418 I'm A Teapot</h1>
</div>
<div class="p-2">
<p class="text-center">No coffee available. I am only capable of brewing tea.</p>
</div>
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/500"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/500-internal-server-error.png">
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">500 Internal Server Error</h1>
</div>
<div class="p-2">
<p class="text-center">This is my fault, not yours.</p>
</div>
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/404"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/404-not-found.png">
</div>
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">404 Not Found</h1>
</div>
<div class="p-2">
<p class="text-center">The requested page could not be found.</p>
</div>
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/503"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">503 Service Unavailable</h1>
</div>
<div class="p-2">
<p class="text-center">The server is currently unable to process your request. Please try again later.</p>
</div>
</div>
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/503-service-unavailable.png">
</div>
</div>
</article>

View File

@ -0,0 +1,17 @@
@page "/error/429"
<article>
<div class="d-flex align-items-center justify-content-center">
<div class="d-flex flex-column align-items-center justify-content-center">
<div class="p-2">
<h1 class="text-center">429 Too Many Requests</h1>
</div>
<div class="p-2">
<p class="text-center">You are being rate limited.</p>
</div>
</div>
<div class="align-self-stretch">
<img class="img-fluid" src="~/img/error/429-too-many-requests.png">
</div>
</div>
</article>

View File

@ -69,7 +69,7 @@ builder.Services.AddSingleton<IProjectService, ProjectService>();
builder.Services.AddSingleton<IMastodonService, MastodonService>(); builder.Services.AddSingleton<IMastodonService, MastodonService>();
builder.Services.AddSingleton<ITutorialService, TutorialService>(); builder.Services.AddSingleton<ITutorialService, TutorialService>();
builder.Services.AddSingleton<IReadingListService, ReadingListService>(); builder.Services.AddSingleton<IReadingListService, ReadingListService>();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews(); builder.Services.AddControllersWithViews();
builder.Services.AddRouting(options => options.LowercaseUrls = true); builder.Services.AddRouting(options => options.LowercaseUrls = true);
builder.Services.AddReCaptcha(builder.Configuration.GetSection("ReCaptcha")); builder.Services.AddReCaptcha(builder.Configuration.GetSection("ReCaptcha"));
@ -81,9 +81,78 @@ if (builder.Environment.IsProduction())
WebApplication app = builder.Build(); WebApplication app = builder.Build();
app.Use(async (ctx, next) =>
{
await next();
if (ctx.Response.HasStarted)
{
return;
}
string? originalPath = ctx.Request.Path.Value;
ctx.Items["originalPath"] = originalPath;
bool matchedErrorPage = false;
switch (ctx.Response.StatusCode)
{
case 400:
ctx.Request.Path = "/error/401";
matchedErrorPage = true;
break;
case 403:
ctx.Request.Path = "/error/403";
matchedErrorPage = true;
break;
case 404:
ctx.Request.Path = "/error/404";
matchedErrorPage = true;
break;
case 410:
ctx.Request.Path = "/error/410";
matchedErrorPage = true;
break;
case 418:
ctx.Request.Path = "/error/418";
matchedErrorPage = true;
break;
case 429:
ctx.Request.Path = "/error/429";
matchedErrorPage = true;
break;
case 500:
ctx.Request.Path = "/error/500";
matchedErrorPage = true;
break;
case 503:
ctx.Request.Path = "/error/503";
matchedErrorPage = true;
break;
case 504:
ctx.Request.Path = "/error/504";
matchedErrorPage = true;
break;
}
if (matchedErrorPage)
{
await next();
}
});
app.UseStatusCodePagesWithReExecute("/error/{0}");
if (!app.Environment.IsDevelopment()) if (!app.Environment.IsDevelopment())
{ {
app.UseExceptionHandler("/Error"); app.UseExceptionHandler("/error/500");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts(); app.UseHsts();
} }

View File

@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5000",
"sslPort": 2845
}
},
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"workingDirectory": "bin/Debug/net8.0/",
"applicationUrl": "https://localhost:2845",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -15,18 +15,20 @@ internal sealed class MastodonService : IMastodonService
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
}; };
private readonly IConfiguration _configuration;
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
public MastodonService(HttpClient httpClient) public MastodonService(IConfiguration configuration, HttpClient httpClient)
{ {
_configuration = configuration;
_httpClient = httpClient; _httpClient = httpClient;
} }
/// <inheritdoc /> /// <inheritdoc />
public IMastodonStatus GetLatestStatus() public IMastodonStatus GetLatestStatus()
{ {
string token = Environment.GetEnvironmentVariable("MASTODON_TOKEN") ?? string.Empty; string token = _configuration.GetSection("Mastodon:Token").Value ?? string.Empty;
string account = Environment.GetEnvironmentVariable("MASTODON_ACCOUNT") ?? string.Empty; string account = _configuration.GetSection("Mastodon:Account").Value ?? string.Empty;
using var request = new HttpRequestMessage(); using var request = new HttpRequestMessage();
request.Headers.Add("Authorization", $"Bearer {token}"); request.Headers.Add("Authorization", $"Bearer {token}");
request.RequestUri = new Uri($"https://mastodon.olivr.me/api/v1/accounts/{account}/statuses"); request.RequestUri = new Uri($"https://mastodon.olivr.me/api/v1/accounts/{account}/statuses");

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
src/img/error/410-gone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB