mirror of
https://github.com/alexhopeoconnor/bom-local-service.git
synced 2026-10-18 10:41:42 +10:00
Remove minutes ago parsing, use UTC timestamps directly
- Refactor TimeParsingService to parse time strings to UTC only - Remove minutesAgo from RadarFrame and FrameMetadata models - Update CaptureFramesStep to extract UTC timestamps directly - Update CacheService to store/load ObservationTime instead of MinutesAgo - Update BomRadarService to use AbsoluteObservationTime directly - Remove all backward compatibility code for old cache folders - Update documentation and test SPA to calculate minutes ago on client - Fix step registration to register concrete types for DI resolution - Fix hosted service registration to use reflection correctly
This commit is contained in:
+4
-118
@@ -1,12 +1,6 @@
|
||||
using BomLocalService.Services;
|
||||
using BomLocalService.Extensions;
|
||||
using BomLocalService.Services.Interfaces;
|
||||
using BomLocalService.Services.Scraping;
|
||||
using BomLocalService.Services.Scraping.Steps.Navigation;
|
||||
using BomLocalService.Services.Scraping.Steps.Search;
|
||||
using BomLocalService.Services.Scraping.Steps.Map;
|
||||
using BomLocalService.Services.Scraping.Steps.Metadata;
|
||||
using BomLocalService.Services.Scraping.Steps.Capture;
|
||||
using BomLocalService.Services.Scraping.Workflows;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -17,100 +11,10 @@ builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
// Configure CORS - MUST be added before other services
|
||||
// Configuration values come from appsettings.json (defaults) and can be overridden via environment variables
|
||||
// Environment variables use double underscore for nested keys (e.g., CORS__ALLOWEDORIGINS)
|
||||
var corsOrigins = builder.Configuration.GetValue<string>("Cors:AllowedOrigins")
|
||||
?? throw new InvalidOperationException("Cors:AllowedOrigins configuration is required. Set it in appsettings.json or via CORS__ALLOWEDORIGINS environment variable.");
|
||||
var corsMethods = builder.Configuration.GetValue<string>("Cors:AllowedMethods")
|
||||
?? throw new InvalidOperationException("Cors:AllowedMethods configuration is required. Set it in appsettings.json or via CORS__ALLOWEDMETHODS environment variable.");
|
||||
var corsHeaders = builder.Configuration.GetValue<string>("Cors:AllowedHeaders")
|
||||
?? throw new InvalidOperationException("Cors:AllowedHeaders configuration is required. Set it in appsettings.json or via CORS__ALLOWEDHEADERS environment variable.");
|
||||
builder.Services.AddCorsConfiguration(builder.Configuration);
|
||||
|
||||
// For bool, check if the key exists in configuration (GetValue<bool> returns false if not found, which is ambiguous)
|
||||
var corsAllowCredentialsKey = builder.Configuration["Cors:AllowCredentials"];
|
||||
if (corsAllowCredentialsKey == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cors:AllowCredentials configuration is required. Set it in appsettings.json or via CORS__ALLOWCREDENTIALS environment variable.");
|
||||
}
|
||||
var corsAllowCredentials = builder.Configuration.GetValue<bool>("Cors:AllowCredentials");
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
if (corsOrigins == "*")
|
||||
{
|
||||
policy.AllowAnyOrigin();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split comma-separated origins
|
||||
var origins = corsOrigins.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
policy.WithOrigins(origins);
|
||||
}
|
||||
|
||||
// Split comma-separated methods
|
||||
var methods = corsMethods.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
policy.WithMethods(methods);
|
||||
|
||||
// Split comma-separated headers or allow all
|
||||
if (corsHeaders == "*")
|
||||
{
|
||||
policy.AllowAnyHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = corsHeaders.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
policy.WithHeaders(headers);
|
||||
}
|
||||
|
||||
if (corsAllowCredentials)
|
||||
{
|
||||
policy.AllowCredentials();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Register core services via interfaces (order matters - dependencies must be registered first)
|
||||
builder.Services.AddSingleton<IDebugService, DebugService>();
|
||||
builder.Services.AddSingleton<ICacheService, CacheService>();
|
||||
builder.Services.AddSingleton<ITimeParsingService, TimeParsingService>();
|
||||
builder.Services.AddSingleton<IBrowserService, BrowserService>();
|
||||
builder.Services.AddSingleton<ISelectorService, SelectorService>();
|
||||
|
||||
// Register scraping step registry
|
||||
builder.Services.AddSingleton<IScrapingStepRegistry, ScrapingStepRegistry>();
|
||||
|
||||
// Register all scraping steps
|
||||
builder.Services.AddScoped<NavigateHomepageStep>();
|
||||
builder.Services.AddScoped<ClickSearchButtonStep>();
|
||||
builder.Services.AddScoped<FillSearchInputStep>();
|
||||
builder.Services.AddScoped<WaitForSearchResultsStep>();
|
||||
builder.Services.AddScoped<SelectSearchResultStep>();
|
||||
builder.Services.AddScoped<ClickRadarLinkStep>();
|
||||
builder.Services.AddScoped<WaitForMapReadyStep>();
|
||||
builder.Services.AddScoped<PauseRadarStep>();
|
||||
builder.Services.AddScoped<ResetToFirstFrameStep>();
|
||||
builder.Services.AddScoped<ExtractMetadataStep>();
|
||||
builder.Services.AddScoped<CalculateMapBoundsStep>();
|
||||
builder.Services.AddScoped<CaptureFramesStep>();
|
||||
|
||||
// Register workflows
|
||||
builder.Services.AddScoped<RadarScrapingWorkflow>();
|
||||
builder.Services.AddScoped<TemperatureMapWorkflow>();
|
||||
|
||||
// Register workflow factory
|
||||
builder.Services.AddSingleton<IWorkflowFactory, WorkflowFactory>();
|
||||
|
||||
// Register scraping service (depends on workflow factory)
|
||||
builder.Services.AddSingleton<IScrapingService, ScrapingService>();
|
||||
|
||||
// Register BOM Radar Service as singleton (orchestrator, depends on all above services)
|
||||
builder.Services.AddSingleton<IBomRadarService, BomLocalService.Services.BomRadarService>();
|
||||
|
||||
// Register background services (order matters - management service needs radar service)
|
||||
builder.Services.AddHostedService<CacheCleanupService>();
|
||||
builder.Services.AddHostedService<CacheManagementService>();
|
||||
// Register all services via assembly scanning
|
||||
builder.Services.ScanAndRegisterServices(typeof(Program).Assembly);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -142,24 +46,6 @@ app.MapControllers();
|
||||
// Map health check endpoint for Docker health monitoring
|
||||
app.MapHealthChecks("/api/health");
|
||||
|
||||
// Auto-register all scraping steps in the registry
|
||||
var stepRegistry = app.Services.GetRequiredService<IScrapingStepRegistry>();
|
||||
var stepTypes = typeof(IScrapingStep).Assembly.GetTypes()
|
||||
.Where(t => typeof(IScrapingStep).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && !t.IsGenericType);
|
||||
foreach (var stepType in stepTypes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var step = (IScrapingStep)ActivatorUtilities.CreateInstance(app.Services, stepType);
|
||||
stepRegistry.RegisterStep(step);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = app.Services.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogWarning(ex, "Failed to register step {StepType}", stepType.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup incomplete cache folders from previous crashes/restarts before starting services
|
||||
var cacheService = app.Services.GetRequiredService<ICacheService>();
|
||||
var deletedCount = cacheService.CleanupIncompleteCacheFolders();
|
||||
|
||||
Reference in New Issue
Block a user