feat: complete end to end functionality

This commit is contained in:
Stevan Freeborn
2024-09-01 18:09:48 -05:00
parent 78748cc104
commit e400be31f7
19 changed files with 376 additions and 454 deletions
@@ -1,63 +0,0 @@
namespace SanctionsSearch.Worker.Tests.Unit;
public class AddressRepositoryTests
{
private readonly Mock<DbContext> _contextMock = new();
private readonly Mock<ILogger<AddressRepository>> _loggerMock = new();
private readonly AddressFaker _faker = new();
private readonly AddressRepository _repository;
public AddressRepositoryTests()
{
_repository = new AddressRepository(_contextMock.Object, _loggerMock.Object);
}
[Fact]
public async Task Upsert_WhenExceptionIsThrown_ItShouldLogError()
{
var entity = _faker.Generate();
var mockSet = new Mock<DbSet<Address>>();
mockSet
.Setup(x => x.FindAsync(entity.Id))
.Throws<Exception>();
_contextMock
.Setup(x => x.Set<Address>())
.Returns(mockSet.Object);
await _repository.Upsert(entity);
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
[Fact]
public async Task Find_WhenExceptionIsThrown_ItShouldReturnEmptyListAndLogError()
{
_contextMock
.Setup(x => x.Set<Address>())
.Throws<Exception>();
var result = await _repository.Find(x => x.Id == 1);
result.Should().BeEmpty();
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
}
@@ -1,63 +0,0 @@
namespace SanctionsSearch.Worker.Tests.Unit;
public class AliasRepositoryTests
{
private readonly Mock<DbContext> _contextMock = new();
private readonly Mock<ILogger<AliasRepository>> _loggerMock = new();
private readonly AliasFaker _faker = new();
private readonly AliasRepository _repository;
public AliasRepositoryTests()
{
_repository = new AliasRepository(_contextMock.Object, _loggerMock.Object);
}
[Fact]
public async Task Upsert_WhenExceptionIsThrown_ItShouldLogError()
{
var entity = _faker.Generate();
var mockSet = new Mock<DbSet<Alias>>();
mockSet
.Setup(x => x.FindAsync(entity.Id))
.Throws<Exception>();
_contextMock
.Setup(x => x.Set<Alias>())
.Returns(mockSet.Object);
await _repository.Upsert(entity);
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
[Fact]
public async Task Find_WhenExceptionIsThrown_ItShouldReturnEmptyListAndLogError()
{
_contextMock
.Setup(x => x.Set<Alias>())
.Throws<Exception>();
var result = await _repository.Find(x => x.Id == 1);
result.Should().BeEmpty();
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
}
@@ -1,63 +0,0 @@
namespace SanctionsSearch.Worker.Tests.Unit;
public class CommentRepositoryTests
{
private readonly Mock<DbContext> _contextMock = new();
private readonly Mock<ILogger<CommentRepository>> _loggerMock = new();
private readonly CommentFaker _faker = new();
private readonly CommentRepository _repository;
public CommentRepositoryTests()
{
_repository = new CommentRepository(_contextMock.Object, _loggerMock.Object);
}
[Fact]
public async Task Upsert_WhenExceptionIsThrown_ItShouldLogError()
{
var entity = _faker.Generate();
var mockSet = new Mock<DbSet<Comment>>();
mockSet
.Setup(x => x.FindAsync(entity.Id))
.Throws<Exception>();
_contextMock
.Setup(x => x.Set<Comment>())
.Returns(mockSet.Object);
await _repository.Upsert(entity);
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
[Fact]
public async Task Find_WhenExceptionIsThrown_ItShouldReturnEmptyListAndLogError()
{
_contextMock
.Setup(x => x.Set<Comment>())
.Throws<Exception>();
var result = await _repository.Find(x => x.Id == 1);
result.Should().BeEmpty();
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
}
@@ -1,86 +0,0 @@
namespace SanctionsSearch.Worker.Tests.Unit;
public class EfUnitOfWorkTests
{
private readonly Mock<DbContext> _contextMock = new();
private readonly Mock<ILogger<EfUnitOfWork>> _loggerMock = new();
private readonly Mock<ILoggerFactory> _loggerFactoryMock = new();
private readonly EfUnitOfWork _unitOfWork;
public EfUnitOfWorkTests()
{
_unitOfWork = new(_contextMock.Object, _loggerMock.Object, _loggerFactoryMock.Object);
}
[Fact]
public void Sdns_WhenCalled_ShouldReturnSdnRepository()
{
_unitOfWork.Sdns.Should().BeOfType<SdnRepository>();
}
[Fact]
public void Addresses_WhenCalled_ShouldReturnAddressRepository()
{
_unitOfWork.Addresses.Should().BeOfType<AddressRepository>();
}
[Fact]
public void Aliases_WhenCalled_ShouldReturnAliasRepository()
{
_unitOfWork.Aliases.Should().BeOfType<AliasRepository>();
}
[Fact]
public void Comments_WhenCalled_ShouldReturnCommentRepository()
{
_unitOfWork.Comments.Should().BeOfType<CommentRepository>();
}
[Fact]
public void SaveChangesAsync_WhenADatabaseUpdateExceptionIsThrown_ItShouldBeCaughtAndLogged()
{
_contextMock
.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ThrowsAsync(new DbUpdateException());
var action = _unitOfWork.SaveChangesAsync;
action.Should().NotThrowAsync();
}
[Fact]
public void SaveChangesAsync_WhenExceptionIsThrown_ItShouldNotBeCaught()
{
_contextMock
.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception());
var action = _unitOfWork.SaveChangesAsync;
action.Should().ThrowAsync<Exception>();
}
[Fact]
public void DisposeAsync_WhenExceptionIsThrown_ItShouldBeCaught()
{
_contextMock
.Setup(x => x.DisposeAsync())
.Throws(new Exception());
var action = async () => await _unitOfWork.DisposeAsync();
action.Should().NotThrowAsync();
}
[Fact]
public void Dispose_WhenExceptionIsThrown_ItShouldBeCaught()
{
_contextMock
.Setup(x => x.Dispose())
.Throws(new Exception());
var action = _unitOfWork.Dispose;
action.Should().NotThrow();
}
}
@@ -1,63 +0,0 @@
namespace SanctionsSearch.Worker.Tests.Unit;
public class SdnRepositoryTests
{
private readonly Mock<DbContext> _contextMock = new();
private readonly Mock<ILogger<SdnRepository>> _loggerMock = new();
private readonly SdnFaker _faker = new();
private readonly SdnRepository _repository;
public SdnRepositoryTests()
{
_repository = new SdnRepository(_contextMock.Object, _loggerMock.Object);
}
[Fact]
public async Task Upsert_WhenExceptionIsThrown_ItShouldLogError()
{
var entity = _faker.Generate();
var mockSet = new Mock<DbSet<Sdn>>();
mockSet
.Setup(x => x.FindAsync(entity.Id))
.Throws<Exception>();
_contextMock
.Setup(x => x.Set<Sdn>())
.Returns(mockSet.Object);
await _repository.Upsert(entity);
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
[Fact]
public async Task Find_WhenExceptionIsThrown_ItShouldReturnEmptyListAndLogError()
{
_contextMock
.Setup(x => x.Set<Sdn>())
.Throws<Exception>();
var result = await _repository.Find(x => x.Id == 1);
result.Should().BeEmpty();
_loggerMock.Verify(
x => x.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
)
);
}
}
@@ -0,0 +1,10 @@
namespace SanctionsSearch.Worker.Extensions;
static class SearchRequestExtensions
{
public static Expression<Func<Sdn, bool>> ToSdnFilter(this SearchRequest request)
{
var nameParts = request.Name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return sdn => nameParts.All(part => sdn.Name.Contains(part));
}
}
@@ -2,6 +2,8 @@ namespace SanctionsSearch.Worker.Interfaces;
interface IOnspringService interface IOnspringService
{ {
Task<Result> UpdateSearchRequestAsProcessingAsync(SearchRequest request);
Task<Result> UpdateSearchRequestAsFailedAsync(SearchRequest request, string error);
Task<List<SearchRequest>> GetSearchRequestsAsync(); Task<List<SearchRequest>> GetSearchRequestsAsync();
Task<Result> AddSearchResultAsync(SearchResult result); Task<Result> AddSearchResultAsync(SearchResult result);
} }
+28 -1
View File
@@ -12,6 +12,33 @@ class Address : Entity
public override string ToString() public override string ToString()
{ {
return $"{StreetAddress}, {CityProvincePostal}, {Country}"; var address = new StringBuilder();
if (string.IsNullOrWhiteSpace(StreetAddress) is false)
{
address.Append(StreetAddress);
}
if (string.IsNullOrWhiteSpace(CityProvincePostal) is false)
{
if (address.Length > 0)
{
address.Append(", ");
}
address.Append(CityProvincePostal);
}
if (string.IsNullOrWhiteSpace(Country) is false)
{
if (address.Length > 0)
{
address.Append(", ");
}
address.Append(Country);
}
return address.ToString();
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ class Sdn : Entity
Name = Name, Name = Name,
Address = Addresses.FirstOrDefault()?.ToString() ?? string.Empty, Address = Addresses.FirstOrDefault()?.ToString() ?? string.Empty,
Type = Type, Type = Type,
Programs = [.. Program.Split("] [")] Programs = [.. Program.Split("] [")],
}; };
} }
} }
@@ -4,14 +4,4 @@ class SearchRequest
{ {
public int Id { get; init; } public int Id { get; init; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string State { get; set; } = string.Empty;
public string Zip { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public Expression<Func<Sdn, bool>> ToSdnFilter()
{
return sdn => EF.Functions.Like(sdn.Name, $"%{Name}%");
}
} }
@@ -1,15 +1,9 @@
namespace SanctionsSearch.Worker.Models; namespace SanctionsSearch.Worker.Models;
class SearchResult class SearchResult(int searchRequestId, List<Hit> hits)
{ {
public int SearchRequestId { get; init; } public int SearchRequestId { get; init; } = searchRequestId;
public List<Hit> Hits { get; init; } = []; public List<Hit> Hits { get; init; } = hits;
public SearchResult(int searchRequestId, List<Hit> hits)
{
SearchRequestId = searchRequestId;
Hits = hits;
}
} }
class Hit class Hit
@@ -13,11 +13,6 @@ class SearchRequestOptions
{ {
public int AppId { get; init; } public int AppId { get; init; }
public int NameFieldId { get; init; } public int NameFieldId { get; init; }
public int AddressFieldId { get; init; }
public int CityFieldId { get; init; }
public int StateFieldId { get; init; }
public int ZipFieldId { get; init; }
public int CountryFieldId { get; init; }
public int StatusFieldId { get; init; } public int StatusFieldId { get; init; }
public Guid AwaitingProcessingStatusId { get; init; } public Guid AwaitingProcessingStatusId { get; init; }
public Guid ProcessingStatusId { get; init; } public Guid ProcessingStatusId { get; init; }
@@ -13,41 +13,26 @@ class EfRepository<T> : IRepository<T> where T : Entity
public async Task<IEnumerable<T>> Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[]? includes) public async Task<IEnumerable<T>> Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[]? includes)
{ {
try var query = _context.Set<T>().AsQueryable();
{
var query = _context.Set<T>().AsQueryable();
if (includes is not null) if (includes is not null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return await query.Where(predicate).ToListAsync();
}
catch (Exception ex)
{ {
_logger.LogError(ex, "Error finding entities of type {Type}", typeof(T).Name); query = includes.Aggregate(query, (current, include) => current.Include(include));
return [];
} }
return await query.Where(predicate).ToListAsync();
} }
public async Task Upsert(T entity) public async Task Upsert(T entity)
{ {
try var existing = await _context.Set<T>().FindAsync(entity.Id);
{
var existing = await _context.Set<T>().FindAsync(entity.Id);
if (existing is null) if (existing is null)
{
await _context.Set<T>().AddAsync(entity);
return;
}
_context.Entry(existing).CurrentValues.SetValues(entity);
}
catch (Exception ex)
{ {
_logger.LogError(ex, "Error upserting entity of type {Type} with {Id}", typeof(T).Name, entity.Id); await _context.Set<T>().AddAsync(entity);
return;
} }
_context.Entry(existing).CurrentValues.SetValues(entity);
} }
} }
@@ -15,37 +15,16 @@ class EfUnitOfWork(
public async Task SaveChangesAsync() public async Task SaveChangesAsync()
{ {
try await _context.SaveChangesAsync();
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, "Failed to save changes to the database.");
}
} }
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
try await _context.DisposeAsync();
{
await _context.DisposeAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to dispose of the database context.");
}
} }
public void Dispose() public void Dispose()
{ {
try _context.Dispose();
{
_context.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to dispose of the database context.");
}
} }
} }
+2
View File
@@ -81,6 +81,7 @@ class Program
builder.Services.AddScoped<IOfacFileService, OfacFileService>(); builder.Services.AddScoped<IOfacFileService, OfacFileService>();
builder.Services.AddScoped<IOnspringService, OnspringService>(); builder.Services.AddScoped<IOnspringService, OnspringService>();
builder.Services.AddScoped<ISearchService, SearchService>();
builder.Services.AddScoped<ISdnRepository, SdnRepository>(); builder.Services.AddScoped<ISdnRepository, SdnRepository>();
builder.Services.AddScoped<IAddressRepository, AddressRepository>(); builder.Services.AddScoped<IAddressRepository, AddressRepository>();
@@ -91,6 +92,7 @@ class Program
builder.Services.AddScoped<IDatabaseMaintainer, DatabaseMaintainer>(); builder.Services.AddScoped<IDatabaseMaintainer, DatabaseMaintainer>();
builder.Services.AddHostedService<DatabaseWorker>(); builder.Services.AddHostedService<DatabaseWorker>();
builder.Services.AddHostedService<OnspringWorker>();
return builder; return builder;
} }
@@ -1,4 +1,3 @@
namespace SanctionsSearch.Worker.Services; namespace SanctionsSearch.Worker.Services;
class OnspringService( class OnspringService(
@@ -11,14 +10,139 @@ class OnspringService(
private readonly ILogger<OnspringService> _logger = logger; private readonly ILogger<OnspringService> _logger = logger;
private readonly IOnspringClient _client = client; private readonly IOnspringClient _client = client;
public Task<Result> AddSearchResultAsync(SearchResult result) public async Task<Result> AddSearchResultAsync(SearchResult result)
{ {
// TODO: Implement this method var typeValues = result.Hits.Select(h => h.Type).ToList();
// - we should add a new record for each hit var typeFieldPairs = await GetOrAddListValuePairs(
// - each new hit record should reference the request _options.SearchResultOptions.TypeFieldId,
// - we should update the request status to processed typeValues
// - we should return a Result indicating success or failure );
throw new NotImplementedException();
var programValues = result.Hits.SelectMany(h => h.Programs).ToList();
var programFieldPairs = await GetOrAddListValuePairs(
_options.SearchResultOptions.ProgramsFieldId,
programValues
);
var resultRecords = result.Hits.Select(hit =>
{
var resultRecord = new ResultRecord()
{
AppId = _options.SearchResultOptions.AppId,
FieldData = [
new IntegerFieldValue()
{
FieldId = _options.SearchResultOptions.SearchRequestFieldId,
Value = result.SearchRequestId
},
new StringFieldValue()
{
FieldId = _options.SearchResultOptions.NameFieldId,
Value = hit.Name
},
new StringFieldValue()
{
FieldId = _options.SearchResultOptions.AddressFieldId,
Value = hit.Address
},
]
};
if (string.IsNullOrWhiteSpace(hit.Type) is false)
{
resultRecord.FieldData.Add(
new GuidFieldValue()
{
FieldId = _options.SearchResultOptions.TypeFieldId,
Value = typeFieldPairs[hit.Type]
}
);
}
var programValues = hit.Programs
.Where(p => string.IsNullOrWhiteSpace(p) is false)
.Select(p => programFieldPairs[p])
.ToList();
if (programValues.Any())
{
resultRecord.FieldData.Add(
new GuidListFieldValue()
{
FieldId = _options.SearchResultOptions.ProgramsFieldId,
Value = programValues
}
);
}
return resultRecord;
});
var saveResultRequests = resultRecords.Select(_client.SaveRecordAsync);
var saveResultResponses = await Task.WhenAll(saveResultRequests);
foreach (var response in saveResultResponses)
{
if (response.IsSuccessful is false)
{
_logger.LogError(
"Failed to save search result: {StatusCode} - {Error}",
response.StatusCode,
response.Message
);
}
}
var isFailed = saveResultResponses.Any(r => r.IsSuccessful is false);
var updatedSearchRequest = isFailed
? new ResultRecord()
{
AppId = _options.SearchRequestOptions.AppId,
RecordId = result.SearchRequestId,
FieldData = [
new GuidFieldValue()
{
FieldId = _options.SearchRequestOptions.StatusFieldId,
Value = _options.SearchRequestOptions.ProcessedErrorStatusId
},
new StringFieldValue()
{
FieldId = _options.SearchRequestOptions.ErrorFieldId,
Value = "Unable to save all search results"
}
]
}
: new ResultRecord()
{
AppId = _options.SearchRequestOptions.AppId,
RecordId = result.SearchRequestId,
FieldData = [
new GuidFieldValue()
{
FieldId = _options.SearchRequestOptions.StatusFieldId,
Value = _options.SearchRequestOptions.ProcessedSuccessStatusId
},
new StringFieldValue()
{
FieldId = _options.SearchRequestOptions.ErrorFieldId,
Value = string.Empty
}
]
};
var updateRequestResponse = await _client.SaveRecordAsync(updatedSearchRequest);
if (updateRequestResponse.IsSuccessful is false)
{
_logger.LogError(
"Failed to update search request {RequestId} status: {StatusCode} - {Error}",
result.SearchRequestId,
updateRequestResponse.StatusCode,
updateRequestResponse.Message
);
}
return isFailed ? Result.Fail("Failed to saving all search results") : Result.Ok();
} }
public async Task<List<SearchRequest>> GetSearchRequestsAsync() public async Task<List<SearchRequest>> GetSearchRequestsAsync()
@@ -29,11 +153,6 @@ class OnspringService(
Filter = $"{_options.SearchRequestOptions.StatusFieldId} contains '{_options.SearchRequestOptions.AwaitingProcessingStatusId}'", Filter = $"{_options.SearchRequestOptions.StatusFieldId} contains '{_options.SearchRequestOptions.AwaitingProcessingStatusId}'",
FieldIds = [ FieldIds = [
_options.SearchRequestOptions.NameFieldId, _options.SearchRequestOptions.NameFieldId,
_options.SearchRequestOptions.AddressFieldId,
_options.SearchRequestOptions.CityFieldId,
_options.SearchRequestOptions.StateFieldId,
_options.SearchRequestOptions.ZipFieldId,
_options.SearchRequestOptions.CountryFieldId
], ],
}; };
@@ -89,6 +208,135 @@ class OnspringService(
return records.Select(MapRecordToSearchRequest).ToList(); return records.Select(MapRecordToSearchRequest).ToList();
} }
public async Task<Result> UpdateSearchRequestAsFailedAsync(SearchRequest request, string error)
{
var updatedSearchRequest = new ResultRecord()
{
AppId = _options.SearchRequestOptions.AppId,
RecordId = request.Id,
FieldData = [
new GuidFieldValue()
{
FieldId = _options.SearchRequestOptions.StatusFieldId,
Value = _options.SearchRequestOptions.ProcessedErrorStatusId
},
new StringFieldValue()
{
FieldId = _options.SearchRequestOptions.ErrorFieldId,
Value = error
}
]
};
var updateRequestResponse = await _client.SaveRecordAsync(updatedSearchRequest);
if (updateRequestResponse.IsSuccessful is false)
{
_logger.LogError(
"Failed to update search request {RequestId} status: {StatusCode} - {Error}",
request.Id,
updateRequestResponse.StatusCode,
updateRequestResponse.Message
);
return Result.Fail("Failed to update search request status");
}
return Result.Ok();
}
public async Task<Result> UpdateSearchRequestAsProcessingAsync(SearchRequest request)
{
var updatedSearchRequest = new ResultRecord()
{
AppId = _options.SearchRequestOptions.AppId,
RecordId = request.Id,
FieldData = [
new GuidFieldValue()
{
FieldId = _options.SearchRequestOptions.StatusFieldId,
Value = _options.SearchRequestOptions.ProcessingStatusId
}
]
};
var updateRequestResponse = await _client.SaveRecordAsync(updatedSearchRequest);
if (updateRequestResponse.IsSuccessful is false)
{
_logger.LogError(
"Failed to update search request {RequestId} status: {StatusCode} - {Error}",
request.Id,
updateRequestResponse.StatusCode,
updateRequestResponse.Message
);
return Result.Fail("Failed to update search request status");
}
return Result.Ok();
}
private async Task<Dictionary<string, Guid>> GetOrAddListValuePairs(int listFieldId, List<string> values)
{
var pairs = new Dictionary<string, Guid>();
var getFieldResponse = await _client.GetFieldAsync(listFieldId);
if (getFieldResponse.IsSuccessful is false)
{
_logger.LogError(
"Failed to retrieve field information for list field with id {ListFieldId}: {StatusCode} - {Error}",
listFieldId,
getFieldResponse.StatusCode,
getFieldResponse.Message
);
return pairs;
}
if (getFieldResponse.Value is not ListField listField)
{
_logger.LogError("Field with id {ListFieldId} is not a list field", listFieldId);
return pairs;
}
foreach (var value in values.Where(v => string.IsNullOrWhiteSpace(v) is false).Distinct())
{
var existingListFieldValue = listField.Values.FirstOrDefault(v => string.Equals(v.Name, value, StringComparison.InvariantCultureIgnoreCase));
if (existingListFieldValue is not null)
{
pairs.Add(value, existingListFieldValue.Id);
continue;
}
var saveListFieldValueRequest = new SaveListItemRequest()
{
ListId = listField.ListId,
Name = value
};
var saveListFieldValueResponse = await _client.SaveListItemAsync(saveListFieldValueRequest);
if (saveListFieldValueResponse.IsSuccessful is false)
{
_logger.LogError(
"Failed to save list field value for list field with id {ListFieldId}: {StatusCode} - {Error}",
listFieldId,
saveListFieldValueResponse.StatusCode,
saveListFieldValueResponse.Message
);
continue;
}
pairs.Add(value, saveListFieldValueResponse.Value.Id);
}
return pairs;
}
private SearchRequest MapRecordToSearchRequest(ResultRecord record) private SearchRequest MapRecordToSearchRequest(ResultRecord record)
{ {
var searchRequest = new SearchRequest var searchRequest = new SearchRequest
@@ -102,26 +350,6 @@ class OnspringService(
{ {
searchRequest.Name = field.GetStringValue(); searchRequest.Name = field.GetStringValue();
} }
else if (field.FieldId == _options.SearchRequestOptions.AddressFieldId)
{
searchRequest.Address = field.GetStringValue();
}
else if (field.FieldId == _options.SearchRequestOptions.CityFieldId)
{
searchRequest.City = field.GetStringValue();
}
else if (field.FieldId == _options.SearchRequestOptions.StateFieldId)
{
searchRequest.State = field.GetStringValue();
}
else if (field.FieldId == _options.SearchRequestOptions.ZipFieldId)
{
searchRequest.Zip = field.GetStringValue();
}
else if (field.FieldId == _options.SearchRequestOptions.CountryFieldId)
{
searchRequest.Country = field.GetStringValue();
}
} }
return searchRequest; return searchRequest;
+1
View File
@@ -2,6 +2,7 @@ global using System.Collections.Concurrent;
global using System.Globalization; global using System.Globalization;
global using System.Linq.Expressions; global using System.Linq.Expressions;
global using System.Reflection; global using System.Reflection;
global using System.Text;
global using CsvHelper; global using CsvHelper;
global using CsvHelper.Configuration; global using CsvHelper.Configuration;
@@ -42,10 +42,62 @@ public class OnspringWorker(
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private Task RunSearchRequests() private async Task RunSearchRequests()
{ {
var searchBatchIdProperty = LogContext.PushProperty("SearchBatchId", Guid.NewGuid());
_logger.LogInformation("Running search requests"); _logger.LogInformation("Running search requests");
return Task.CompletedTask; try
{
using var scope = _serviceScopeFactory.CreateAsyncScope();
var onspringService = scope.ServiceProvider.GetRequiredService<IOnspringService>();
var searchService = scope.ServiceProvider.GetRequiredService<ISearchService>();
var searchRequests = await onspringService.GetSearchRequestsAsync();
if (searchRequests.Count is 0)
{
_logger.LogInformation("No search requests found");
return;
}
foreach (var searchRequest in searchRequests)
{
var searchBatchItemIdProperty = LogContext.PushProperty("SearchBatchItemId", Guid.NewGuid());
try
{
_logger.LogInformation("Processing search request {SearchRequestId}", searchRequest.Id);
await onspringService.UpdateSearchRequestAsProcessingAsync(searchRequest);
var searchResult = await searchService.PerformSearchAsync(searchRequest);
await onspringService.AddSearchResultAsync(searchResult);
_logger.LogInformation("Search request {SearchRequestId} processed", searchRequest.Id);
}
catch (Exception ex)
{
await onspringService.UpdateSearchRequestAsFailedAsync(
searchRequest,
$"Failed to process search request: {ex.Message}"
);
_logger.LogError(ex, "Failed to process search request {SearchRequestId}", searchRequest.Id);
}
finally
{
searchBatchItemIdProperty.Dispose();
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to run search requests");
}
finally
{
searchBatchIdProperty.Dispose();
}
} }
} }
@@ -27,11 +27,6 @@
"SearchRequestOptions": { "SearchRequestOptions": {
"AppId": 949, "AppId": 949,
"NameFieldId": 20946, "NameFieldId": 20946,
"AddressFieldId": 20947,
"CityFieldId": 20948,
"StateFieldId": 20949,
"ZipFieldId": 20967,
"CountryFieldId": 20950,
"StatusFieldId": 20964, "StatusFieldId": 20964,
"AwaitingProcessingStatusId": "3cea19f0-afae-4dac-ac5b-e6f5555659b3", "AwaitingProcessingStatusId": "3cea19f0-afae-4dac-ac5b-e6f5555659b3",
"ProcessingStatusId": "597c3096-ab0e-4910-a7a9-98d149acee81", "ProcessingStatusId": "597c3096-ab0e-4910-a7a9-98d149acee81",