81 lines
3.2 KiB
C#
81 lines
3.2 KiB
C#
using Elternbeirat.Contracts;
|
|
using Elternbeirat.PocketBase;
|
|
using Elternbeirat.Web.Components;
|
|
using Elternbeirat.Web.Features.Events;
|
|
using Microsoft.AspNetCore.HttpOverrides;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddRazorComponents();
|
|
|
|
// The typed client reads content from PocketBase over its REST API. The base URL
|
|
// comes from config: appsettings for local dev, the PocketBase__BaseUrl env var
|
|
// on the server (see compose.yaml).
|
|
var pocketBaseUrl = builder.Configuration["PocketBase:BaseUrl"]
|
|
?? throw new InvalidOperationException("PocketBase:BaseUrl is not configured.");
|
|
builder.Services.AddHttpClient<PocketBaseClient>(client =>
|
|
{
|
|
client.BaseAddress = new Uri(pocketBaseUrl);
|
|
// Every page waits for PocketBase. The default wait is 100 seconds, so if
|
|
// PocketBase is down the page would hang that long. 5 seconds fails fast instead.
|
|
client.Timeout = TimeSpan.FromSeconds(5);
|
|
});
|
|
|
|
var app = builder.Build();
|
|
|
|
// NPM sits in front of the app and handles HTTPS. Without this line the app would
|
|
// think every request is plain HTTP and would see NPM's IP, not the visitor's.
|
|
// KnownNetworks/KnownProxies are left empty on purpose: only NPM reaches the app.
|
|
app.UseForwardedHeaders(
|
|
new ForwardedHeadersOptions
|
|
{
|
|
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
|
});
|
|
|
|
if (app.Environment.IsDevelopment() is false)
|
|
{
|
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
|
// No UseHsts() and no UseHttpsRedirection() here: NPM already handles HTTPS.
|
|
// Adding them behind NPM would send the browser into a redirect loop.
|
|
}
|
|
|
|
// Don't add UseStatusCodePagesWithReExecute here. Blazor already shows its own
|
|
// not-found page. That middleware would catch *every* error code, so when PocketBase
|
|
// is down (a 503) it would wrongly show "not found" instead of our own message.
|
|
app.UseAntiforgery();
|
|
|
|
app.MapStaticAssets();
|
|
app.MapRazorComponents<App>();
|
|
|
|
// Calendar feed people can subscribe to. It returns calendar data, not a web page,
|
|
// so it is a plain endpoint, not a Razor page. If PocketBase is down it returns an
|
|
// empty calendar rather than an error.
|
|
app.MapGet("/events.ics", async (PocketBaseClient pocketBase, CancellationToken token) =>
|
|
{
|
|
IReadOnlyList<Event> events;
|
|
try
|
|
{
|
|
events = await pocketBase.GetEventsAsync(token);
|
|
}
|
|
catch (PocketBaseUnavailableException)
|
|
{
|
|
events = [];
|
|
}
|
|
|
|
return Results.Text(IcsCalendar.Build(events), "text/calendar; charset=utf-8");
|
|
});
|
|
|
|
// Status page for an outside monitor (Uptime Kuma) to check. One check tells all
|
|
// three states apart: no reply = the app is down, 503 = the app runs but PocketBase
|
|
// is down, 200 = both are fine. It talks to PocketBase directly, not through the
|
|
// page-rendering code, so it can still report when that code is the thing failing.
|
|
app.MapGet("/health", async (PocketBaseClient pocketBase, CancellationToken token) =>
|
|
await pocketBase.IsHealthyAsync(token)
|
|
? Results.Text("healthy", "text/plain; charset=utf-8")
|
|
: Results.Text(
|
|
"PocketBase unreachable",
|
|
"text/plain; charset=utf-8",
|
|
statusCode: StatusCodes.Status503ServiceUnavailable));
|
|
|
|
app.Run();
|