Files
2026-03-24 15:26:03 -05:00

396 lines
17 KiB
C#

using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using MassTransit;
using LCU.Library.Internal.Configuration;
using LCU.Library.Internal.LcuMassTransit;
using LCU.Library.Internal.LcuMassTransitModels;
using LCU.Library.Internal.Logging;
using LCU.Library.Internal.Logging.Middleware;
using LCU.Library.Internal.Logging.Models;
using LCU.Api.Internal.Dispatcher.Configurations;
using LCU.Api.Internal.Dispatcher.Consumers;
using LCU.Api.Internal.Dispatcher.HostedServices;
using LCU.Api.Internal.Dispatcher.Models;
using LCU.Api.Internal.Dispatcher.Services;
using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices;
using LCU.Api.Internal.Dispatcher.Services.QueueServices;
using LCU.Api.Internal.DispatcherLib.Persistence;
using LCU.Api.Internal.DispatcherLib.Repositories;
using LCU.Api.Internal.DispatcherSharedLib;
using System.Collections.Generic;
using LCU.Library.Internal.ApiSecurity.Constants;
using LCU.Library.Internal.ApiSecurity;
using LCU.Library.Internal.LcuHttpClient;
using LCU.Api.Internal.Dispatcher.DataAccess;
string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
ApiConfiguration _apiConfiguration;
var builder = BuildBuilder(args);
BuildServicesAndConfigurations(builder);
BuildAppAndRun(builder);
WebApplicationBuilder BuildBuilder(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("/app/config/environment-appsettings/appsettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/app/config/workload-appsettings/appsettings.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-database-connection.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-seq-api-key.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-rabbitmq-username.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-rabbitmq-password.json", optional: true, reloadOnChange: true);
builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-external-api-key-set.json", optional: true, reloadOnChange: true);
builder.Host.UseSerilog();
return builder;
}
void BuildServicesAndConfigurations(WebApplicationBuilder builder)
{
var services = builder.Services;
var configuration = builder.Configuration;
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddHttpClient();
_apiConfiguration = configuration.Bind<ApiConfiguration>(services, "Primary");
var connectionsConfiguration = configuration.Bind<ConnectionsConfiguration>(services);
var loggerConfiguration = new LoggingConfiguration(_apiConfiguration.AppName,
_apiConfiguration.SeqApiKey,
_apiConfiguration.SeqUrl);
var lcuLogging = new LcuLogging(loggerConfiguration);
lcuLogging.UseLcuLogging();
var connectionConfiguration = MassTransitConfigurationSetUp.AddConnectionConfiguration(ref services, configuration);
//debugging
Console.WriteLine(JsonConvert.SerializeObject(connectionConfiguration));
var receiveEndpointConfigurations = new ReceiveEndpointConfigurations()
{
ExecutionReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration,
"ExecutionReceiveEndpoint"),
CompletionReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration,
"CompletionReceiveEndpoint"),
DetermineOperationReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration,
"DetermineOperationReceiveEndpoint"),
JobFailureReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration,
"JobFailureReceiveEndpoint"),
SendErroredJobAlertReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration,
"SendErroredJobAlertReceiveEndpoint")
};
services.AddSingleton(receiveEndpointConfigurations);
//debugging
Console.WriteLine(JsonConvert.SerializeObject(receiveEndpointConfigurations));
services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins(
_apiConfiguration.CorsAllowedOrigins
).AllowAnyHeader().AllowAnyMethod();
});
});
services.AddDbContextPool<AppDbContext>(options =>
{
options.UseSqlServer(
connectionsConfiguration.Sql.DispatcherDb,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: connectionsConfiguration.Sql.SQLOptions.MaxRetryCount,
maxRetryDelay: connectionsConfiguration.Sql.SQLOptions.MaxRetryDelay,
errorNumbersToAdd: connectionsConfiguration.Sql.SQLOptions.ErrorNumbersToAdd
);
sqlOptions.CommandTimeout(connectionsConfiguration.Sql.SQLOptions.CommandTimeout);
}
);
options.EnableDetailedErrors(connectionsConfiguration.Sql.SQLOptions.EnableDetailedErrors);
});
AddMassTransitServices(ref services, connectionConfiguration, receiveEndpointConfigurations, _apiConfiguration);
services.AddDispatcherServices(configuration);
services.AddLCUHttpServices();
AddServices(services);
AddRepositories(services);
AddHostedServices(services);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = $"{_apiConfiguration.AppName}", Version = "v1" });
//Add Security Entry Fields In Swagger
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = HeaderKeys.ApiKey, // Must match the "AddSecurityDefinition"
Type = ReferenceType.SecurityScheme
}
},
new List<string>()
}
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = HeaderKeys.Client, // Must match the "AddSecurityDefinition"
Type = ReferenceType.SecurityScheme
}
},
new List<string>()
}
});
c.AddSecurityDefinition(HeaderKeys.ApiKey,
new OpenApiSecurityScheme
{
Description = HeaderKeys.ApiKey,
In = ParameterLocation.Header,
Name = HeaderKeys.ApiKey,
Type = SecuritySchemeType.ApiKey,
Scheme = "apikey"
});
c.AddSecurityDefinition(HeaderKeys.Client,
new OpenApiSecurityScheme
{
Description = HeaderKeys.Client,
In = ParameterLocation.Header,
Name = HeaderKeys.Client,
Type = SecuritySchemeType.ApiKey,
Scheme = "apikey"
});
});
builder.Services.AddApiKeySecurity(builder.Configuration);
}
void AddMassTransitServices(ref IServiceCollection services,
ConnectionConfiguration connectionConfiguration,
ReceiveEndpointConfigurations receiveEndpointConfigurations,
ApiConfiguration apiConfiguration)
{
services.AddMassTransit(x =>
{
x.AddConsumer<ExecutionConsumer>();
x.AddConsumer<CompletionConsumer>();
x.AddConsumer<DetermineOperationConsumer>();
x.AddConsumer<JobFailureConsumer>();
x.AddConsumer<SendErroredJobAlertConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host(connectionConfiguration.HostUrl, "/", host =>
{
host.Username(connectionConfiguration.UserName);
host.Password(connectionConfiguration.Password);
});
if (apiConfiguration.FeatureFlags.RunExecutionConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.ExecutionReceiveEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<ExecutionConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.CompletionReceiveEndpoint.PrefetchLimit;
});
}
if (apiConfiguration.FeatureFlags.RunCompletionConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.CompletionReceiveEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<CompletionConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.CompletionReceiveEndpoint.PrefetchLimit;
});
}
if (apiConfiguration.FeatureFlags.RunDetermineOperationConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.DetermineOperationReceiveEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<DetermineOperationConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.DetermineOperationReceiveEndpoint.PrefetchLimit;
});
}
if (apiConfiguration.FeatureFlags.RunJobFailureConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.JobFailureReceiveEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<JobFailureConsumer>(context);
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.JobFailureReceiveEndpoint.PrefetchLimit;
});
}
if (apiConfiguration.FeatureFlags.RunSendErroredJobAlertConsumer)
{
cfg.ReceiveEndpoint(receiveEndpointConfigurations.SendErroredJobAlertReceiveEndpoint.ReceiveEndpoint, e =>
{
e.ConfigureConsumer<SendErroredJobAlertConsumer>(context, cons =>
{
cons.Options<BatchOptions>(options => options
.SetMessageLimit(200)
.SetTimeLimit(5000)
.SetConcurrencyLimit(1));
});
e.SetQuorumQueue();
e.PrefetchCount = receiveEndpointConfigurations.SendErroredJobAlertReceiveEndpoint.PrefetchLimit;
});
}
});
});
services.AddMassTransitHostedService();
}
void AddServices(IServiceCollection services)
{
services.AddScoped<IEnvironmentView, EnvironmentView>();
AddDispatcherOperationServices(services);
AddQueueServices(services);
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobTagService, JobTagService>();
services.AddScoped<ITagService, TagService>();
services.AddScoped<IWorkItemService, WorkItemService>();
services.AddScoped<IWorkItemExecutionService, WorkItemExecutionService>();
services.AddScoped<IExecutionMessageService, ExecutionMessageService>();
services.AddScoped<IJobErrorRecipientService, JobErrorRecipientService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<ICleanUpJobSettingService, CleanUpJobSettingService>();
services.AddScoped<ICleanUpJobTagService, CleanUpJobTagService>();
}
void AddDispatcherOperationServices(IServiceCollection services)
{
services.AddScoped<IJobScheduleService, JobScheduleService>();
services.AddScoped<IBatchErroredJobsService, BatchErroredJobsService>();
services.AddScoped<IIngestionService, IngestionService>();
services.AddScoped<IExecutionService, ExecutionService>();
services.AddScoped<ICompletionService, CompletionService>();
services.AddScoped<ICleanUpJobsService, CleanUpJobsService>();
services.AddScoped<IDetermineOperationService, DetermineOperationService >();
services.AddScoped<IJobFailureService, JobFailureService >();
services.AddScoped<ISendErroredJobAlertService, SendErroredJobAlertService >();
}
void AddQueueServices(IServiceCollection services)
{
services.AddScoped<IQueueExecutionService, QueueExecutionService>();
services.AddScoped<IQueueCompletionService, QueueCompletionService>();
services.AddScoped<IQueueSendErroredJobAlertService, QueueSendErroredJobAlertService>();
services.AddScoped<IQueueDetermineOperationService, QueueDetermineOperationService>();
services.AddScoped<IQueueJobFailureService, QueueJobFailureService>();
}
void AddRepositories(IServiceCollection services)
{
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IJobRepository, JobRepository>();
services.AddScoped<IJobErrorRecipientRepository, JobErrorRecipientRepository>();
services.AddScoped<IJobTagRepository, JobTagRepository>();
services.AddScoped<ITagRepository, TagRepository>();
services.AddScoped<IWorkItemRepository, WorkItemRepository>();
services.AddScoped<IWorkItemExecutionRepository, WorkItemExecutionRepository>();
services.AddScoped<IExecutionMessageRepository, ExecutionMessageRepository>();
services.AddScoped<ICleanUpJobSettingRepository, CleanUpJobSettingRepository>();
services.AddScoped<ICleanUpJobTagRepository, CleanUpJobTagRepository>();
services.AddScoped<INotificationsApiDao, NotificationsApiDao>();
}
void AddHostedServices(IServiceCollection services)
{
services.AddSingleton<ICleanUpJobsHostedService, CleanUpJobsHostedService>();
services.AddSingleton<IBatchErroredJobsHostedService, BatchErroredJobsHostedService>();
services.AddSingleton<IJobSchedulerHostedService, JobSchedulerHostedService>();
}
void BuildAppAndRun(WebApplicationBuilder builder)
{
var app = builder.Build();
if (_apiConfiguration.FeatureFlags.RunCleanUpJobsHostedService)
{
var service = app.Services.GetRequiredService<ICleanUpJobsHostedService>();
service.StartAsync().Wait();
}
if (_apiConfiguration.FeatureFlags.RunJobSchedulerHostedService)
{
var service = app.Services.GetRequiredService<IJobSchedulerHostedService>();
service.StartAsync().Wait();
}
if (_apiConfiguration.FeatureFlags.RunBatchErroredJobsHostedService)
{
var service = app.Services.GetRequiredService<IBatchErroredJobsHostedService>();
service.StartAsync().Wait();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swaggerDoc, request) =>
{
const string prefixHeader = "X-Forwarded-Prefix";
if (request.Headers.ContainsKey(prefixHeader)) return;
var serverUrl = request.Headers[prefixHeader];
if (!String.IsNullOrWhiteSpace(serverUrl))
{
swaggerDoc.Servers = new List<OpenApiServer>()
{
new() { Description = serverUrl, Url = serverUrl }
};
}
});
});
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger";
c.SwaggerEndpoint("v1/swagger.json", "LCU.Api.Internal.Dispatcher v1");
});
app.UseMiddleware<RequestResponseLoggingMiddleware>();
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
});
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.Run();
}