Files
QuantEngineByItz/src/dotnet/QuantEngine.Web/Program.cs
T

186 lines
6.5 KiB
C#

using QuantEngine.Web.Components;
using QuantEngine.Infrastructure.Data;
using Microsoft.AspNetCore.Components.Authorization;
using QuantEngine.Web.Infrastructure;
using QuantEngine.Infrastructure.Repositories;
using QuantEngine.Infrastructure.Services;
using QuantEngine.Core.Interfaces;
using QuantEngine.Application.Services;
using System.Text.Json;
using static QuantEngine.Application.Services.DataCollectionService;
using Microsoft.FluentUI.AspNetCore.Components;
using Serilog;
using QuantEngine.Web.Client.Infrastructure;
using QuantEngine.Web.Client.Services;
using QuantEngine.Web.Endpoints;
// Serilog Configuration with Telegram Sink
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Console()
.WriteTo.Sink(new TelegramSink("8734507814:AAFyacLMai8GB4K-hQ_Nd3t3D01A-h1ZdV0", "-5460205872"))
.CreateLogger();
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
// Authentication and Custom State Provider (Shared client components)
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<LocalStorageService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();
// Fluent UI Services
builder.Services.AddFluentUIComponents();
// PostgreSQL Dapper Setup
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=127.0.0.1;Database=giteadb;Username=gitea;Password=C8RFlZ9fdQrBA1vyLhLDS4v70I8dJfRS2ERJW4+zsS4=;Search Path=quantengine;";
builder.Services.AddSingleton<IDbConnectionFactory>(new DbConnectionFactory(connectionString));
builder.Services.AddScoped<IWorkspaceRepository, WorkspaceRepository>();
builder.Services.AddScoped<IPostgresqlHistoryStore, PostgresqlHistoryStore>();
builder.Services.AddScoped<IPostgresqlHistorySnapshotReader, PostgresqlHistorySnapshotReader>();
builder.Services.AddScoped<HistoryIngestionService>();
// Collection Pipeline Services (PostgreSQL-backed implementations)
builder.Services.AddScoped<ICollectionRepository, CollectionRepository>();
builder.Services.AddScoped<ITokenCache, PostgresTokenCache>();
builder.Services.AddScoped<IKisApiClient, KisApiClient>();
builder.Services.AddScoped<DataCollectionService>();
// HTTP Client & API Services
builder.Services.AddHttpClient<ApiClient>();
builder.Services.AddScoped<ApiClient>();
var app = builder.Build();
// Initialize database tables (PostgreSQL-backed repositories)
using (var scope = app.Services.CreateScope())
{
var tokenCache = scope.ServiceProvider.GetRequiredService<ITokenCache>();
var collectionRepo = scope.ServiceProvider.GetRequiredService<ICollectionRepository>();
try
{
// Ensure tables exist on startup
await tokenCache.GetCachedTokenAsync("_init_test_");
await collectionRepo.GetDashboardStateAsync();
Log.Information("Database tables initialized successfully");
}
catch (Exception ex)
{
Log.Warning($"Database initialization warning: {ex.Message}");
}
}
// Enable reverse proxy subpath mapping
app.UsePathBase("/quant");
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}
// Redirect status code pages only for non-API routes
app.UseStatusCodePages(async ctx =>
{
if (!ctx.HttpContext.Request.Path.StartsWithSegments("/api"))
ctx.HttpContext.Response.Redirect("/not-found");
});
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
// Collection API Endpoints (must be before MapRazorComponents)
app.MapCollectionEndpoints();
// Login API (API-First for Blazor WASM client authentication)
app.MapPost("/api/auth/login", (LoginRequest request, IConfiguration config) =>
{
var expectedUser = config["AdminSettings:Username"] ?? "admin";
var expectedPass = config["AdminSettings:Password"] ?? "quant123!";
if (request.Username == expectedUser && request.Password == expectedPass)
{
return Results.Ok(new { success = true, username = request.Username });
}
return Results.Json(new { success = false, error = "invalid_credentials" }, statusCode: 401);
});
// Operational Report serving API (WASM safe file loading substitute)
app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
{
var path = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
if (!File.Exists(path))
{
return Results.NotFound(new { gate = "FAIL", error = "operational_report_missing" });
}
var json = await File.ReadAllTextAsync(path);
using var doc = JsonDocument.Parse(json);
return Results.Ok(doc.RootElement);
});
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
{
var rows = await reader.ReadAsync(domain, limit ?? 500);
return Results.Ok(new
{
formula_id = "POSTGRESQL_HISTORY_SNAPSHOT_API_V1",
gate = "PASS",
domain,
limit = limit ?? 500,
rows
});
});
app.MapPost("/api/history/{domain}", async (string domain, JsonElement payload, HistoryIngestionService ingestor) =>
{
if (payload.ValueKind != JsonValueKind.Object)
{
return Results.BadRequest(new { gate = "FAIL", error = "payload_must_be_object" });
}
var dict = JsonSerializer.Deserialize<Dictionary<string, object?>>(payload.GetRawText())
?? new Dictionary<string, object?>();
var affected = domain switch
{
"decision_result_history" => await ingestor.AppendDecisionAsync(dict),
"factor_output_history" => await ingestor.AppendFactorOutputAsync(dict),
"market_raw_history" => await ingestor.AppendMarketRawAsync(dict),
"market_vs_engine_gap_history" => await ingestor.AppendGapAsync(dict),
_ => -1
};
if (affected < 0)
{
return Results.BadRequest(new { gate = "FAIL", error = "unsupported_domain" });
}
return Results.Ok(new
{
formula_id = "POSTGRESQL_HISTORY_APPEND_API_V1",
gate = "PASS",
domain,
affected
});
});
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);
app.Run();
public class LoginRequest
{
public string Username { get; set; } = "";
public string Password { get; set; } = "";
}