166 lines
5.4 KiB
C#
166 lines
5.4 KiB
C#
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<Program>(optional: true);
|
|
}
|
|
|
|
var connectionString = builder.Configuration.GetConnectionString("DashboardDb")
|
|
?? throw new InvalidOperationException("Connection string 'DashboardDb' is not configured.");
|
|
|
|
builder.Services.AddDbContext<DashboardDbContext>(options => options.UseSqlServer(connectionString));
|
|
builder.Services.AddScoped<ProgressAggregationService>();
|
|
builder.Services.AddScoped<DashboardSeeder>();
|
|
builder.Services.AddScoped<DashboardQueryService>();
|
|
builder.Services.AddScoped<ProgressUpdateService>();
|
|
builder.Services.AddScoped<BomImportService>();
|
|
builder.Services.AddSignalR();
|
|
builder.Services.AddSingleton<IDashboardNotifier, SignalRDashboardNotifier>();
|
|
builder.Services.AddSingleton<DashboardUpdateQueue>();
|
|
builder.Services.AddSingleton<IDashboardUpdateQueue>(services =>
|
|
services.GetRequiredService<DashboardUpdateQueue>());
|
|
builder.Services.AddHostedService(services => services.GetRequiredService<DashboardUpdateQueue>());
|
|
builder.Services.AddHealthChecks();
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddDefaultPolicy(policy =>
|
|
{
|
|
var origins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
|
|
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<string, string[]>
|
|
{
|
|
["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<DashboardHub>("/hubs/dashboard");
|
|
await DatabaseInitializer.InitializeAsync(app);
|
|
app.Run();
|
|
|
|
public partial class Program;
|