mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 18:53:12 +00:00
Some checks failed
* feat: add API Sentry tracing * feat: activate Sentry deployment environments * fix: allow Sentry-free design-time tooling * fix: trace background jobs in Sentry * feat(observability): identify and scrub Sentry transactions * fix(observability): finish abandoned transactions
102 lines
4.2 KiB
C#
102 lines
4.2 KiB
C#
using Api.SeaHavenIndustries.Observability;
|
|
using Data.SeaHavenIndustries;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Sentry;
|
|
|
|
namespace Api.SeaHavenIndustries.Helper
|
|
{
|
|
public class VendorDocumentScanWorker : BackgroundService
|
|
{
|
|
private readonly IServiceScopeFactory _scopeFactory;
|
|
private readonly IWebHostEnvironment _environment;
|
|
private readonly ILogger<VendorDocumentScanWorker> _logger;
|
|
private readonly IHub _sentryHub;
|
|
|
|
public VendorDocumentScanWorker(
|
|
IServiceScopeFactory scopeFactory,
|
|
IWebHostEnvironment environment,
|
|
ILogger<VendorDocumentScanWorker> logger,
|
|
IHub sentryHub)
|
|
{
|
|
_scopeFactory = scopeFactory;
|
|
_environment = environment;
|
|
_logger = logger;
|
|
_sentryHub = sentryHub;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
await ScanPendingAsync(stoppingToken);
|
|
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
|
}
|
|
}
|
|
|
|
private async Task ScanPendingAsync(CancellationToken cancellationToken)
|
|
{
|
|
using var transaction = SentryObservability.BeginBackgroundTransaction(
|
|
_sentryHub,
|
|
"vendor-documents.scan-pending",
|
|
$"{nameof(VendorDocumentScanWorker)}.{nameof(ScanPendingAsync)}");
|
|
try
|
|
{
|
|
using var scope = _scopeFactory.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
|
var scanner = scope.ServiceProvider.GetRequiredService<IDocumentScanner>();
|
|
var pending = await db.VendorCompletionDocuments
|
|
.Where(document => document.ScanStatus == "Pending")
|
|
.OrderBy(document => document.CreatedDate)
|
|
.Take(20)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
foreach (var document in pending)
|
|
{
|
|
var path = VendorDocumentStorage.ResolvePath(_environment.ContentRootPath, document);
|
|
var result = await scanner.ScanAsync(path, cancellationToken);
|
|
if (result == DocumentScanResult.Unavailable)
|
|
{
|
|
_logger.LogWarning("Vendor document scan service unavailable; document {DocumentId} remains quarantined", document.Id);
|
|
continue;
|
|
}
|
|
|
|
document.ScanStatus = result == DocumentScanResult.Passed ? "Passed" : "Rejected";
|
|
document.ReviewStatus = result == DocumentScanResult.Passed ? "Processing" : "Rejected";
|
|
document.RejectionReason = result == DocumentScanResult.Infected
|
|
? "The upload failed malware scanning."
|
|
: document.RejectionReason;
|
|
document.ScannedAt = DateTime.UtcNow;
|
|
document.LastModificationTime = DateTime.UtcNow;
|
|
await db.SaveChangesAsync(cancellationToken);
|
|
|
|
if (result == DocumentScanResult.Infected && File.Exists(path))
|
|
File.Delete(path);
|
|
}
|
|
|
|
transaction.FinishOk();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
transaction.FinishCancelled();
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
transaction.FinishError(ex);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class VendorDocumentStorage
|
|
{
|
|
public static string Root(string contentRoot) =>
|
|
Path.Combine(contentRoot, "App_Data", "vendor-documents");
|
|
|
|
public static string ResolvePath(string contentRoot, int vendorId, int dispatchId, string storedFileName) =>
|
|
Path.Combine(Root(contentRoot), vendorId.ToString(), dispatchId.ToString(), storedFileName);
|
|
|
|
public static string ResolvePath(string contentRoot, VendorCompletionDocument document) =>
|
|
ResolvePath(contentRoot, document.VendorId, document.DispatchId, document.StoredFileName);
|
|
}
|
|
}
|