using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RadicadeServer.API.Notifications; using RadicadeServer.DB; namespace RadicadeServer.API; [ApiController] public sealed class ServiceRootHealthAPI(IDbContextFactory dbFactory) : ControllerBase { private static readonly HashSet HealthRootPaths = new(StringComparer.OrdinalIgnoreCase) { "auth", "ai", "data", "www", "cards", "notify", "images", "cdn", "commerce", "datacollection", "discovery", "matchmaking", "storage", "chat", "geo", "leaderboard", "accounts", "link", "locker", "moderation", "roomcomments", "roomieintegrations", "clubs", "rooms", "econ", "bugreporting", "cms", "gamelogs", "platformnotifications", "stringscdn", "strings", "lists", "studio", "studiocdn", "thorn", "videos" }; internal static bool IsHealthRootPath(string path) => HealthRootPaths.Contains(path.Trim('/')); [HttpGet("~/auth")] [HttpGet("~/ai")] [HttpGet("~/www")] [HttpGet("~/cards")] [HttpGet("~/notify")] [HttpGet("~/images")] [HttpGet("~/cdn")] [HttpGet("~/commerce")] [HttpGet("~/datacollection")] [HttpGet("~/discovery")] [HttpGet("~/matchmaking")] [HttpGet("~/storage")] [HttpGet("~/chat")] [HttpGet("~/geo")] [HttpGet("~/leaderboard")] [HttpGet("~/accounts")] [HttpGet("~/link")] [HttpGet("~/locker")] [HttpGet("~/moderation")] [HttpGet("~/roomcomments")] [HttpGet("~/roomieintegrations")] [HttpGet("~/clubs")] [HttpGet("~/rooms")] [HttpGet("~/econ")] [HttpGet("~/bugreporting")] [HttpGet("~/cms")] [HttpGet("~/gamelogs")] [HttpGet("~/platformnotifications")] [HttpGet("~/stringscdn")] [HttpGet("~/strings")] [HttpGet("~/lists")] [HttpGet("~/studio")] [HttpGet("~/studiocdn")] [HttpGet("~/thorn")] [HttpGet("~/videos")] [AllowAnonymous] public Task GetServiceStatus() { var service = (Request.Path.Value ?? string.Empty).Trim('/').ToLowerInvariant(); var notifyService = service == "notify"; return ServiceRootHealth.CheckAsync(this, service, dbFactory, notifyService); } } internal static class ServiceRootHealth { public static async Task CheckAsync( ControllerBase controller, string service, IDbContextFactory? dbFactory, bool requireNotifyHub = false) { if (requireNotifyHub && Notify.Context is null) return Unavailable(controller, service, "NOTIFY_HUB_UNAVAILABLE"); var effectiveFactory = requireNotifyHub ? Notify.DbFactory : dbFactory; if (effectiveFactory is null) return Unavailable(controller, service, $"{ErrorPrefix(service)}_DATABASE_NOT_CONFIGURED"); try { await using var db = await effectiveFactory.CreateDbContextAsync(); if (!await db.Database.CanConnectAsync()) return Unavailable(controller, service, $"{ErrorPrefix(service)}_DATABASE_UNAVAILABLE"); } catch { return Unavailable(controller, service, $"{ErrorPrefix(service)}_DATABASE_UNAVAILABLE"); } return controller.Ok(new { service, status = "ok" }); } private static IActionResult Unavailable(ControllerBase controller, string service, string errorCode) => controller.StatusCode(StatusCodes.Status503ServiceUnavailable, new { service, status = errorCode }); private static string ErrorPrefix(string service) => service.ToUpperInvariant(); }