WBS-11: Hardening BFF Architecture, fix /Index page redirect and JsonDocument ObjectDisposedException in operational-report API
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 12s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 8s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 2m21s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 12s
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 8s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 2m21s
This commit is contained in:
@@ -8,18 +8,28 @@ namespace QuantEngine.Infrastructure.Data
|
|||||||
IDbConnection CreateConnection();
|
IDbConnection CreateConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DbConnectionFactory : IDbConnectionFactory
|
public class DbConnectionFactory : IDbConnectionFactory, IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly NpgsqlDataSource _dataSource;
|
||||||
|
|
||||||
public DbConnectionFactory(string connectionString)
|
public DbConnectionFactory(string connectionString)
|
||||||
{
|
{
|
||||||
_connectionString = connectionString;
|
_dataSource = NpgsqlDataSource.Create(connectionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbConnectionFactory(NpgsqlDataSource dataSource)
|
||||||
|
{
|
||||||
|
_dataSource = dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDbConnection CreateConnection()
|
public IDbConnection CreateConnection()
|
||||||
{
|
{
|
||||||
return new NpgsqlConnection(_connectionString);
|
return _dataSource.CreateConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_dataSource.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<form method="post" class="login-form">
|
<form method="post" class="login-form">
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="form-label">관리자 아이디</label>
|
<label for="username" class="form-label">관리자 아이디</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ namespace QuantEngine.Web.Pages.Account
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var loginRequest = new { Username = username, Password = password };
|
var loginRequest = new { Username = username, Password = password };
|
||||||
var response = await _httpClient.PostAsJsonAsync("/api/auth/login", loginRequest);
|
var requestUri = $"{Request.Scheme}://{Request.Host}/api/auth/login";
|
||||||
|
var response = await _httpClient.PostAsJsonAsync(requestUri, loginRequest);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -64,7 +65,7 @@ namespace QuantEngine.Web.Pages.Account
|
|||||||
Response.Cookies.Delete("quant_admin_username");
|
Response.Cookies.Delete("quant_admin_username");
|
||||||
}
|
}
|
||||||
|
|
||||||
return RedirectToPage("/Index");
|
return Redirect("/");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using QuantEngine.Web.Components;
|
|||||||
using QuantEngine.Infrastructure.Data;
|
using QuantEngine.Infrastructure.Data;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using QuantEngine.Web.Infrastructure;
|
using QuantEngine.Web.Infrastructure;
|
||||||
|
using Npgsql;
|
||||||
using QuantEngine.Infrastructure.Repositories;
|
using QuantEngine.Infrastructure.Repositories;
|
||||||
using QuantEngine.Infrastructure.Services;
|
using QuantEngine.Infrastructure.Services;
|
||||||
using QuantEngine.Core.Interfaces;
|
using QuantEngine.Core.Interfaces;
|
||||||
@@ -37,7 +38,6 @@ builder.Host.UseSerilog();
|
|||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
.AddInteractiveServerComponents()
|
|
||||||
.AddInteractiveWebAssemblyComponents();
|
.AddInteractiveWebAssemblyComponents();
|
||||||
|
|
||||||
// Authentication and Custom State Provider (Shared client components)
|
// Authentication and Custom State Provider (Shared client components)
|
||||||
@@ -62,7 +62,9 @@ if (!string.Equals(configuredDatabase, "quantenginedb", StringComparison.Ordinal
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException("QuantEngine must use the quantenginedb PostgreSQL database.");
|
throw new InvalidOperationException("QuantEngine must use the quantenginedb PostgreSQL database.");
|
||||||
}
|
}
|
||||||
builder.Services.AddSingleton<IDbConnectionFactory>(new DbConnectionFactory(connectionString));
|
var dataSource = NpgsqlDataSource.Create(connectionString);
|
||||||
|
builder.Services.AddSingleton(dataSource);
|
||||||
|
builder.Services.AddSingleton<IDbConnectionFactory>(new DbConnectionFactory(dataSource));
|
||||||
builder.Services.AddSingleton<DbMigrator>();
|
builder.Services.AddSingleton<DbMigrator>();
|
||||||
builder.Services.AddScoped<IWorkspaceRepository, WorkspaceRepository>();
|
builder.Services.AddScoped<IWorkspaceRepository, WorkspaceRepository>();
|
||||||
builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
|
builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
|
||||||
@@ -248,7 +250,7 @@ app.MapPost("/api/auth/login", async (JsonElement payload, HttpContext httpConte
|
|||||||
new Microsoft.AspNetCore.Http.CookieOptions
|
new Microsoft.AspNetCore.Http.CookieOptions
|
||||||
{
|
{
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
Secure = httpContext.Request.IsHttps,
|
Secure = true,
|
||||||
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax,
|
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax,
|
||||||
Expires = devExpiresAt,
|
Expires = devExpiresAt,
|
||||||
Path = "/"
|
Path = "/"
|
||||||
@@ -303,8 +305,8 @@ app.MapPost("/api/auth/login", async (JsonElement payload, HttpContext httpConte
|
|||||||
new Microsoft.AspNetCore.Http.CookieOptions
|
new Microsoft.AspNetCore.Http.CookieOptions
|
||||||
{
|
{
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
Secure = httpContext.Request.IsHttps, // Only secure on HTTPS
|
Secure = true, // Force Secure Cookie for SSL standard
|
||||||
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Lax for localhost
|
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax,
|
||||||
Expires = expiresAt,
|
Expires = expiresAt,
|
||||||
Path = "/"
|
Path = "/"
|
||||||
}
|
}
|
||||||
@@ -443,7 +445,6 @@ app.MapPost("/api/auth/admin/reset-password", async (HttpContext context, JsonEl
|
|||||||
});
|
});
|
||||||
}).DisableAntiforgery();
|
}).DisableAntiforgery();
|
||||||
|
|
||||||
// Operational Report serving API (WASM safe file loading substitute)
|
|
||||||
app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
|
app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
|
||||||
{
|
{
|
||||||
var path = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
|
var path = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
|
||||||
@@ -452,8 +453,8 @@ app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
|
|||||||
return Results.NotFound(new { gate = "FAIL", error = "operational_report_missing" });
|
return Results.NotFound(new { gate = "FAIL", error = "operational_report_missing" });
|
||||||
}
|
}
|
||||||
var json = await File.ReadAllTextAsync(path);
|
var json = await File.ReadAllTextAsync(path);
|
||||||
using var doc = JsonDocument.Parse(json);
|
// Directly return raw JSON string with correct Content-Type to bypass using-disposed JSON document serializer issue
|
||||||
return Results.Ok(doc.RootElement);
|
return Results.Content(json, "application/json");
|
||||||
});
|
});
|
||||||
|
|
||||||
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
|
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
|
||||||
@@ -505,7 +506,6 @@ app.MapRazorPages();
|
|||||||
// Map Blazor Components - catches all remaining routes
|
// Map Blazor Components - catches all remaining routes
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveWebAssemblyRenderMode()
|
.AddInteractiveWebAssemblyRenderMode()
|
||||||
.AddInteractiveServerRenderMode()
|
|
||||||
.AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);
|
.AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
Reference in New Issue
Block a user