using DongfangHydro.Dashboard.Api.Contracts; using DongfangHydro.Dashboard.Api.Data; using DongfangHydro.Dashboard.Api.Importing; using DongfangHydro.Dashboard.Api.Realtime; using DongfangHydro.Dashboard.Api.Services; using Microsoft.EntityFrameworkCore; using System.Text.Json; var importCommand = BomImportCommand.Parse(args); var builder = WebApplication.CreateBuilder(importCommand is null ? args : []); if (importCommand is not null) { builder.Configuration.AddUserSecrets(optional: true); } var connectionString = builder.Configuration.GetConnectionString("DashboardDb") ?? throw new InvalidOperationException("Connection string 'DashboardDb' is not configured."); builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSignalR(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(services => services.GetRequiredService()); builder.Services.AddHostedService(services => services.GetRequiredService()); builder.Services.AddHealthChecks(); builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { var origins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get() ?? []; if (origins.Length == 0) { policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); return; } policy.WithOrigins(origins).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); }); var app = builder.Build(); if (importCommand is not null) { var report = await BomImportCommandRunner.RunAsync(app.Services, importCommand, CancellationToken.None); Console.WriteLine(JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true })); return; } app.UseCors(); app.MapHealthChecks("/health"); app.MapGet("/api/dashboard/overview", async ( DashboardQueryService service, CancellationToken cancellationToken) => Results.Ok(await service.GetOverviewAsync(cancellationToken))); app.MapGet("/api/orders", async ( string? keyword, string? status, Guid? lineId, int? page, int? pageSize, DashboardQueryService service, CancellationToken cancellationToken) => Results.Ok(await service.GetOrdersAsync( keyword, status, lineId, page ?? 1, pageSize ?? 20, cancellationToken))); app.MapGet("/api/orders/{orderId:guid}", async ( Guid orderId, DashboardQueryService service, CancellationToken cancellationToken) => { var order = await service.GetOrderAsync(orderId, cancellationToken); return order is null ? Results.NotFound() : Results.Ok(order); }); app.MapGet("/api/orders/{orderId:guid}/tree", async ( Guid orderId, DashboardQueryService service, CancellationToken cancellationToken) => { var tree = await service.GetTreeAsync(orderId, cancellationToken); return tree is null ? Results.NotFound() : Results.Ok(tree); }); app.MapGet("/api/orders/{orderId:guid}/trend", async ( Guid orderId, int? limit, DashboardQueryService service, CancellationToken cancellationToken) => Results.Ok(await service.GetTrendAsync(orderId, limit ?? 12, cancellationToken))); app.MapGet("/api/orders/{orderId:guid}/material-quotas", async ( Guid orderId, string? category, string? keyword, int? page, int? pageSize, DashboardQueryService service, CancellationToken cancellationToken) => Results.Ok(await service.GetMaterialQuotasAsync( orderId, category, keyword, page ?? 1, pageSize ?? 50, cancellationToken))); app.MapGet("/api/risk-events", async ( Guid? orderId, int? limit, DashboardQueryService service, CancellationToken cancellationToken) => Results.Ok(await service.GetRiskEventsAsync(orderId, limit ?? 20, cancellationToken))); app.MapGet("/api/delay-top", async ( int? limit, DashboardQueryService service, CancellationToken cancellationToken) => Results.Ok(await service.GetDelayTopAsync(limit ?? 5, cancellationToken))); app.MapPatch("/api/nodes/{nodeId:guid}/progress", async ( Guid nodeId, UpdateNodeProgressRequest request, ProgressUpdateService service, CancellationToken cancellationToken) => { try { var result = await service.UpdateAsync(nodeId, request, cancellationToken); return result is null ? Results.NotFound() : Results.Ok(result); } catch (ArgumentException exception) { return Results.ValidationProblem(new Dictionary { ["progress"] = [exception.Message], }); } catch (DbUpdateConcurrencyException) { return Results.Conflict(new { code = "progress_conflict", message = "The production node changed after it was loaded. Reload the node and retry.", }); } }); app.MapHub("/hubs/dashboard"); await DatabaseInitializer.InitializeAsync(app); app.Run(); public partial class Program;