using Microsoft.EntityFrameworkCore; using Asp.Versioning; using FluentValidation; using Hellang.Middleware.ProblemDetails; using AdsAnalyticsService.API.Application.Behaviors; using AdsAnalyticsService.Infrastructure; using Serilog; // EN: Configure Serilog early / VI: Cấu hình Serilog sớm Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateBootstrapLogger(); try { Log.Information("Starting AdsAnalyticsService API / Khởi động AdsAnalyticsService API"); var builder = WebApplication.CreateBuilder(args); // EN: Configure Serilog / VI: Cấu hình Serilog builder.Host.UseSerilog((context, services, configuration) => configuration .ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() .WriteTo.Console()); // EN: Add Infrastructure services / VI: Thêm Infrastructure services builder.Services.AddInfrastructure(builder.Configuration); // EN: Add MediatR with behaviors / VI: Thêm MediatR với behaviors builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssemblyContaining(); cfg.AddOpenBehavior(typeof(LoggingBehavior<,>)); cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>)); cfg.AddOpenBehavior(typeof(TransactionBehavior<,>)); }); // EN: Add FluentValidation / VI: Thêm FluentValidation builder.Services.AddValidatorsFromAssemblyContaining(); // EN: Add API versioning / VI: Thêm API versioning builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; options.ApiVersionReader = ApiVersionReader.Combine( new UrlSegmentApiVersionReader(), new HeaderApiVersionReader("X-Api-Version")); }) .AddApiExplorer(options => { options.GroupNameFormat = "'v'VVV"; options.SubstituteApiVersionInUrl = true; }); // EN: Add controllers / VI: Thêm controllers builder.Services.AddControllers(); // EN: Add ProblemDetails middleware (RFC 7807) / VI: Thêm ProblemDetails middleware builder.Services.AddProblemDetails(options => { options.IncludeExceptionDetails = (ctx, ex) => builder.Environment.IsDevelopment(); }); // EN: Add Swagger / VI: Thêm Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new() { Title = "AdsAnalyticsService API", Version = "v1", Description = "AdsAnalyticsService microservice API / API microservice AdsAnalyticsService" }); }); // EN: Add health checks / VI: Thêm health checks builder.Services.AddHealthChecks() .AddNpgSql( builder.Configuration.GetConnectionString("DefaultConnection") ?? builder.Configuration["DATABASE_URL"] ?? "", name: "postgresql", tags: ["db", "postgresql"]); // EN: Add JWT Bearer authentication via IAM IdentityServer OIDC discovery // VI: Thêm JWT Bearer authentication qua IAM IdentityServer OIDC discovery var jwtAuthority = builder.Configuration["Jwt:Authority"] ?? "http://localhost:5001"; builder.Services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = jwtAuthority; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, // EN: In Development, skip signature validation to allow Docker IAM tokens // VI: Trong Development, bỏ qua validate signature để chấp nhận token từ Docker IAM ValidateIssuerSigningKey = builder.Environment.IsDevelopment() ? false : true, SignatureValidator = builder.Environment.IsDevelopment() ? (token, _) => new Microsoft.IdentityModel.JsonWebTokens.JsonWebToken(token) : null, }; }); builder.Services.AddAuthorization(); // EN: Add CORS / VI: Thêm CORS builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); // EN: Configure middleware pipeline / VI: Cấu hình middleware pipeline app.UseSerilogRequestLogging(); app.UseProblemDetails(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "AdsAnalyticsService API v1"); c.RoutePrefix = "swagger"; }); } app.UseCors(); app.UseRouting(); // EN: Add authentication & authorization middleware / VI: Thêm middleware xác thực & phân quyền app.UseAuthentication(); app.UseAuthorization(); // EN: Map health check endpoints (anonymous) / VI: Map health check endpoints (không cần xác thực) app.MapHealthChecks("/health").AllowAnonymous(); app.MapHealthChecks("/health/live", new() { Predicate = _ => false // EN: Just checks app is running / VI: Chỉ kiểm tra app đang chạy }).AllowAnonymous(); app.MapHealthChecks("/health/ready").AllowAnonymous(); // EN: Map controllers / VI: Map controllers app.MapControllers(); // EN: Run the application / VI: Chạy ứng dụng // EN: Auto-apply EF Core migrations on startup // VI: Tự động áp dụng EF Core migrations khi khởi động try { using (var scope = app.Services.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(); } } catch (Exception ex) { // EN: Log migration errors but don't crash the app (e.g. PendingModelChangesWarning in EF Core 10) // VI: Log lỗi migration nhưng không crash app Console.WriteLine($"Warning: EF Core migration issue: {ex.Message}"); } app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly / Ứng dụng kết thúc bất ngờ"); throw; } finally { Log.CloseAndFlush(); } // EN: Make Program class accessible for integration tests // VI: Làm cho class Program có thể truy cập cho integration tests public partial class Program { }