Files
Elternbeirat/Elternbeirat.Web/Program.cs
T

71 lines
2.7 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);
// Add services to the container.
builder.Services.AddRazorComponents();
// The typed client reads content from PocketBase over its REST API. The base URL
// comes from configuration: 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));
var app = builder.Build();
// NPM terminates TLS and is the only way to reach the container (no port
// mapping in production). Without UseForwardedHeaders the app sees every request
// as HTTP and with the proxy IP instead of the client IP.
// KnownNetworks/KnownProxies are deliberately empty because only NPM reaches
// the container.
app.UseForwardedHeaders(
new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() is false)
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// No UseHsts() and no UseHttpsRedirection(): NPM sets HSTS and terminates
// TLS. Both here would create a redirect loop behind the proxy.
}
// No UseStatusCodePagesWithReExecute: the Router renders the NotFoundPage
// (Pages.NotFound) in process for both an unmatched route and an explicit
// NavigationManager.NotFound() from a component (see Routes.razor). Re-executing
// every error code to /not-found would also turn a 503 (content source down) into
// a "not found" page; leaving it out lets those components keep their own
// "temporarily unavailable" markup and 503 status.
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>();
// Subscribable calendar feed of all events. A minimal API endpoint rather than
// a Razor page because it returns text/calendar, not HTML. Reads from PocketBase
// per request; an unreachable source yields an empty calendar, not a 500.
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");
});
app.Run();