using BMA.EHR.Application; using BMA.EHR.Application.Repositories.Reports; using BMA.EHR.Domain.Middlewares; using BMA.EHR.Infrastructure; using BMA.EHR.Infrastructure.Persistence; using BMA.EHR.Insignia.Service; using BMA.EHR.Insignia.Service.Filters; using BMA.EHR.Insignia.Service.Services; using Hangfire; using Hangfire.Common; using Hangfire.MySql; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Logging; using Microsoft.IdentityModel.Tokens; using Serilog; using Serilog.Exceptions; using Serilog.Sinks.Elasticsearch; using System.Text; using System.Transactions; var builder = WebApplication.CreateBuilder(args); { var issuer = builder.Configuration["Jwt:Issuer"]; var key = builder.Configuration["Jwt:Key"]; IdentityModelEventSource.ShowPII = true; builder.Services.AddHttpContextAccessor(); builder.Services.AddApiVersioning(opt => { opt.DefaultApiVersion = new ApiVersion(1, 0); opt.AssumeDefaultVersionWhenUnspecified = true; opt.ReportApiVersions = true; opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), new HeaderApiVersionReader("x-api-version"), new MediaTypeApiVersionReader("x-api-version")); }); builder.Services.AddVersionedApiExplorer(setup => { setup.GroupNameFormat = "'v'VVV"; setup.SubstituteApiVersionInUrl = true; }); builder.Services.AddEndpointsApiExplorer(); // Authorization builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(opt => { opt.RequireHttpsMetadata = false; //false for dev opt.Authority = issuer; opt.TokenValidationParameters = new() { ValidateIssuer = true, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = issuer, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) }; }); builder.Services.AddAuthorization(); // use serilog //ConfigureLogs(); //builder.Host.UseSerilog(); // Add config CORS builder.Services.AddCors(options => options.AddDefaultPolicy(builder => { builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .SetIsOriginAllowedToAllowWildcardSubdomains(); })); // Add services to the container. builder.Services.AddApplication(); builder.Services.AddPersistence(builder.Configuration); builder.Services.AddLeaveApplication(); builder.Services.AddLeavePersistence(builder.Configuration); builder.Services.AddSingleton(); builder.Services.AddHostedService(); builder.Services.AddControllers(options => { options.SuppressAsyncSuffixInActionNames = false; }) .AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); builder.Services.AddSwaggerGen(); builder.Services.ConfigureOptions(); // Register DbContext var defaultConnection = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => options.UseMySql(defaultConnection, ServerVersion.AutoDetect(defaultConnection)), ServiceLifetime.Transient); builder.Services.AddHealthChecks(); // Add Hangfire services. builder.Services.AddHangfire(configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseStorage( new MySqlStorage( defaultConnection, new MySqlStorageOptions { TransactionIsolationLevel = IsolationLevel.ReadCommitted, QueuePollInterval = TimeSpan.FromSeconds(15), JobExpirationCheckInterval = TimeSpan.FromHours(1), CountersAggregateInterval = TimeSpan.FromMinutes(5), PrepareSchemaIfNecessary = true, DashboardJobListLimit = 50000, TransactionTimeout = TimeSpan.FromMinutes(1), InvisibilityTimeout = TimeSpan.FromHours(3), TablesPrefix = "Hangfire_Insignia" }))); builder.Services.AddHangfireServer(options => { options.ServerName = "Insignia-Server"; // ← ระบุชื่อ server options.WorkerCount = 5; // ← options.Queues = new[] { "insignia","default" }; // ← worker จะรันเฉพาะ queue "insignia" }); // RabbitMQ //builder.Services.AddTransient(provider => //{ // var serviceScopeFactory = provider.GetRequiredService(); // var userRepo = provider.GetRequiredService(); // var insigniaRepo = provider.GetRequiredService(); // var httpContext = provider.GetRequiredService(); // var config = provider.GetRequiredService(); // return new RabbitMQConsumer(userRepo, insigniaRepo, httpContext, serviceScopeFactory, config); //}); } var app = builder.Build(); { var apiVersionDescriptionProvider = app.Services.GetRequiredService(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) { options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); } }); } app.MapHealthChecks("/health"); app.UseHttpsRedirection(); app.UseCors(); app.UseMiddleware(); app.UseAuthentication(); app.UseAuthorization(); app.UseDefaultFiles(); app.UseStaticFiles(); app.MapControllers(); // app.UseMiddleware(); // app.UseMiddleware(); app.UseHangfireDashboard("/hangfire", new DashboardOptions() { Authorization = new[] { new CustomAuthorizeFilter() } }); var manager = new RecurringJobManager(); if (manager != null) { manager.AddOrUpdate("แจ้งเตือนรอบเครื่องราชฯ", Job.FromExpression(x => x.NotifyInsignia()), Cron.Daily(Int32.Parse(builder.Configuration["KeycloakCron:Hour"]), Int32.Parse(builder.Configuration["KeycloakCron:Minute"])), TimeZoneInfo.Local); manager.AddOrUpdate("ล็อกข้อมูลรอบเครื่องราชฯ", Job.FromExpression(x => x.LockInsignia()), Cron.Daily(Int32.Parse(builder.Configuration["KeycloakCron:Hour"]), Int32.Parse(builder.Configuration["KeycloakCron:Minute"])), TimeZoneInfo.Local); //manager.AddOrUpdate("คำนวนผู้ได้รับเครื่องราชฯ", () => CalculateInsigniaRequestBkkByType("officer"), Cron.Daily(Int32.Parse(builder.Configuration["KeycloakCron:Hour"]), Int32.Parse(builder.Configuration["KeycloakCron:Minute"])), TimeZoneInfo.Local); //manager.AddOrUpdate("คำนวนผู้ได้รับเครื่องราชฯ Employee", Job.FromExpression(x => x.CalInsigniaRequestBkkByType("employee")), Cron.Daily(Int32.Parse(builder.Configuration["KeycloakCron:Hour"]), Int32.Parse(builder.Configuration["KeycloakCron:Minute"])), TimeZoneInfo.Local); } RecurringJob.AddOrUpdate( "คำนวนผู้ได้รับเครื่องราชฯ", x => x.CalculateInsigniaRequestBkkByType("officer"), Cron.Daily(Int32.Parse(builder.Configuration["KeycloakCron:Hour"]) - 5, Int32.Parse(builder.Configuration["KeycloakCron:Minute"])), new RecurringJobOptions { TimeZone = TimeZoneInfo.Local, QueueName = "insignia" // ← กำหนด queue } ); RecurringJob.AddOrUpdate( "คำนวนผู้ได้รับเครื่องราชฯ Employee", x => x.CalculateInsigniaRequestBkkByType("employee"), Cron.Daily(Int32.Parse(builder.Configuration["KeycloakCron:Hour"]) - 4, Int32.Parse(builder.Configuration["KeycloakCron:Minute"])), new RecurringJobOptions { TimeZone = TimeZoneInfo.Local, QueueName = "insignia" // ← กำหนด queue } ); // apply migrations await using var scope = app.Services.CreateAsyncScope(); await using var db = scope.ServiceProvider.GetRequiredService(); await db.Database.MigrateAsync(); //var rabbitMQConsumer = app.Services.GetRequiredService(); //rabbitMQConsumer.StartReceiving(); app.Run(); } void ConfigureLogs() { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile( $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true) .Build(); Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.Error() .WriteTo.Console() .Enrich.WithExceptionDetails() .WriteTo.Elasticsearch(ConfigureElasticSink(configuration, environment ?? "")) .Enrich.WithProperty("Environment", environment) .ReadFrom.Configuration(configuration) .CreateLogger(); } ElasticsearchSinkOptions ConfigureElasticSink(IConfigurationRoot configuration, string environment) { return new ElasticsearchSinkOptions(new Uri(configuration["ElasticConfiguration:Uri"] ?? "")) { AutoRegisterTemplate = true, IndexFormat = "bma-ehr-log-index", //IndexFormat = $"{Assembly.GetExecutingAssembly()?.GetName()?.Name?.ToLower().Replace(".", "-")}-{environment?.ToLower().Replace(".", "-")}" }; }