BACK-W-02: Replace string-interpolated SET LOCAL SQL with parameterized set_config() calls in TenantMiddleware across 5 services (order, wallet, inventory, catalog, fnb-engine). Eliminates SQL injection pattern; set_config(key, $1, true) is local-to-transaction, same semantics as SET LOCAL. BACK-C-01: Remove AllowAnyOrigin() from all 26 services. Switch to WithOrigins() reading AllowedOrigins config array, with dev-only fallback to localhost. In production, set AllowedOrigins=["https://goodgo.vn", "https://admin.goodgo.vn"] via environment config. BACK-C-03: Standardize OrdersController GET /orders/{id} 404 response from {Message:...} to {success:false, error:{code,message}} per API contract. BACK-C-04: Add complete ProblemDetails exception mappings to _template_dot_net: ValidationException -> 400, DomainException -> 422, with TODO comments for service-specific types (EntityNotFoundException -> 404, etc.). BACK-C-02: wallet-service and booking-service already have full IRequestManager idempotency implementation — no changes needed. Co-Authored-By: Paperclip <noreply@paperclip.ing>
207 lines
7.3 KiB
C#
207 lines
7.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Asp.Versioning;
|
|
using FluentValidation;
|
|
using Hellang.Middleware.ProblemDetails;
|
|
using System.Data;
|
|
using Npgsql;
|
|
using CatalogService.API.Application.Behaviors;
|
|
using CatalogService.API.Infrastructure.Tenant;
|
|
using CatalogService.API.Middleware;
|
|
using CatalogService.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 CatalogService API / Khởi động CatalogService 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: Register multi-tenant services for row-level security
|
|
// VI: Đăng ký multi-tenant services cho bảo mật row-level
|
|
builder.Services.AddHttpContextAccessor();
|
|
builder.Services.AddScoped<ITenantProvider, HttpContextTenantProvider>();
|
|
builder.Services.AddScoped<ICatalogTenantProvider, CatalogTenantProviderAdapter>();
|
|
|
|
// EN: Add Dapper IDbConnection for TenantMiddleware RLS / VI: Thêm Dapper IDbConnection cho TenantMiddleware RLS
|
|
builder.Services.AddTransient<IDbConnection>(sp =>
|
|
{
|
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
|
?? builder.Configuration["DATABASE_URL"]
|
|
?? throw new InvalidOperationException("Database connection string is required");
|
|
return new NpgsqlConnection(connectionString);
|
|
});
|
|
|
|
// EN: Add MediatR with behaviors / VI: Thêm MediatR với behaviors
|
|
builder.Services.AddMediatR(cfg =>
|
|
{
|
|
cfg.RegisterServicesFromAssemblyContaining<Program>();
|
|
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
|
|
cfg.AddOpenBehavior(typeof(ValidatorBehavior<,>));
|
|
cfg.AddOpenBehavior(typeof(TransactionBehavior<,>));
|
|
});
|
|
|
|
// EN: Add FluentValidation / VI: Thêm FluentValidation
|
|
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
|
|
|
|
// 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 = "CatalogService API",
|
|
Version = "v1",
|
|
Description = "CatalogService microservice API / API microservice CatalogService"
|
|
});
|
|
});
|
|
|
|
// 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,
|
|
};
|
|
});
|
|
builder.Services.AddAuthorization();
|
|
|
|
// EN: Add CORS / VI: Thêm CORS
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddDefaultPolicy(policy =>
|
|
{
|
|
policy.WithOrigins(
|
|
builder.Configuration.GetSection("AllowedOrigins").Get<string[]>()
|
|
?? ["http://localhost:3000", "http://localhost:5173", "http://localhost:5000"])
|
|
.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", "CatalogService API v1");
|
|
c.RoutePrefix = "swagger";
|
|
});
|
|
}
|
|
|
|
app.UseCors();
|
|
app.UseRouting();
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
// EN: Set tenant context for row-level security (must be after auth)
|
|
// VI: Đặt tenant context cho bảo mật row-level (phải sau auth)
|
|
app.UseTenantMiddleware();
|
|
|
|
// EN: Map health check endpoints / VI: Map health check endpoints
|
|
app.MapHealthChecks("/health");
|
|
app.MapHealthChecks("/health/live", new()
|
|
{
|
|
Predicate = _ => false // EN: Just checks app is running / VI: Chỉ kiểm tra app đang chạy
|
|
});
|
|
app.MapHealthChecks("/health/ready");
|
|
|
|
// 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<CatalogContext>();
|
|
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 { }
|