using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Serilog;
using ZB.MOM.WW.MxGateway.Contracts;
using ZB.MOM.WW.MxGateway.Server.Alarms;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
using ZB.MOM.WW.MxGateway.Server.Diagnostics;
using ZB.MOM.WW.MxGateway.Server.Galaxy;
using ZB.MOM.WW.MxGateway.Server.Grpc;
using ZB.MOM.WW.MxGateway.Server.Metrics;
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
using ZB.MOM.WW.MxGateway.Server.Sessions;
using ZB.MOM.WW.MxGateway.Server.Workers;
using ZB.MOM.WW.Telemetry.Serilog;
namespace ZB.MOM.WW.MxGateway.Server;
///
/// Configures and builds the gateway web application.
///
public static class GatewayApplication
{
private const string StaticAssetsManifestFileName = "ZB.MOM.WW.MxGateway.Server.staticwebassets.endpoints.json";
///
/// Builds a configured web application with all gateway services and middleware.
///
/// Command-line arguments passed to the application.
/// A configured web application ready to run.
public static WebApplication Build(string[] args)
{
WebApplicationBuilder builder = CreateBuilder(args);
WebApplication app = builder.Build();
// Push the per-request correlation properties (via Serilog LogContext) before the
// request-logging middleware emits its completion event, so those properties appear on it.
app.UseGatewayRequestLoggingScope();
app.UseSerilogRequestLogging();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.MapGatewayEndpoints();
return app;
}
///
/// Creates a web application builder configured with gateway services.
///
/// Command-line arguments passed to the application.
/// A configured web application builder.
public static WebApplicationBuilder CreateBuilder(string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ContentRootPath = ResolveContentRootPath(),
});
StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration);
ConfigureSerilog(builder);
builder.Services.AddGatewayConfiguration();
builder.Services.AddSqliteAuthStore();
builder.Services.AddGatewayGrpcAuthorization();
builder.Services.AddHealthChecks();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddWorkerProcessLauncher();
builder.Services.AddGatewaySessions();
builder.Services.AddGatewayAlarms();
builder.Services.AddGatewayDashboard();
builder.Services.AddGalaxyRepository();
return builder;
}
///
/// Replaces the default Microsoft.Extensions.Logging provider with the shared
/// ZB.MOM.WW.Telemetry.Serilog bootstrap ().
/// Sinks and minimum level come from the Serilog configuration section; identity
/// (SiteId/NodeRole) is read from MxGateway:Telemetry when present.
/// Also registers the project's adapter so the shared redaction
/// enricher masks gateway secrets on every event.
///
/// The web application builder being configured.
private static void ConfigureSerilog(WebApplicationBuilder builder)
{
string? siteId = builder.Configuration["MxGateway:Telemetry:SiteId"];
string? nodeRole = builder.Configuration["MxGateway:Telemetry:NodeRole"];
builder.Services.AddSingleton();
builder.AddZbSerilog(options =>
{
options.ServiceName = "mxgateway";
options.SiteId = string.IsNullOrWhiteSpace(siteId) ? null : siteId;
options.NodeRole = string.IsNullOrWhiteSpace(nodeRole) ? null : nodeRole;
});
}
private static string ResolveContentRootPath()
{
string? configuredContentRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT");
if (!string.IsNullOrWhiteSpace(configuredContentRootPath)
&& IsServerContentRoot(configuredContentRootPath))
{
return configuredContentRootPath;
}
string currentDirectory = Environment.CurrentDirectory;
if (IsServerContentRoot(currentDirectory))
{
return currentDirectory;
}
string baseDirectory = AppContext.BaseDirectory;
if (IsServerContentRoot(baseDirectory))
{
return baseDirectory;
}
string? discoveredContentRootPath = DiscoverServerContentRoot(currentDirectory)
?? DiscoverServerContentRoot(baseDirectory);
return discoveredContentRootPath ?? baseDirectory;
}
private static string? DiscoverServerContentRoot(string startPath)
{
DirectoryInfo? directory = new(startPath);
while (directory is not null)
{
if (IsServerContentRoot(directory.FullName))
{
return directory.FullName;
}
string serverProjectPath = Path.Combine(directory.FullName, "src", "ZB.MOM.WW.MxGateway.Server");
if (IsServerContentRoot(serverProjectPath))
{
return serverProjectPath;
}
directory = directory.Parent;
}
return null;
}
private static bool IsServerContentRoot(string path)
{
return File.Exists(Path.Combine(path, "appsettings.json"))
&& Directory.Exists(Path.Combine(path, "wwwroot"));
}
///
/// Maps gateway endpoints including gRPC services, health checks, and the dashboard.
///
/// Endpoint route builder to map endpoints to.
/// The same endpoint route builder for chaining.
public static IEndpointRouteBuilder MapGatewayEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapStaticAssets(ResolveStaticAssetsManifestPath());
endpoints.MapGet(
"/health/live",
() => Results.Ok(new GatewayHealthReply(
Status: "Healthy",
DefaultBackend: GatewayContractInfo.DefaultBackendName,
WorkerProtocolVersion: GatewayContractInfo.WorkerProtocolVersion)))
.WithName("LiveHealth");
endpoints.MapGrpcService();
endpoints.MapGrpcService();
endpoints.MapGatewayDashboard();
return endpoints;
}
private static string ResolveStaticAssetsManifestPath()
{
string manifestPath = Path.Combine(AppContext.BaseDirectory, StaticAssetsManifestFileName);
return File.Exists(manifestPath) ? manifestPath : StaticAssetsManifestFileName;
}
}