feat: add health check endpoints for Bot and Web
PR Checks / test-and-build (pull_request) Successful in 8m53s

- Web: add /health endpoint with PostgreSQL readiness check (returns 200+JSON or 503)
- Web: add /alive endpoint for liveness probe
- Bot: add BotHealthCheckHostedService serving /health on port 8081 via HttpListener
- Bot: expose port 8081 in Dockerfile and install wget for healthcheck
- compose.yaml: add healthcheck sections for bot and web services
- tests: add TDD tests for both health endpoints

Bump version -> 1.16.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 10:54:22 +03:00
parent c6aea78ff3
commit 3bea327043
13 changed files with 341 additions and 407 deletions
@@ -56,7 +56,7 @@
</button>
</form>
<div class="nav-version">v1.15.1</div>
<div class="nav-version">v1.16.0</div>
</div>
</Authorized>
<NotAuthorized>
@@ -0,0 +1,25 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Npgsql;
namespace GmRelay.Web.Health;
public sealed class NpgsqlHealthCheck(NpgsqlDataSource dataSource) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
await using var command = connection.CreateCommand();
command.CommandText = "SELECT 1";
await command.ExecuteScalarAsync(cancellationToken);
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("PostgreSQL is unavailable", ex);
}
}
}
+28
View File
@@ -1,9 +1,13 @@
using GmRelay.Web.Components;
using GmRelay.Web.Health;
using GmRelay.Web.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Security.Claims;
using System.Text.Json;
using Telegram.Bot;
using Npgsql;
@@ -12,6 +16,10 @@ var builder = WebApplication.CreateBuilder(args);
// Add Aspire service defaults
builder.AddServiceDefaults();
// Add health checks
builder.Services.AddHealthChecks()
.AddCheck<NpgsqlHealthCheck>("npgsql");
// Add Data Protection
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/app/dataprotection-keys"));
@@ -83,6 +91,26 @@ app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
// Health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var response = new
{
status = report.Status == HealthStatus.Healthy ? "healthy" : "unhealthy",
timestamp = DateTimeOffset.UtcNow.ToString("O")
};
await context.Response.WriteAsJsonAsync(response);
}
});
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
// Endpoint to handle Telegram Login callback
app.MapGet("/auth/telegram", async (HttpContext context, TelegramAuthService authService) =>
{