feat: implement WorkProcessor and search execution services
- SearchRepository: Search table operations with Dapper - SearchExecutionService: Search pipeline with proper cancellation handling - WorkProcessor: Unified BackgroundService for syncs and searches - SearchNotificationService: SignalR notifications in Api layer All 45 new tests pass. Proper shutdown vs timeout distinction prevents marking searches as error on host shutdown.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
using JdeScoping.Api.Hubs;
|
||||
using JdeScoping.Core.Interfaces;
|
||||
using JdeScoping.Core.Models.Infrastructure;
|
||||
using JdeScoping.Core.Models.Search;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace JdeScoping.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// SignalR-based implementation of search notification service.
|
||||
/// Sends real-time updates to connected clients via StatusHub.
|
||||
/// </summary>
|
||||
public class SearchNotificationService : ISearchNotificationService
|
||||
{
|
||||
private readonly IHubContext<StatusHub> _hubContext;
|
||||
private readonly ILogger<SearchNotificationService> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of SearchNotificationService.
|
||||
/// </summary>
|
||||
/// <param name="hubContext">SignalR hub context for StatusHub.</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
public SearchNotificationService(
|
||||
IHubContext<StatusHub> hubContext,
|
||||
ILogger<SearchNotificationService> logger)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task NotifySearchUpdateAsync(Search search, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var update = new SearchUpdate(search);
|
||||
await _hubContext.Clients.All.SendAsync("searchUpdate", update, ct);
|
||||
_logger.LogDebug(
|
||||
"Search update notification sent: Id={SearchId}, Status={Status}",
|
||||
search.Id,
|
||||
search.Status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Best-effort notification - log but don't throw
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to send search update notification for SearchId={SearchId}",
|
||||
search.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task NotifyStatusAsync(string status, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var update = new StatusUpdate(status);
|
||||
await _hubContext.Clients.All.SendAsync("statusUpdate", update, ct);
|
||||
_logger.LogDebug("Status notification sent: {Status}", status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Best-effort notification - log but don't throw
|
||||
_logger.LogWarning(ex, "Failed to send status notification: {Status}", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user