diff --git a/.circleci/config.yml b/.circleci/config.yml index 65848d92..a63bb335 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -231,8 +231,10 @@ jobs: workflows: check-and-deploy-development: jobs: - - check-code-formatting - - build-and-test + - check-code-formatting: + context: api-nuget-token-context + - build-and-test: + context: api-nuget-token-context - assume-role-development: context: api-assume-role-housing-development-context requires: @@ -266,8 +268,10 @@ workflows: only: master check-and-deploy-staging-and-production: jobs: - - check-code-formatting + - check-code-formatting: + context: api-nuget-token-context - build-and-test: + context: api-nuget-token-context filters: branches: only: release diff --git a/CODEOWNERS b/CODEOWNERS index 42ce3d29..b23865cf 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -5,4 +5,4 @@ # @global-owner1 and @global-owner2 will be requested for # review when someone opens a pull request. -* @LBHMGeorgieva @LBHMKeyworth @LBHSPreston @LBHRShetty @mtfh-manage-my-home +* @LBHMGeorgieva @LBHMKeyworth @LBHSPreston @LBHRShetty @LBHackney-IT/mtfh-manage-my-home diff --git a/HousingSearchApi.Tests/HousingSearchApi.Tests.csproj b/HousingSearchApi.Tests/HousingSearchApi.Tests.csproj index 06df94a3..7594c241 100644 --- a/HousingSearchApi.Tests/HousingSearchApi.Tests.csproj +++ b/HousingSearchApi.Tests/HousingSearchApi.Tests.csproj @@ -12,22 +12,18 @@ <ItemGroup> <Compile Remove="TestResults\**" /> <Compile Remove="V1\Domain\**" /> - <Compile Remove="V1\Factories\**" /> <Compile Remove="V1\Gateways\**" /> <Compile Remove="V1\UseCase\**" /> <Content Remove="TestResults\**" /> <Content Remove="V1\Domain\**" /> - <Content Remove="V1\Factories\**" /> <Content Remove="V1\Gateways\**" /> <Content Remove="V1\UseCase\**" /> <EmbeddedResource Remove="TestResults\**" /> <EmbeddedResource Remove="V1\Domain\**" /> - <EmbeddedResource Remove="V1\Factories\**" /> <EmbeddedResource Remove="V1\Gateways\**" /> <EmbeddedResource Remove="V1\UseCase\**" /> <None Remove="TestResults\**" /> <None Remove="V1\Domain\**" /> - <None Remove="V1\Factories\**" /> <None Remove="V1\Gateways\**" /> <None Remove="V1\UseCase\**" /> </ItemGroup> @@ -49,7 +45,7 @@ <PackageReference Include="Docker.DotNet" Version="3.125.4" /> <PackageReference Include="FluentAssertions" Version="5.10.3" /> <PackageReference Include="Hackney.Core.ElasticSearch" Version="1.45.0" /> - <PackageReference Include="Hackney.Shared.HousingSearch" Version="0.6.0" /> + <PackageReference Include="Hackney.Shared.HousingSearch" Version="0.19.0" /> <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.3" /> diff --git a/HousingSearchApi.Tests/MockWebApplicationFactory.cs b/HousingSearchApi.Tests/MockWebApplicationFactory.cs index 6012c478..091de4cf 100644 --- a/HousingSearchApi.Tests/MockWebApplicationFactory.cs +++ b/HousingSearchApi.Tests/MockWebApplicationFactory.cs @@ -1,4 +1,4 @@ -using HousingSearchApi.V1.Infrastructure; +using HousingSearchApi.V1.Infrastructure.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; diff --git a/HousingSearchApi.Tests/V1/Boundary/Requests/Validation/GetPersonListRequestValidatorTests.cs b/HousingSearchApi.Tests/V1/Boundary/Requests/Validation/GetPersonListRequestValidatorTests.cs index 6c9a4407..dc12e783 100644 --- a/HousingSearchApi.Tests/V1/Boundary/Requests/Validation/GetPersonListRequestValidatorTests.cs +++ b/HousingSearchApi.Tests/V1/Boundary/Requests/Validation/GetPersonListRequestValidatorTests.cs @@ -14,9 +14,9 @@ public GetPersonListRequestValidatorTests() _sut = new HousingSearchRequestValidator(); } - private static HousingSearchRequest CreateValidRequest() + private static GetPersonListRequest CreateValidRequest() { - return new HousingSearchRequest() + return new GetPersonListRequest() { SearchText = "Some search text" }; diff --git a/HousingSearchApi.Tests/V1/Controllers/GetAssetListControllerTests.cs b/HousingSearchApi.Tests/V1/Controllers/GetAssetListControllerTests.cs index 7ad9976e..e509e00e 100644 --- a/HousingSearchApi.Tests/V1/Controllers/GetAssetListControllerTests.cs +++ b/HousingSearchApi.Tests/V1/Controllers/GetAssetListControllerTests.cs @@ -9,24 +9,26 @@ namespace HousingSearchApi.Tests.V1.Controllers { [Collection("LogCall collection")] - public class GetTenureListControllerTests + public class GetAssetListControllerTests { private readonly Mock<IGetAssetListUseCase> _mockGetAssetListUseCase; + private readonly Mock<IGetAssetListSetsUseCase> _mockGetAssetListSetsUseCase; private readonly GetAssetListController _classUnderTest; - public GetTenureListControllerTests() + public GetAssetListControllerTests() { new LogCallAspectFixture().RunBeforeTests(); _mockGetAssetListUseCase = new Mock<IGetAssetListUseCase>(); - _classUnderTest = new GetAssetListController(_mockGetAssetListUseCase.Object); + _mockGetAssetListSetsUseCase = new Mock<IGetAssetListSetsUseCase>(); + _classUnderTest = new GetAssetListController(_mockGetAssetListUseCase.Object, _mockGetAssetListSetsUseCase.Object); } [Fact] - public async Task GetTenureListShouldCallGetTenureListUseCase() + public async Task GetAssetListShouldCallGetAssetListUseCase() { // given - var request = new HousingSearchRequest(); + var request = new GetAssetListRequest(); var response = new GetAssetListResponse(); _mockGetAssetListUseCase.Setup(x => x.ExecuteAsync(request)).ReturnsAsync(response); @@ -36,5 +38,19 @@ public async Task GetTenureListShouldCallGetTenureListUseCase() // then _mockGetAssetListUseCase.Verify(x => x.ExecuteAsync(request), Times.Once); } + [Fact] + public async Task GetAssetListSetsShouldCallGetAssetListSetsUseCase() + { + // given + var request = new GetAllAssetListRequest(); + var response = new GetAllAssetListResponse(); + _mockGetAssetListSetsUseCase.Setup(x => x.ExecuteAsync(request)).ReturnsAsync(response); + + // when + await _classUnderTest.GetAllAssetList(request).ConfigureAwait(false); + + // then + _mockGetAssetListSetsUseCase.Verify(x => x.ExecuteAsync(request), Times.Once); + } } } diff --git a/HousingSearchApi.Tests/V1/Controllers/GetTenureListControllerTests.cs b/HousingSearchApi.Tests/V1/Controllers/GetTenureListControllerTests.cs index 7ec7841c..bf0b1930 100644 --- a/HousingSearchApi.Tests/V1/Controllers/GetTenureListControllerTests.cs +++ b/HousingSearchApi.Tests/V1/Controllers/GetTenureListControllerTests.cs @@ -9,33 +9,33 @@ namespace HousingSearchApi.Tests.V1.Controllers { [Collection("LogCall collection")] - public class GetAssetListControllerTests + public class GetTenureListControllerTests { - private readonly Mock<IGetAssetListUseCase> _mockGetAssetListUseCase; - private readonly GetAssetListController _classUnderTest; + private readonly Mock<IGetTenureListUseCase> _mockGetTenureListUseCase; + private readonly GetTenureListController _classUnderTest; - public GetAssetListControllerTests() + public GetTenureListControllerTests() { new LogCallAspectFixture().RunBeforeTests(); - _mockGetAssetListUseCase = new Mock<IGetAssetListUseCase>(); - _classUnderTest = new GetAssetListController(_mockGetAssetListUseCase.Object); + _mockGetTenureListUseCase = new Mock<IGetTenureListUseCase>(); + _classUnderTest = new GetTenureListController(_mockGetTenureListUseCase.Object); } [Fact] - public async Task GetAssetListShouldCallGetAssetListUseCase() + public async Task GetTenureListShouldCallGetTenureListUseCase() { // given - var request = new HousingSearchRequest(); - var response = new GetAssetListResponse(); - _mockGetAssetListUseCase.Setup(x => x.ExecuteAsync(request)).ReturnsAsync(response); + var request = new GetTenureListRequest(); + var response = new GetTenureListResponse(); + _mockGetTenureListUseCase.Setup(x => x.ExecuteAsync(request)).ReturnsAsync(response); // when - await _classUnderTest.GetAssetList(request).ConfigureAwait(false); + await _classUnderTest.GetTenureList(request).ConfigureAwait(false); // then - _mockGetAssetListUseCase.Verify(x => x.ExecuteAsync(request), Times.Once); + _mockGetTenureListUseCase.Verify(x => x.ExecuteAsync(request), Times.Once); } } } diff --git a/HousingSearchApi.Tests/V1/Controllers/GetTransactionListControllerTests.cs b/HousingSearchApi.Tests/V1/Controllers/GetTransactionListControllerTests.cs new file mode 100644 index 00000000..836ab35f --- /dev/null +++ b/HousingSearchApi.Tests/V1/Controllers/GetTransactionListControllerTests.cs @@ -0,0 +1,74 @@ +using FluentAssertions; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses.Metadata; +using HousingSearchApi.V1.Boundary.Responses.Transactions; +using HousingSearchApi.V1.Controllers; +using HousingSearchApi.V1.UseCase.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Moq; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace HousingSearchApi.Tests.V1.Controllers +{ + [Collection("LogCall collection")] + public class GetTransactionListControllerTests + { + private readonly Mock<IGetTransactionListUseCase> _mockGetTransactionListUseCase; + private readonly GetTransactionListController _classUnderTest; + + public GetTransactionListControllerTests() + { + new LogCallAspectFixture().RunBeforeTests(); + + _mockGetTransactionListUseCase = new Mock<IGetTransactionListUseCase>(); + _classUnderTest = new GetTransactionListController(_mockGetTransactionListUseCase.Object); + } + + [Fact] + public async Task GetTransactionListReturnsOk() + { + const int transactionsCount = 10; + var request = new GetTransactionListRequest(); + var response = GetTransactionListResponse.Create(transactionsCount, new List<TransactionResponse>()); + + _mockGetTransactionListUseCase + .Setup(x => x.ExecuteAsync(request)) + .ReturnsAsync(response); + + var controllerResponse = await _classUnderTest.GetTransactionList(request).ConfigureAwait(false); + + _mockGetTransactionListUseCase.Verify(x => x.ExecuteAsync(request), Times.Once); + + var okObjectResult = controllerResponse as OkObjectResult; + okObjectResult.Should().NotBeNull(); + okObjectResult.Value.Should().NotBeNull(); + + var apiResponse = okObjectResult.Value as APIResponse<GetTransactionListResponse>; + apiResponse.Should().NotBeNull(); + + apiResponse.Results.Should().BeEquivalentTo(response); + apiResponse.Total.Should().Be(transactionsCount); + } + + [Fact] + public async Task GetTransactionListThrowsIfUseCaseReturnsNull() + { + var request = new GetTransactionListRequest(); + + _mockGetTransactionListUseCase + .Setup(x => x.ExecuteAsync(request)) + .Throws(new Exception("Some error")); + + var controllerResponse = await _classUnderTest.GetTransactionList(request).ConfigureAwait(false); + + _mockGetTransactionListUseCase.Verify(x => x.ExecuteAsync(request), Times.Once); + + var objectResult = controllerResponse as BadRequestObjectResult; + objectResult.Should().NotBeNull(); + objectResult.Value.Should().Be("Some error"); + } + } +} diff --git a/HousingSearchApi.Tests/V1/E2ETests/Fixtures/BaseFixture.cs b/HousingSearchApi.Tests/V1/E2ETests/Fixtures/BaseFixture.cs index 7fe9d74e..e5e1b365 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Fixtures/BaseFixture.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Fixtures/BaseFixture.cs @@ -58,6 +58,5 @@ protected void EnsureEnvVarConfigured(string name, string defaultValue) _elasticSearchAddress = default; } } - } } diff --git a/HousingSearchApi.Tests/V1/E2ETests/Fixtures/PersonsFixture.cs b/HousingSearchApi.Tests/V1/E2ETests/Fixtures/PersonsFixture.cs index 3f288949..4ab72f48 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Fixtures/PersonsFixture.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Fixtures/PersonsFixture.cs @@ -15,7 +15,6 @@ namespace HousingSearchApi.Tests.V1.E2ETests.Fixtures { public class PersonsFixture : BaseFixture { - public List<QueryablePerson> Persons { get; private set; } private const string INDEX = "persons"; public static string[] Alphabet = { "aa", "bb", "cc", "dd", "ee", "vv", "ww", "xx", "yy", "zz" }; diff --git a/HousingSearchApi.Tests/V1/E2ETests/Fixtures/TransactionsFixture.cs b/HousingSearchApi.Tests/V1/E2ETests/Fixtures/TransactionsFixture.cs new file mode 100644 index 00000000..bbf04646 --- /dev/null +++ b/HousingSearchApi.Tests/V1/E2ETests/Fixtures/TransactionsFixture.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Nest; +using System.Net.Http; +using System.Threading; +using AutoFixture; +using Elasticsearch.Net; +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; + +namespace HousingSearchApi.Tests.V1.E2ETests.Fixtures +{ + public class TransactionsFixture : BaseFixture + { + private const string IndexName = "transactions"; + private const int SendersCount = 5; + + private static readonly Fixture _fixture = new Fixture(); + + public static List<QueryableSender> Senders { get; } = CreateSendersData(SendersCount); + + public TransactionsFixture(IElasticClient elasticClient, HttpClient httpHttpClient) : base(elasticClient, httpHttpClient) + { + WaitForESInstance(); + } + + private static List<QueryableSender> CreateSendersData(int personsCount) + { + return _fixture.CreateMany<QueryableSender>(personsCount).ToList(); + } + + public void GivenAnAssetIndexExists() + { + ElasticSearchClient.Indices.Delete(IndexName); + + if (ElasticSearchClient.Indices.Exists(Indices.Index(IndexName)).Exists) + { + return; + } + + // ToDo: add transactionsIndex to the folder + var assetSettingsDoc = File.ReadAllTextAsync("./data/elasticsearch/transactionsIndex.json").Result; + ElasticSearchClient.LowLevel.Indices.CreateAsync<BytesResponse>(IndexName, assetSettingsDoc) + .ConfigureAwait(true); + + var transactions = CreateTransactionsData(20); + var awaitable = ElasticSearchClient.IndexManyAsync(transactions, IndexName).ConfigureAwait(true); + + while (!awaitable.GetAwaiter().IsCompleted) { } + + Thread.Sleep(5000); + } + + private List<QueryableTransaction> CreateTransactionsData(int transactionsCount) + { + var listOfTransactions = new List<QueryableTransaction>(transactionsCount); + var random = new Random(); + + for (var i = 0; i < transactionsCount; i++) + { + var personIndex = random.Next(SendersCount); + + var transaction = _fixture.Create<QueryableTransaction>(); + transaction.Sender = Senders[personIndex]; + + listOfTransactions.Add(transaction); + } + + return listOfTransactions; + } + } +} diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/Base/BaseSteps.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/Base/BaseSteps.cs index 92655a25..1f66dc43 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Steps/Base/BaseSteps.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/Base/BaseSteps.cs @@ -1,6 +1,10 @@ +using System; +using System.Net; using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; +using FluentAssertions; namespace HousingSearchApi.Tests.V1.E2ETests.Steps.Base { @@ -27,5 +31,30 @@ protected JsonSerializerOptions CreateJsonOptions() options.Converters.Add(new JsonStringEnumConverter()); return options; } + + public async Task ThenTheLastRequestShouldBeBadRequestResult(string expectedErrorMessage = null) + { + _lastResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); + + if (expectedErrorMessage != null) + { + var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + + resultBody.Should().NotBeNull(); + resultBody.Should().Contain(expectedErrorMessage); + } + } + + public async Task ThenTheLastRequestShouldBe200() + { + if (_lastResponse.StatusCode != HttpStatusCode.OK) + { + var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + + resultBody.Should().NotBeNull(); + throw new Exception(resultBody); + } + _lastResponse.StatusCode.Should().Be(HttpStatusCode.OK); + } } } diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetAssetSteps.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetAssetSteps.cs index 3003d33a..1b07b0d1 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetAssetSteps.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetAssetSteps.cs @@ -1,20 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; using FluentAssertions; using HousingSearchApi.Tests.V1.E2ETests.Fixtures; using HousingSearchApi.Tests.V1.E2ETests.Steps.Base; using HousingSearchApi.V1.Boundary.Responses; using HousingSearchApi.V1.Boundary.Responses.Metadata; +using System; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; namespace HousingSearchApi.Tests.V1.E2ETests.Steps { public class GetAssetSteps : BaseSteps { + private string _lastHitId; public GetAssetSteps(HttpClient httpClient) : base(httpClient) { } @@ -29,6 +28,7 @@ public async Task WhenRequestContainsSearchString() _lastResponse = await _httpClient.GetAsync(new Uri("api/v1/search/assets?searchText=%20abc", UriKind.Relative)).ConfigureAwait(false); } + public async Task WhenAPageSizeIsProvided(int pageSize) { var route = new Uri($"api/v1/search/assets?searchText={AssetFixture.Addresses.Last().FirstLine}&pageSize={pageSize}", @@ -36,6 +36,7 @@ public async Task WhenAPageSizeIsProvided(int pageSize) _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); } + public async Task WhenAssetTypesAreProvided(string assetType) { var route = new Uri($"api/v1/search/assets?searchText={AssetFixture.Addresses.Last()}&assetTypes={assetType}&pageSize={5}", @@ -43,23 +44,26 @@ public async Task WhenAssetTypesAreProvided(string assetType) _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); } - - public async Task WhenAnExactMatchExists(string address) + public async Task WhenSearchTextProvidedAsStarStarAndAssetTypeProvidedAndLastHitIdNotProvided(string assetType) { - var route = new Uri($"api/v1/search/assets?searchText={address}&pageSize={5}", + var route = new Uri($"api/v1/search/assets/all?searchText=**&assetTypes={assetType}&pageSize={5}", UriKind.Relative); _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); } - - public void ThenTheLastRequestShouldBeBadRequestResult() + public async Task WhenSearchTextProvidedAsStarStarAndAssetTypeProvidedAndLastHitIdProvided(string assetType) { - _lastResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } + var route = new Uri($"api/v1/search/assets/all?searchText=**&assetTypes={assetType}&pageSize={5}&lastHitId={_lastHitId}", + UriKind.Relative); - public void ThenTheLastRequestShouldBe200() + _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); + } + public async Task WhenAnExactMatchExists(string address) { - _lastResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var route = new Uri($"api/v1/search/assets?searchText={address}&pageSize={5}", + UriKind.Relative); + + _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); } public async Task ThenTheReturningResultsShouldBeOfThatSize(int pageSize) @@ -78,6 +82,23 @@ public async Task ThenOnlyTheseAssetTypesShouldBeIncluded(string allowedAssetTyp var assets = allowedAssetType.Split(","); result.Results.Assets.All(x => x.AssetType == assets[0] || x.AssetType == assets[1]); + } + public async Task ThenOnlyAllAssetsResponseTheseAssetTypesShouldBeIncluded(string allowedAssetType) + { + var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize<APIAllResponse<GetAllAssetListResponse>>(resultBody, _jsonOptions); + + var assets = allowedAssetType.Split(","); + + result.Results.Assets.All(x => x.AssetType == assets[0] || x.AssetType == assets[1]); + + } + public async Task ThenOnlyLastHitIdShouldBeIncluded() + { + var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonSerializer.Deserialize<APIAllResponse<GetAllAssetListResponse>>(resultBody, _jsonOptions); + + _lastHitId = result?.LastHitId; } diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetPersonsSteps.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetPersonsSteps.cs index 50cf79ea..98ae1eeb 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetPersonsSteps.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetPersonsSteps.cs @@ -76,21 +76,6 @@ public async Task WhenARequestContainsSearchByLeaseholder() _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); } - public async Task ThenTheLastRequestShouldBeBadRequestResult() - { - _lastResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); - - var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); - - resultBody.Should().NotBeNull(); - resultBody.Should().Contain("'Search Text' must not be empty."); - } - - public void ThenTheLastRequestShouldBe200() - { - _lastResponse.StatusCode.Should().Be(HttpStatusCode.OK); - } - public async Task ThenTheReturningResultsShouldBeOfThatSize(int pageSize) { var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTenureSteps.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTenureSteps.cs index 82bf08b8..672283ba 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTenureSteps.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTenureSteps.cs @@ -41,16 +41,6 @@ public async Task WhenAPageSizeIsProvided(int pageSize) _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); } - public void ThenTheLastRequestShouldBeBadRequestResult() - { - _lastResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - public void ThenTheLastRequestShouldBe200() - { - _lastResponse.StatusCode.Should().Be(HttpStatusCode.OK); - } - public async Task ThenTheReturningResultsShouldBeOfThatSize(int pageSize) { var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTransactionsSteps.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTransactionsSteps.cs new file mode 100644 index 00000000..7a4a349c --- /dev/null +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/GetTransactionsSteps.cs @@ -0,0 +1,79 @@ +using FluentAssertions; +using HousingSearchApi.Tests.V1.E2ETests.Fixtures; +using HousingSearchApi.Tests.V1.E2ETests.Steps.Base; +using HousingSearchApi.Tests.V1.E2ETests.Steps.ResponseModels; +using HousingSearchApi.V1.Boundary.Responses.Metadata; +using HousingSearchApi.V1.Infrastructure; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; + +namespace HousingSearchApi.Tests.V1.E2ETests.Steps +{ + public class GetTransactionsSteps : BaseSteps + { + private const string BaseTransactionsRoute = "api/v1/search/transactions"; + public GetTransactionsSteps(HttpClient httpClient) : base(httpClient) + { + } + + public async Task WhenRequestDoesNotContainSearchString() + { + _lastResponse = await _httpClient.GetAsync(new Uri(BaseTransactionsRoute, UriKind.Relative)).ConfigureAwait(false); + } + + public async Task WhenRequestContainsSearchText(string searchText = null) + { + _lastResponse = await _httpClient.GetAsync(new Uri($"{BaseTransactionsRoute}?searchText={searchText}", UriKind.Relative)).ConfigureAwait(false); + } + + public async Task WhenAPageSizeIsProvided(int pageSize) + { + var route = new Uri($"{BaseTransactionsRoute}?searchText={TransactionsFixture.Senders.First().FullName}&pageSize={pageSize}", + UriKind.Relative); + + _lastResponse = await _httpClient.GetAsync(route).ConfigureAwait(false); + } + + public async Task ThenThatTextShouldBeInTheResult(string searchText) + { + var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + + if (_lastResponse.StatusCode == HttpStatusCode.BadRequest) + { + throw new Exception(resultBody); + } + + _lastResponse.StatusCode.Should().Be(HttpStatusCode.OK); + var parsedResponse = JsonSerializer.Deserialize<APIResponse<TransactionListDTO>>(resultBody, _jsonOptions); + + parsedResponse.Should().NotBeNull(); + parsedResponse.Results.Should().NotBeNull(); + parsedResponse.Results.Transactions.Should().NotBeNull(); + + var transactions = parsedResponse.Results.Transactions; + + transactions.All(t => + t.Sender == null || t.Sender.FullName.SafeContains(searchText) || + t.TransactionType.ToString().SafeContains(searchText) || + t.PaymentReference.SafeContains(searchText) || + t.BankAccountNumber.SafeContains(searchText) || + t.TransactionAmount.ToString().SafeContains(searchText)); + } + + public async Task ThenTheReturningResultsShouldBeOfThatSize(int pageSize) + { + var resultBody = await _lastResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + var parsedResponse = JsonSerializer.Deserialize<APIResponse<TransactionListDTO>>(resultBody, _jsonOptions); + + parsedResponse.Should().NotBeNull(); + parsedResponse.Results.Should().NotBeNull(); + parsedResponse.Results.Transactions.Should().NotBeNull(); + + parsedResponse.Results.Transactions.Count.Should().BeLessOrEqualTo(pageSize); + } + } +} diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/PersonDTO.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/PersonDTO.cs new file mode 100644 index 00000000..aa5656a9 --- /dev/null +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/PersonDTO.cs @@ -0,0 +1,7 @@ +namespace HousingSearchApi.Tests.V1.E2ETests.Steps.ResponseModels +{ + public class PersonDTO + { + public string FullName { get; set; } + } +} diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/TransactionDTO.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/TransactionDTO.cs new file mode 100644 index 00000000..0cecd8a7 --- /dev/null +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/TransactionDTO.cs @@ -0,0 +1,47 @@ +using System; + +namespace HousingSearchApi.Tests.V1.E2ETests.Steps.ResponseModels +{ + public class TransactionDTO + { + public Guid Id { get; set; } + + public Guid TargetId { get; set; } + + public Hackney.Shared.HousingSearch.Domain.Transactions.TargetType TargetType { get; set; } + + public short PeriodNo { get; set; } + + public short FinancialYear { get; set; } + + public short FinancialMonth { get; set; } + + public string TransactionSource { get; set; } + + public Hackney.Shared.HousingSearch.Domain.Transactions.TransactionType TransactionType { get; set; } + + public DateTime TransactionDate { get; set; } + + public PersonDTO Sender { get; set; } + + public decimal TransactionAmount { get; set; } + + public string PaymentReference { get; set; } + + public string BankAccountNumber { get; set; } + + public bool IsSuspense { get; set; } + + public decimal PaidAmount { get; set; } + + public decimal ChargedAmount { get; set; } + + public decimal BalanceAmount { get; set; } + + public decimal HousingBenefitAmount { get; set; } + + public string Address { get; set; } + + public string Fund { get; set; } + } +} diff --git a/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/TransactionListDTO.cs b/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/TransactionListDTO.cs new file mode 100644 index 00000000..d7030fdb --- /dev/null +++ b/HousingSearchApi.Tests/V1/E2ETests/Steps/ResponseModels/TransactionListDTO.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace HousingSearchApi.Tests.V1.E2ETests.Steps.ResponseModels +{ + public class TransactionListDTO + { + public long Total { get; } + + public List<TransactionDTO> Transactions { get; set; } + } +} diff --git a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetAssetStories.cs b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetAssetStories.cs index b850f2af..7116bedf 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetAssetStories.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetAssetStories.cs @@ -32,7 +32,7 @@ public void ServiceReturnsBadResult() { this.Given(g => _assetsFixture.GivenAnAssetIndexExists()) .When(w => _steps.WhenRequestDoesNotContainSearchString()) - .Then(t => _steps.ThenTheLastRequestShouldBeBadRequestResult()) + .Then(t => _steps.ThenTheLastRequestShouldBeBadRequestResult(default)) .BDDfy(); } @@ -64,6 +64,27 @@ public void ServiceFiltersGivenAssetTypes() .BDDfy(); } + [Fact] + public void ServiceFiltersGivenAssetTypesWithSearchTextStarStar() + { + var asset = "Dwelling"; + this.Given(g => _assetsFixture.GivenAnAssetIndexExists()) + .When(w => _steps.WhenSearchTextProvidedAsStarStarAndAssetTypeProvidedAndLastHitIdNotProvided(asset)) + .Then(d => _steps.ThenOnlyLastHitIdShouldBeIncluded()) + .Then(t => _steps.ThenOnlyAllAssetsResponseTheseAssetTypesShouldBeIncluded(asset)) + .BDDfy(); + } + + [Fact] + public void ServiceFiltersGivenAssetTypesWithSearchTextStarStarAndGivenLastHitId() + { + var asset = "Dwelling"; + this.Given(g => _assetsFixture.GivenAnAssetIndexExists()) + .When(w => _steps.WhenSearchTextProvidedAsStarStarAndAssetTypeProvidedAndLastHitIdProvided(asset)) + .Then(t => _steps.ThenOnlyAllAssetsResponseTheseAssetTypesShouldBeIncluded(asset)) + .BDDfy(); + } + [Fact] public void ServiceReturnsExactMatchFirstIfExists() { diff --git a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetPersonsStories.cs b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetPersonsStories.cs index 62c965a0..6318aa3c 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetPersonsStories.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetPersonsStories.cs @@ -8,7 +8,7 @@ namespace HousingSearchApi.Tests.V1.E2ETests.Stories { [Story( AsA = "Service", - IWant = "The Person Search Endpoint to return results", + IWant = "The Sender Search Endpoint to return results", SoThat = "it is possible to search for persons")] [Collection("ElasticSearch collection")] public class GetPersonsStories @@ -32,7 +32,7 @@ public void ServiceReturnsBadResult() { this.Given(g => _personsFixture.GivenAPersonIndexExists()) .When(w => _steps.WhenRequestDoesNotContainSearchString()) - .Then(t => _steps.ThenTheLastRequestShouldBeBadRequestResult()) + .Then(t => _steps.ThenTheLastRequestShouldBeBadRequestResult("'Search Text' must not be empty.")) .BDDfy(); } diff --git a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTenureStories.cs b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTenureStories.cs index 38183e15..6f57c3d2 100644 --- a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTenureStories.cs +++ b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTenureStories.cs @@ -31,7 +31,7 @@ public void ServiceReturnsBadResult() { this.Given(g => _tenureFixture.GivenATenureIndexExists()) .When(w => _steps.WhenRequestDoesNotContainSearchString()) - .Then(t => _steps.ThenTheLastRequestShouldBeBadRequestResult()) + .Then(t => _steps.ThenTheLastRequestShouldBeBadRequestResult(default)) .BDDfy(); } diff --git a/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTransactionsStories.cs b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTransactionsStories.cs new file mode 100644 index 00000000..756069f6 --- /dev/null +++ b/HousingSearchApi.Tests/V1/E2ETests/Stories/GetTransactionsStories.cs @@ -0,0 +1,65 @@ +using System.Linq; +using HousingSearchApi.Tests.V1.E2ETests.Fixtures; +using HousingSearchApi.Tests.V1.E2ETests.Steps; +using TestStack.BDDfy; +using Xunit; + +namespace HousingSearchApi.Tests.V1.E2ETests.Stories +{ + [Story( + AsA = "Service", + IWant = "The Transactions Search Endpoint to return results", + SoThat = "it is possible to search for transactions")] + [Collection("ElasticSearch collection")] + public class GetTransactionsStories + { + private readonly TransactionsFixture _transactionsFixture; + private readonly GetTransactionsSteps _transactionsSteps; + + public GetTransactionsStories(MockWebApplicationFactory<Startup> factory) + { + var httpClient = factory.CreateClient(); + var elasticClient = factory.ElasticSearchClient; + + _transactionsSteps = new GetTransactionsSteps(httpClient); + _transactionsFixture = new TransactionsFixture(elasticClient, httpClient); + } + + [Fact] + + public void ServiceReturnsBadResult() + { + this.Given(_ => _transactionsFixture.GivenAnAssetIndexExists()) + .When(_ => _transactionsSteps.WhenRequestDoesNotContainSearchString()) + .Then(_ => _transactionsSteps.ThenTheLastRequestShouldBe200()) + .BDDfy(); + } + + [Fact] + public void ServiceReturnsOkWithData() + { + this.Given(_ => _transactionsFixture.GivenAnAssetIndexExists()) + .When(_ => _transactionsSteps.WhenRequestContainsSearchText("some wrong search string")) + .Then(_ => _transactionsSteps.ThenTheLastRequestShouldBe200()) + .BDDfy(); + } + + [Fact] + public void ServiceReturnsOkWithExactPageSize() + { + this.Given(_ => _transactionsFixture.GivenAnAssetIndexExists()) + .When(_ => _transactionsSteps.WhenAPageSizeIsProvided(10)) + .Then(_ => _transactionsSteps.ThenTheReturningResultsShouldBeOfThatSize(10)) + .BDDfy(); + } + + [Fact] + public void ServiceReturnsOkWithMatchesByFullName() + { + this.Given(_ => _transactionsFixture.GivenAnAssetIndexExists()) + .When(_ => _transactionsSteps.WhenRequestContainsSearchText(TransactionsFixture.Senders.First().FullName)) + .Then(_ => _transactionsSteps.ThenThatTextShouldBeInTheResult(TransactionsFixture.Senders.First().FullName)) + .BDDfy(); + } + } +} diff --git a/HousingSearchApi.Tests/V1/Factories/DomainFactoryTests.cs b/HousingSearchApi.Tests/V1/Factories/DomainFactoryTests.cs new file mode 100644 index 00000000..53b54f13 --- /dev/null +++ b/HousingSearchApi.Tests/V1/Factories/DomainFactoryTests.cs @@ -0,0 +1,29 @@ +using AutoFixture; +using FluentAssertions; +using Hackney.Shared.HousingSearch.Domain.Transactions; +using HousingSearchApi.V1.Factories; +using Xunit; + +namespace HousingSearchApi.Tests.V1.Factories +{ + public class DomainFactoryTests + { + private readonly Fixture _fixture = new Fixture(); + + [Fact] + public void CanMapASharedDomainSuspenseResolutionInfoObjectToADomainObject() + { + var sharedDomain = _fixture.Create<SuspenseResolutionInfo>(); + var domain = sharedDomain.ToDomain(); + sharedDomain.Should().BeEquivalentTo(domain); + } + + [Fact] + public void CanMapASharedDomainPersonTypeObjectToADomainObject() + { + var sharedDomain = _fixture.Create<Sender>(); + var domain = sharedDomain.ToDomain(); + sharedDomain.Should().BeEquivalentTo(domain); + } + } +} diff --git a/HousingSearchApi.Tests/V1/Factories/ResponseFactoryTests.cs b/HousingSearchApi.Tests/V1/Factories/ResponseFactoryTests.cs new file mode 100644 index 00000000..d86abef1 --- /dev/null +++ b/HousingSearchApi.Tests/V1/Factories/ResponseFactoryTests.cs @@ -0,0 +1,55 @@ +using AutoFixture; +using FluentAssertions; +using Hackney.Shared.HousingSearch.Domain.Transactions; +using HousingSearchApi.V1.Factories; +using System.Collections.Generic; +using Xunit; + +namespace HousingSearchApi.Tests.V1.Factories +{ + public class ResponseFactoryTests + { + private readonly Fixture _fixture = new Fixture(); + + [Fact] + public void CanMapANullTransactionResponseObjectToAResponseObject() + { + Transaction domain = null; + var response = domain.ToResponse(); + + response.Should().BeNull(); + } + + [Fact] + public void CanMapATransactionResponseObjectToAResponseObject() + { + var domain = _fixture.Create<Transaction>(); + var response = domain.ToResponse(); + domain.Should().BeEquivalentTo(response); + + } + + [Fact] + public void CanMapDomainTransactionResponseObjectListToAResponsesList() + { + var list = _fixture.CreateMany<Transaction>(10); + var responseNotes = list.ToResponse(); + + responseNotes.Should().BeEquivalentTo(list, + options => options + .Excluding(t => t.CreatedBy) + .Excluding(t => t.LastUpdatedBy) + .Excluding(t => t.CreatedAt) + .Excluding(t => t.LastUpdatedAt)); + } + + [Fact] + public void CanMapNullDomainTransactionResponseObjectListToAnEmptyResponsesList() + { + List<Transaction> list = null; + var responseNotes = list.ToResponse(); + + responseNotes.Should().BeEmpty(); + } + } +} diff --git a/HousingSearchApi.Tests/V1/Helper/AssetListSetsSortFactoryTests.cs b/HousingSearchApi.Tests/V1/Helper/AssetListSetsSortFactoryTests.cs new file mode 100644 index 00000000..59bd2c8e --- /dev/null +++ b/HousingSearchApi.Tests/V1/Helper/AssetListSetsSortFactoryTests.cs @@ -0,0 +1,48 @@ +using FluentAssertions; +using Hackney.Shared.HousingSearch.Gateways.Models.Assets; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Sorting; +using HousingSearchApi.V1.Infrastructure.Sorting; +using Xunit; + +namespace HousingSearchApi.Tests.V1.Helper +{ + public class AssetListSetsSortFactoryTests + { + private SortFactory _sut; + + public AssetListSetsSortFactoryTests() + { + _sut = new SortFactory(); + } + [Fact] + public void ShouldNotSortAsDefault() + { + // Arrange + Act + var result = _sut.Create<QueryableAsset, GetAssetListRequest>(new GetAssetListRequest()); + + // Assert + result.Should().BeOfType(typeof(DefaultSort<QueryableAsset>)); + } + + [Fact] + public void ShouldReturnAssetIdAscWhenRequestAssetIdAndAsc() + { + // Arrange + Act + var result = _sut.Create<QueryableAsset, GetAssetListRequest>(new GetAssetListRequest { SortBy = "assetId", IsDesc = false }); + + // Assert + result.Should().BeOfType(typeof(AssetIdAsc)); + } + + [Fact] + public void ShouldReturnAssetIdDescWhenRequestAssetIdAndDesc() + { + // Arrange + Act + var result = _sut.Create<QueryableAsset, GetAssetListRequest>(new GetAssetListRequest { SortBy = "assetId", IsDesc = true }); + + // Assert + result.Should().BeOfType(typeof(AssetIdDesc)); + } + } +} diff --git a/HousingSearchApi.Tests/V1/Helper/PagingHelperTests.cs b/HousingSearchApi.Tests/V1/Helper/PagingHelperTests.cs index 95884c35..cb124473 100644 --- a/HousingSearchApi.Tests/V1/Helper/PagingHelperTests.cs +++ b/HousingSearchApi.Tests/V1/Helper/PagingHelperTests.cs @@ -1,6 +1,6 @@ using System; using FluentAssertions; -using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.Infrastructure; using Xunit; namespace HousingSearchApi.Tests.V1.Helper diff --git a/HousingSearchApi.Tests/V1/Helper/PersonListSortFactoryTests.cs b/HousingSearchApi.Tests/V1/Helper/PersonListSortFactoryTests.cs index 8a8dea28..5ab4cfe3 100644 --- a/HousingSearchApi.Tests/V1/Helper/PersonListSortFactoryTests.cs +++ b/HousingSearchApi.Tests/V1/Helper/PersonListSortFactoryTests.cs @@ -1,7 +1,7 @@ using FluentAssertions; using Hackney.Shared.HousingSearch.Gateways.Models.Persons; using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Interfaces.Sorting; +using HousingSearchApi.V1.Infrastructure.Sorting; using Xunit; namespace HousingSearchApi.Tests.V1.Helper @@ -19,7 +19,7 @@ public PersonListSortFactoryTests() public void ShouldNotSortAsDefault() { // Arrange + Act - var result = _sut.Create<QueryablePerson>(new HousingSearchRequest()); + var result = _sut.Create<QueryablePerson, GetPersonListRequest>(new GetPersonListRequest()); // Assert result.Should().BeOfType(typeof(DefaultSort<QueryablePerson>)); @@ -29,7 +29,7 @@ public void ShouldNotSortAsDefault() public void ShouldReturnSurnameAscWhenRequestSurnameAndAsc() { // Arrange + Act - var result = _sut.Create<QueryablePerson>(new HousingSearchRequest { SortBy = "surname", IsDesc = false }); + var result = _sut.Create<QueryablePerson, GetPersonListRequest>(new GetPersonListRequest { SortBy = "surname", IsDesc = false }); // Assert result.Should().BeOfType(typeof(SurnameAsc)); @@ -39,7 +39,7 @@ public void ShouldReturnSurnameAscWhenRequestSurnameAndAsc() public void ShouldReturnSurnameDescWhenRequestSurnameAndDesc() { // Arrange + Act - var result = _sut.Create<QueryablePerson>(new HousingSearchRequest { SortBy = "surname", IsDesc = true }); + var result = _sut.Create<QueryablePerson, GetPersonListRequest>(new GetPersonListRequest { SortBy = "surname", IsDesc = true }); // Assert result.Should().BeOfType(typeof(SurnameDesc)); diff --git a/HousingSearchApi.Tests/V1/Helper/SearchPhraseTests.cs b/HousingSearchApi.Tests/V1/Helper/SearchPhraseTests.cs index 74b8ab53..022a169c 100644 --- a/HousingSearchApi.Tests/V1/Helper/SearchPhraseTests.cs +++ b/HousingSearchApi.Tests/V1/Helper/SearchPhraseTests.cs @@ -1,9 +1,9 @@ +using System; using FluentAssertions; using Hackney.Core.ElasticSearch; using Hackney.Shared.HousingSearch.Gateways.Models.Persons; using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Infrastructure; -using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.Infrastructure.Factories; using Nest; using Xunit; @@ -25,10 +25,11 @@ public SearchPhraseTests() public void ShouldReturnNullIfRequestTypeIsUnknown(string searchText) { // Arrange + Act - var result = _sut.Create(new HousingSearchRequest { SearchText = searchText }, new QueryContainerDescriptor<QueryablePerson>()); + QueryContainer Func() => _sut.Create<HousingSearchRequest>(new GetAccountListRequest { SearchText = searchText }, new QueryContainerDescriptor<QueryablePerson>()); // Assert - result.Should().BeNull(); + Exception ex = Assert.Throws<ArgumentNullException>((Func<QueryContainer>) Func); + } } } diff --git a/HousingSearchApi.Tests/V1/Infrastructure/ElasticSearchExtensionsTests.cs b/HousingSearchApi.Tests/V1/Infrastructure/ElasticSearchExtensionsTests.cs index 4b6c7066..4a8ed060 100644 --- a/HousingSearchApi.Tests/V1/Infrastructure/ElasticSearchExtensionsTests.cs +++ b/HousingSearchApi.Tests/V1/Infrastructure/ElasticSearchExtensionsTests.cs @@ -1,11 +1,11 @@ using FluentAssertions; -using HousingSearchApi.V1.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Moq; using Nest; using System; using System.Linq; +using HousingSearchApi.V1.Infrastructure.Extensions; using Xunit; namespace HousingSearchApi.Tests.V1.Infrastructure @@ -14,7 +14,7 @@ public class ElasticSearchExtensionsTests { private readonly Mock<IConfiguration> _mockConfiguration; private const string ConfigKey = "ELASTICSEARCH_DOMAIN_URL"; - private const string EsNodeUrl = "http://somedomain:9200"; + private const string EsNodeUrl = "http://localhost:9200"; public ElasticSearchExtensionsTests() { diff --git a/HousingSearchApi.Tests/V1/Interfaces/IndexSelectorTests.cs b/HousingSearchApi.Tests/V1/Interfaces/IndexSelectorTests.cs index 8cc0b1a2..418ac53e 100644 --- a/HousingSearchApi.Tests/V1/Interfaces/IndexSelectorTests.cs +++ b/HousingSearchApi.Tests/V1/Interfaces/IndexSelectorTests.cs @@ -2,7 +2,7 @@ using Hackney.Shared.HousingSearch.Gateways.Models.Assets; using Hackney.Shared.HousingSearch.Gateways.Models.Persons; using Hackney.Shared.HousingSearch.Gateways.Models.Tenures; -using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.Infrastructure; using Xunit; namespace HousingSearchApi.Tests.V1.Interfaces diff --git a/HousingSearchApi.Tests/V1/Interfaces/SortFactoryTests.cs b/HousingSearchApi.Tests/V1/Interfaces/SortFactoryTests.cs index d28eedb7..b8f9bf3b 100644 --- a/HousingSearchApi.Tests/V1/Interfaces/SortFactoryTests.cs +++ b/HousingSearchApi.Tests/V1/Interfaces/SortFactoryTests.cs @@ -1,6 +1,6 @@ using FluentAssertions; using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Interfaces.Sorting; +using HousingSearchApi.V1.Infrastructure.Sorting; using Xunit; namespace HousingSearchApi.Tests.V1.Interfaces @@ -18,7 +18,7 @@ public SortFactoryTests() public void GivenARequestShouldReturnDefaultSortForUnknownType() { // Arrange + act - var result = _sut.Create<SomeUnknownType>(new HousingSearchRequest()); + var result = _sut.Create<SomeUnknownType, GetPersonListRequest>(new GetPersonListRequest()); // Assert result.Should().BeOfType<DefaultSort<SomeUnknownType>>(); diff --git a/HousingSearchApi/HousingSearchApi.csproj b/HousingSearchApi/HousingSearchApi.csproj index 16d462df..2d2c26c5 100644 --- a/HousingSearchApi/HousingSearchApi.csproj +++ b/HousingSearchApi/HousingSearchApi.csproj @@ -10,12 +10,6 @@ <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild> </PropertyGroup> - <ItemGroup> - <Compile Remove="V1\Factories\**" /> - <Content Remove="V1\Factories\**" /> - <EmbeddedResource Remove="V1\Factories\**" /> - <None Remove="V1\Factories\**" /> - </ItemGroup> <ItemGroup> <PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="5.1.1" /> @@ -31,7 +25,7 @@ <PackageReference Include="Hackney.Core.Logging" Version="1.30.0" /> <PackageReference Include="Hackney.Core.Middleware" Version="1.30.0" /> <PackageReference Include="Hackney.Core.Validation" Version="1.30.0" /> - <PackageReference Include="Hackney.Shared.HousingSearch" Version="0.6.0" /> + <PackageReference Include="Hackney.Shared.HousingSearch" Version="0.19.0" /> <PackageReference Include="Microsoft.AspNetCore.HealthChecks" Version="1.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="4.1.1" /> diff --git a/HousingSearchApi/Properties/launchSettings.json b/HousingSearchApi/Properties/launchSettings.json index 84eb1bf1..7ec4841f 100644 --- a/HousingSearchApi/Properties/launchSettings.json +++ b/HousingSearchApi/Properties/launchSettings.json @@ -1,5 +1,4 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, @@ -8,15 +7,16 @@ "sslPort": 44394 } }, + "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "housing_search_api": { "commandName": "Project", "launchBrowser": true, "launchUrl": "api/v1/healthcheck/ping", - "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "LocalDevelopment" - } + }, + "applicationUrl": "http://localhost:5003" } } -} +} \ No newline at end of file diff --git a/HousingSearchApi/Startup.cs b/HousingSearchApi/Startup.cs index 5becac1d..2713ac16 100644 --- a/HousingSearchApi/Startup.cs +++ b/HousingSearchApi/Startup.cs @@ -34,6 +34,14 @@ using System.Reflection; using Hackney.Core.ElasticSearch; using Hackney.Core.ElasticSearch.Interfaces; +using HousingSearchApi.V1.Gateways.Interfaces; +using HousingSearchApi.V1.Gateways; +using HousingSearchApi.V1.Infrastructure.Extensions; +using HousingSearchApi.V1.Infrastructure.Factories; +using HousingSearchApi.V1.Infrastructure.Filtering; +using HousingSearchApi.V1.Infrastructure.Sorting; +using HousingSearchApi.V1.Interfaces.Factories; +using HousingSearchApi.V1.Interfaces.Filtering; namespace HousingSearchApi { @@ -158,16 +166,21 @@ public void ConfigureServices(IServiceCollection services) private static void RegisterGateways(IServiceCollection services) { services.AddScoped<ISearchGateway, SearchGateway>(); + services.AddScoped<IGetAccountGateway, GetAccountGateway>(); } private static void RegisterUseCases(IServiceCollection services) { services.AddScoped<IGetPersonListUseCase, GetPersonListUseCase>(); + services.AddScoped<IGetAccountListUseCase, GetAccountListUseCase>(); services.AddScoped<IGetTenureListUseCase, GetTenureListUseCase>(); - services.AddScoped<IElasticSearchWrapper, ElasticElasticSearchWrapper>(); + services.AddScoped<IElasticSearchWrapper, ElasticSearchWrapper>(); services.AddScoped<IPagingHelper, PagingHelper>(); services.AddScoped<ISortFactory, SortFactory>(); + services.AddScoped<IFilterFactory, FilterFactory>(); services.AddScoped<IGetAssetListUseCase, GetAssetListUseCase>(); + services.AddScoped<IGetAssetListSetsUseCase, GetAssetListSetsUseCase>(); + services.AddScoped<IGetTransactionListUseCase, GetTransactionListUseCase>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/HousingSearchApi/V1/Boundary/Requests/GetAccountListRequest.cs b/HousingSearchApi/V1/Boundary/Requests/GetAccountListRequest.cs new file mode 100644 index 00000000..61c7a910 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/GetAccountListRequest.cs @@ -0,0 +1,7 @@ +namespace HousingSearchApi.V1.Boundary.Requests +{ + public class GetAccountListRequest : HousingSearchRequest + { + + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/GetAllAssetListRequest.cs b/HousingSearchApi/V1/Boundary/Requests/GetAllAssetListRequest.cs new file mode 100644 index 00000000..63d69624 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/GetAllAssetListRequest.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc; + +namespace HousingSearchApi.V1.Boundary.Requests +{ + public class GetAllAssetListRequest : GetAssetListRequest + { + [FromQuery(Name = "lastHitId")] + public string LastHitId { get; set; } + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/GetAssetListRequest.cs b/HousingSearchApi/V1/Boundary/Requests/GetAssetListRequest.cs new file mode 100644 index 00000000..16998907 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/GetAssetListRequest.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc; + +namespace HousingSearchApi.V1.Boundary.Requests +{ + public class GetAssetListRequest : HousingSearchRequest + { + [FromQuery(Name = "assetTypes")] + public string AssetTypes { get; set; } + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/GetTenureListRequest.cs b/HousingSearchApi/V1/Boundary/Requests/GetTenureListRequest.cs new file mode 100644 index 00000000..be3a1b23 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/GetTenureListRequest.cs @@ -0,0 +1,7 @@ +namespace HousingSearchApi.V1.Boundary.Requests +{ + public class GetTenureListRequest : HousingSearchRequest + { + + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/GetTransactionListRequest.cs b/HousingSearchApi/V1/Boundary/Requests/GetTransactionListRequest.cs new file mode 100644 index 00000000..25710691 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/GetTransactionListRequest.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using System; + +namespace HousingSearchApi.V1.Boundary.Requests +{ + public class GetTransactionListRequest : HousingSearchRequest + { + /// <summary> + /// Date by which filtering begins + /// </summary> + /// <example> + /// 2020-04-27 + /// </example> + [FromQuery(Name = "startDate")] + public DateTime? StartDate { get; set; } + + /// <summary> + /// Date by which filtering ends + /// </summary> + /// <example> + /// 2021-12-01 + /// </example> + [FromQuery(Name = "endDate")] + public DateTime? EndDate { get; set; } + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/HousingSearchRequest.cs b/HousingSearchApi/V1/Boundary/Requests/HousingSearchRequest.cs index 560e233b..866c1f4f 100644 --- a/HousingSearchApi/V1/Boundary/Requests/HousingSearchRequest.cs +++ b/HousingSearchApi/V1/Boundary/Requests/HousingSearchRequest.cs @@ -2,19 +2,28 @@ namespace HousingSearchApi.V1.Boundary.Requests { - public class HousingSearchRequest + public abstract class HousingSearchRequest { private const int DefaultPageSize = 12; + /// <summary> + /// Some search phrase. Can be empty to return all transactions + /// </summary> + /// <example>HSGSUN</example> [FromQuery(Name = "searchText")] public string SearchText { get; set; } - [FromQuery(Name = "assetTypes")] - public string AssetTypes { get; set; } - + /// <summary> + /// Page size. Default value is 12 + /// </summary> + /// <example>10</example> [FromQuery(Name = "pageSize")] public int PageSize { get; set; } = DefaultPageSize; + /// <summary> + /// Page number for pagination + /// </summary> + /// <example>1</example> [FromQuery(Name = "page")] public int Page { get; set; } @@ -24,7 +33,5 @@ public class HousingSearchRequest [FromQuery(Name = "isDesc")] public bool IsDesc { get; set; } - [FromQuery(Name = "propertyReference")] - public string PropertyReference { get; set; } } } diff --git a/HousingSearchApi/V1/Boundary/Requests/Validation/GetAccountListRequestValidator.cs b/HousingSearchApi/V1/Boundary/Requests/Validation/GetAccountListRequestValidator.cs new file mode 100644 index 00000000..c24979b5 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/Validation/GetAccountListRequestValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using Hackney.Core.Validation; + +namespace HousingSearchApi.V1.Boundary.Requests.Validation +{ + public class GetAccountListRequestValidator : AbstractValidator<GetAccountListRequest> + { + public GetAccountListRequestValidator() + { + RuleFor(x => x.SearchText).NotNull() + .NotEmpty() + .MinimumLength(2) + .NotXssString(); + RuleFor(x => x.PageSize).GreaterThan(0); + RuleFor(x => x.SortBy).NotXssString(); + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/Validation/GetAssetListRequestValidator.cs b/HousingSearchApi/V1/Boundary/Requests/Validation/GetAssetListRequestValidator.cs new file mode 100644 index 00000000..bf83c85d --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/Validation/GetAssetListRequestValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using Hackney.Core.Validation; + +namespace HousingSearchApi.V1.Boundary.Requests.Validation +{ + public class GetAssetListRequestValidator : AbstractValidator<GetAssetListRequest> + { + public GetAssetListRequestValidator() + { + RuleFor(x => x.SearchText).NotNull() + .NotEmpty() + .MinimumLength(2) + .NotXssString(); + RuleFor(x => x.PageSize).GreaterThan(0); + RuleFor(x => x.SortBy).NotXssString(); + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Requests/Validation/GetTenureListRequestValidator.cs b/HousingSearchApi/V1/Boundary/Requests/Validation/GetTenureListRequestValidator.cs new file mode 100644 index 00000000..557f9cda --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Requests/Validation/GetTenureListRequestValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; +using Hackney.Core.Validation; + +namespace HousingSearchApi.V1.Boundary.Requests.Validation +{ + public class GetTenureListRequestValidator : AbstractValidator<GetTenureListRequest> + { + public GetTenureListRequestValidator() + { + RuleFor(x => x.SearchText).NotNull() + .NotEmpty() + .MinimumLength(2) + .NotXssString(); + RuleFor(x => x.PageSize).GreaterThan(0); + RuleFor(x => x.SortBy).NotXssString(); + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/GetAccountListResponse.cs b/HousingSearchApi/V1/Boundary/Responses/GetAccountListResponse.cs new file mode 100644 index 00000000..2d50e361 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/GetAccountListResponse.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Hackney.Shared.HousingSearch.Domain.Accounts; + +namespace HousingSearchApi.V1.Boundary.Responses +{ + public class GetAccountListResponse + { + public static GetAccountListResponse Create(List<Account> accounts) + { + return new GetAccountListResponse(accounts); + } + + private GetAccountListResponse(List<Account> accounts) + { + Accounts = accounts; + } + + private long _total; + + public List<Account> Accounts { get; } + + public void SetTotal(long total) + { + _total = total < 0 ? 0 : total; + } + + public long Total() + { + return _total; + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/GetAllAssetListResponse.cs b/HousingSearchApi/V1/Boundary/Responses/GetAllAssetListResponse.cs new file mode 100644 index 00000000..10c537a5 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/GetAllAssetListResponse.cs @@ -0,0 +1,19 @@ +using System; + +namespace HousingSearchApi.V1.Boundary.Responses +{ + public class GetAllAssetListResponse : GetAssetListResponse + { + private string _lastHitId; + + public void SetLastHitId(string lastHitId) + { + _lastHitId = lastHitId; + } + + public string LastHitId() + { + return _lastHitId; + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/GetAssetListResponse.cs b/HousingSearchApi/V1/Boundary/Responses/GetAssetListResponse.cs index 1b7cbfdf..bc43bf97 100644 --- a/HousingSearchApi/V1/Boundary/Responses/GetAssetListResponse.cs +++ b/HousingSearchApi/V1/Boundary/Responses/GetAssetListResponse.cs @@ -16,7 +16,7 @@ public GetAssetListResponse() public void SetTotal(long total) { - _total = total; + _total = total < 0 ? 0 : total; } public long Total() diff --git a/HousingSearchApi/V1/Boundary/Responses/GetPersonListResponse.cs b/HousingSearchApi/V1/Boundary/Responses/GetPersonListResponse.cs index e91612cb..d1421d23 100644 --- a/HousingSearchApi/V1/Boundary/Responses/GetPersonListResponse.cs +++ b/HousingSearchApi/V1/Boundary/Responses/GetPersonListResponse.cs @@ -16,7 +16,7 @@ public GetPersonListResponse() public void SetTotal(long total) { - _total = total; + _total = total < 0 ? 0 : total; } public long Total() diff --git a/HousingSearchApi/V1/Boundary/Responses/GetTenureListResponse.cs b/HousingSearchApi/V1/Boundary/Responses/GetTenureListResponse.cs index 074014ea..2faf24b3 100644 --- a/HousingSearchApi/V1/Boundary/Responses/GetTenureListResponse.cs +++ b/HousingSearchApi/V1/Boundary/Responses/GetTenureListResponse.cs @@ -16,7 +16,7 @@ public GetTenureListResponse() public void SetTotal(long total) { - _total = total; + _total = total < 0 ? 0 : total; } public long Total() diff --git a/HousingSearchApi/V1/Boundary/Responses/Metadata/APIAllResponse.cs b/HousingSearchApi/V1/Boundary/Responses/Metadata/APIAllResponse.cs new file mode 100644 index 00000000..28055b09 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/Metadata/APIAllResponse.cs @@ -0,0 +1,19 @@ +namespace HousingSearchApi.V1.Boundary.Responses.Metadata +{ + public class APIAllResponse<T> where T : class + { + public string LastHitId { get; set; } + + public T Results { get; set; } + + public long Total { get; set; } + + public APIAllResponse() { } + + public APIAllResponse(T result) + { + Results = result; + } + + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/Transactions/GetTransactionListResponse.cs b/HousingSearchApi/V1/Boundary/Responses/Transactions/GetTransactionListResponse.cs new file mode 100644 index 00000000..6f59d2a0 --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/Transactions/GetTransactionListResponse.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace HousingSearchApi.V1.Boundary.Responses.Transactions +{ + public class GetTransactionListResponse + { + public long Total { get; } + public List<TransactionResponse> Transactions { get; } + + private GetTransactionListResponse(long total, IEnumerable<TransactionResponse> transactions) + { + if (total < 0) + { + throw new ArgumentException("Transactions count should be greater than or equals to 0.", nameof(total)); + } + + Total = total; + Transactions = new List<TransactionResponse>(transactions ?? + throw new ArgumentNullException(nameof(transactions), "Transactions cannot be null. Provide empty list if no transactions was found.")); + } + + public static GetTransactionListResponse Create(long total, IEnumerable<TransactionResponse> transactions) + { + return new GetTransactionListResponse(total, transactions); + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/Transactions/Sender.cs b/HousingSearchApi/V1/Boundary/Responses/Transactions/Sender.cs new file mode 100644 index 00000000..d54fc47f --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/Transactions/Sender.cs @@ -0,0 +1,22 @@ +using System; + +namespace HousingSearchApi.V1.Boundary.Responses.Transactions +{ + public class Sender + { + public Guid Id { get; } + + public string FullName { get; } + + private Sender(Guid id, string fullName) + { + Id = id; + FullName = fullName; + } + + public static Sender Create(Guid id, string fullName) + { + return new Sender(id, fullName); + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/Transactions/SuspenseResolutionInfo.cs b/HousingSearchApi/V1/Boundary/Responses/Transactions/SuspenseResolutionInfo.cs new file mode 100644 index 00000000..89b9c99b --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/Transactions/SuspenseResolutionInfo.cs @@ -0,0 +1,28 @@ +using System; + +namespace HousingSearchApi.V1.Boundary.Responses.Transactions +{ + public class SuspenseResolutionInfo + { + public DateTime? ResolutionDate { get; } + public bool IsConfirmed { get; } + public bool IsApproved { get; } + public string Note { get; } + + public bool IsResolve + => IsConfirmed && IsApproved; + + private SuspenseResolutionInfo(DateTime? resolutionDate, bool isConfirmed, bool isApproved, string note) + { + ResolutionDate = resolutionDate; + IsConfirmed = isConfirmed; + IsApproved = isApproved; + Note = note; + } + + public static SuspenseResolutionInfo Create(DateTime? resolutionDate, bool isConfirmed, bool isApproved, string note) + { + return new SuspenseResolutionInfo(resolutionDate, isConfirmed, isApproved, note); + } + } +} diff --git a/HousingSearchApi/V1/Boundary/Responses/Transactions/TransactionResponse.cs b/HousingSearchApi/V1/Boundary/Responses/Transactions/TransactionResponse.cs new file mode 100644 index 00000000..048686ca --- /dev/null +++ b/HousingSearchApi/V1/Boundary/Responses/Transactions/TransactionResponse.cs @@ -0,0 +1,218 @@ +using Hackney.Shared.HousingSearch.Domain.Transactions; +using System; + +namespace HousingSearchApi.V1.Boundary.Responses.Transactions +{ + public class TransactionResponse + { + /// <summary> + /// The guid of a record + /// </summary> + /// <example> + /// 2f378d65-38d3-4fb4-877b-afeee666209e + /// </example> + public Guid Id { get; } + + /// <summary> + /// The guid of a tenancy/property + /// </summary> + /// <example> + /// 94b02545-0233-4640-98dd-b2900423c0a5 + /// </example> + public Guid TargetId { get; } + + /// <summary> + /// The target of provided id by target_id + /// </summary> + /// <example> + /// Asset + /// </example> + public TargetType TargetType { get; } + /// <summary> + /// Week number for Rent and Period number for LeaseHolders + /// </summary> + /// <example> + /// 2 + /// </example> + public short PeriodNo { get; } + + /// <summary> + /// Financial year of transaction + /// </summary> + /// <example> + /// 2022 + /// </example> + public short FinancialYear { get; } + + /// <summary> + /// Financial Month of transaction + /// </summary> + /// <example> + /// 1 + /// </example> + public short FinancialMonth { get; } + + /// <summary> + /// Transaction Information + /// </summary> + /// <example> + /// DD + /// </example> + public string TransactionSource { get; } + + /// <summary> + /// Type of transaction + /// </summary> + /// <example> + /// Rent + /// </example> + public TransactionType TransactionType { get; } + + /// <summary> + /// Date of transaction + /// </summary> + /// <example> + /// 2021-04-27T23:00:00.000Z + /// </example> + public DateTime TransactionDate { get; } + + /// <summary> + /// Amount of Transaction + /// </summary> + /// <example> + /// 56.78 + /// </example> + public decimal TransactionAmount { get; } + + /// <summary> + /// Same as Rent Account Number + /// </summary> + /// <example> + /// 216704 + /// </example> + public string PaymentReference { get; } + + /// <summary> + /// Partially filled bank account number + /// </summary> + /// <example> + /// ******78 + /// </example> + public string BankAccountNumber { get; } + + /// <summary> + /// Is this account need to be in suspense + /// </summary> + /// <example> + /// true + /// </example> + public bool IsSuspense { get; } + + /// <summary> + /// Information after this recond ceases to be suspense + /// </summary> + /// <example> + /// { + /// "ResolutionDate": "2021-04-28T23:00:00.000Z", + /// "IsResolve" : true, + /// "Note": "Some notes about this record" + /// } + /// </example> + public SuspenseResolutionInfo SuspenseResolutionInfo { get; } + + /// <summary> + /// Total paid amount + /// </summary> + /// <example> + /// 56.78 + /// </example> + public decimal PaidAmount { get; } + + /// <summary> + /// Total charged amount + /// </summary> + /// <example> + /// 87.53 + /// </example> + public decimal ChargedAmount { get; } + + /// <summary> + /// Total balance amount + /// </summary> + /// <example> + /// 1025.00 + /// </example> + public decimal BalanceAmount { get; } + + /// <summary> + /// Housing Benefit Contribution + /// </summary> + /// <example> + /// 25.56 + /// </example> + public decimal HousingBenefitAmount { get; } + + /// <summary> + /// Address of property + /// </summary> + /// <example> + /// Apartment 22, 18 G road, SW11 + /// </example> + public string Address { get; } + + /// <summary> + /// Sender, who paid for the transaction + /// </summary> + /// <example> + /// { + /// "Id": "6d290de9-75aa-46a9-8bf5-cb8e9bdf4ff0", + /// "FullName": "Kian Hayward" + /// } + /// </example> + public Sender Sender { get; } + + /// <summary> + /// ToDO: No information about this field + /// </summary> + /// <example> + /// HSGSUN + /// </example> + public string Fund { get; } + + private TransactionResponse(Guid id, Guid targetId, TargetType targetType, short periodNo, short financialYear, short financialMonth, string transactionSource, TransactionType transactionType, + DateTime transactionDate, decimal transactionAmount, string paymentReference, string bankAccountNumber, bool isSuspense, SuspenseResolutionInfo suspenseResolutionInfo, + decimal paidAmount, decimal chargedAmount, decimal balanceAmount, decimal housingBenefitAmount, string address, Sender sender, string fund) + { + Id = id; + TargetId = targetId; + TargetType = targetType; + PeriodNo = periodNo; + FinancialYear = financialYear; + FinancialMonth = financialMonth; + TransactionSource = transactionSource; + TransactionType = transactionType; + TransactionDate = transactionDate; + TransactionAmount = transactionAmount; + PaymentReference = paymentReference; + BankAccountNumber = bankAccountNumber; + IsSuspense = isSuspense; + SuspenseResolutionInfo = suspenseResolutionInfo; + PaidAmount = paidAmount; + ChargedAmount = chargedAmount; + BalanceAmount = balanceAmount; + HousingBenefitAmount = housingBenefitAmount; + Address = address; + Sender = sender; + Fund = fund; + } + + public static TransactionResponse Create(Guid id, Guid targetId, TargetType targetType, short periodNo, short financialYear, short financialMonth, string transactionSource, TransactionType transactionType, + DateTime transactionDate, decimal transactionAmount, string paymentReference, string bankAccountNumber, bool isSuspense, SuspenseResolutionInfo suspenseResolutionInfo, + decimal paidAmount, decimal chargedAmount, decimal balanceAmount, decimal housingBenefitAmount, string address, Sender person, string fund) + { + return new TransactionResponse(id, targetId, targetType, periodNo, financialYear, financialMonth, transactionSource, transactionType, transactionDate, transactionAmount, + paymentReference, bankAccountNumber, isSuspense, suspenseResolutionInfo, paidAmount, chargedAmount, balanceAmount, + housingBenefitAmount, address, person, fund); + } + } +} diff --git a/HousingSearchApi/V1/Controllers/GetAccountListController.cs b/HousingSearchApi/V1/Controllers/GetAccountListController.cs new file mode 100644 index 00000000..78f27d38 --- /dev/null +++ b/HousingSearchApi/V1/Controllers/GetAccountListController.cs @@ -0,0 +1,49 @@ +using Amazon.Lambda.Core; +using Hackney.Core.Logging; +using Hackney.Shared.HousingSearch.Domain.Accounts; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; +using HousingSearchApi.V1.Boundary.Responses.Metadata; +using HousingSearchApi.V1.UseCase.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; + +namespace HousingSearchApi.V1.Controllers +{ + [ApiVersion("1")] + [Produces("application/json")] + [Route("api/v1/search/accounts")] + [ApiController] + public class GetAccountListController : BaseController + { + private readonly IGetAccountListUseCase _getAccountListUseCase; + + public GetAccountListController(IGetAccountListUseCase getAccountListUseCase) + { + _getAccountListUseCase = getAccountListUseCase; + } + + [ProducesResponseType(typeof(APIResponse<Account>), 200)] + [ProducesResponseType(typeof(APIResponse<NotFoundException>), 404)] + [ProducesResponseType(typeof(APIResponse<BadRequestException>), 400)] + [HttpGet, MapToApiVersion("1")] + [LogCall(LogLevel.Information)] + public async Task<IActionResult> GetAccountList([FromQuery] GetAccountListRequest request) + { + try + { + var accountSearchResult = await _getAccountListUseCase.ExecuteAsync(request).ConfigureAwait(false); + var apiResponse = new APIResponse<GetAccountListResponse>(accountSearchResult) { Total = accountSearchResult.Total() }; + + return new OkObjectResult(apiResponse); + } + catch (Exception e) + { + LambdaLogger.Log(e.Message + e.StackTrace); + return new BadRequestObjectResult(e.Message); + } + } + } +} diff --git a/HousingSearchApi/V1/Controllers/GetAssetListController.cs b/HousingSearchApi/V1/Controllers/GetAssetListController.cs index e0b785a1..8514e478 100644 --- a/HousingSearchApi/V1/Controllers/GetAssetListController.cs +++ b/HousingSearchApi/V1/Controllers/GetAssetListController.cs @@ -18,10 +18,12 @@ namespace HousingSearchApi.V1.Controllers public class GetAssetListController : BaseController { private readonly IGetAssetListUseCase _getAssetListUseCase; + private readonly IGetAssetListSetsUseCase _getAssetListSetsUseCase; - public GetAssetListController(IGetAssetListUseCase getAssetListUseCase) + public GetAssetListController(IGetAssetListUseCase getAssetListUseCase, IGetAssetListSetsUseCase getAssetListSetsUseCase) { _getAssetListUseCase = getAssetListUseCase; + _getAssetListSetsUseCase = getAssetListSetsUseCase; } [ProducesResponseType(typeof(APIResponse<GetAssetListResponse>), 200)] @@ -29,7 +31,7 @@ public GetAssetListController(IGetAssetListUseCase getAssetListUseCase) [ProducesResponseType(typeof(APIResponse<BadRequestException>), 400)] [HttpGet, MapToApiVersion("1")] [LogCall(LogLevel.Information)] - public async Task<IActionResult> GetAssetList([FromQuery] HousingSearchRequest request) + public async Task<IActionResult> GetAssetList([FromQuery] GetAssetListRequest request) { try { @@ -45,5 +47,31 @@ public async Task<IActionResult> GetAssetList([FromQuery] HousingSearchRequest r return new BadRequestObjectResult(e.Message); } } + + [ProducesResponseType(typeof(APIResponse<GetAllAssetListResponse>), 200)] + [ProducesResponseType(typeof(APIResponse<NotFoundException>), 404)] + [ProducesResponseType(typeof(APIResponse<BadRequestException>), 400)] + [Route("all")] + [HttpGet, MapToApiVersion("1")] + [LogCall(LogLevel.Information)] + public async Task<IActionResult> GetAllAssetList([FromQuery] GetAllAssetListRequest request) + { + try + { + var assetsSearchResult = await _getAssetListSetsUseCase.ExecuteAsync(request).ConfigureAwait(false); + var apiResponse = new APIAllResponse<GetAllAssetListResponse>(assetsSearchResult) + { + Total = assetsSearchResult.Total(), + LastHitId = assetsSearchResult.LastHitId() + }; + + return new OkObjectResult(apiResponse); + } + catch (Exception e) + { + LambdaLogger.Log(e.Message + e.StackTrace); + return new BadRequestObjectResult(e.Message); + } + } } } diff --git a/HousingSearchApi/V1/Controllers/GetTenureListController.cs b/HousingSearchApi/V1/Controllers/GetTenureListController.cs index a2b35df7..fe4021ec 100644 --- a/HousingSearchApi/V1/Controllers/GetTenureListController.cs +++ b/HousingSearchApi/V1/Controllers/GetTenureListController.cs @@ -29,7 +29,7 @@ public GetTenureListController(IGetTenureListUseCase getTenureListUseCase) [ProducesResponseType(typeof(APIResponse<BadRequestException>), 400)] [HttpGet, MapToApiVersion("1")] [LogCall(LogLevel.Information)] - public async Task<IActionResult> GetTenureList([FromQuery] HousingSearchRequest request) + public async Task<IActionResult> GetTenureList([FromQuery] GetTenureListRequest request) { try { diff --git a/HousingSearchApi/V1/Controllers/GetTransactionListController.cs b/HousingSearchApi/V1/Controllers/GetTransactionListController.cs new file mode 100644 index 00000000..bc12ac41 --- /dev/null +++ b/HousingSearchApi/V1/Controllers/GetTransactionListController.cs @@ -0,0 +1,60 @@ +using Amazon.Lambda.Core; +using Hackney.Core.Logging; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses.Metadata; +using HousingSearchApi.V1.Boundary.Responses.Transactions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Threading.Tasks; +using HousingSearchApi.V1.UseCase.Interfaces; + +namespace HousingSearchApi.V1.Controllers +{ + /// <summary> + /// Controller for performing flexible transaction search + /// </summary> + [ApiVersion("1")] + [Produces("application/json")] + [Route("api/v1/search/transactions")] + [ApiController] + public class GetTransactionListController : BaseController + { + private readonly IGetTransactionListUseCase _getTransactionListUseCase; + + public GetTransactionListController(IGetTransactionListUseCase getTransactionListUseCase) + { + _getTransactionListUseCase = getTransactionListUseCase; + } + + /// <summary> + /// Paginated search for transactions based on searchText + /// </summary> + /// <param name="request"></param> + /// <response code="200">Transactions list</response> + /// <response code="500">Internal Server Error</response> + [ProducesResponseType(typeof(APIResponse<GetTransactionListResponse>), 200)] + [ProducesResponseType(typeof(APIResponse<BadRequestException>), 400)] + [HttpGet, MapToApiVersion("1")] + [LogCall(LogLevel.Information)] + public async Task<IActionResult> GetTransactionList([FromQuery] GetTransactionListRequest request) + { + try + { + var transactionSearchResult = await _getTransactionListUseCase.ExecuteAsync(request).ConfigureAwait(false); + var apiResponse = new APIResponse<GetTransactionListResponse>(transactionSearchResult) + { + Total = transactionSearchResult.Total + }; + + return Ok(apiResponse); + } + catch (Exception e) + { + LambdaLogger.Log(e.Message + e.StackTrace); + + return BadRequest(e.Message); + } + } + } +} diff --git a/HousingSearchApi/V1/Factories/DomainFactory.cs b/HousingSearchApi/V1/Factories/DomainFactory.cs new file mode 100644 index 00000000..763dbf1a --- /dev/null +++ b/HousingSearchApi/V1/Factories/DomainFactory.cs @@ -0,0 +1,21 @@ +using SuspenseResolutionInfoResponse = HousingSearchApi.V1.Boundary.Responses.Transactions.SuspenseResolutionInfo; +using SuspenseResolutionInfoDomain = Hackney.Shared.HousingSearch.Domain.Transactions.SuspenseResolutionInfo; + +using SenderResponse = HousingSearchApi.V1.Boundary.Responses.Transactions.Sender; +using SenderDomain = Hackney.Shared.HousingSearch.Domain.Transactions.Sender; + +namespace HousingSearchApi.V1.Factories +{ + public static class DomainFactory + { + public static SuspenseResolutionInfoResponse ToDomain(this SuspenseResolutionInfoDomain sharedDomain) + { + return SuspenseResolutionInfoResponse.Create(sharedDomain.ResolutionDate, sharedDomain.IsConfirmed, sharedDomain.IsApproved, sharedDomain.Note); + } + + public static SenderResponse ToDomain(this SenderDomain sharedDomain) + { + return SenderResponse.Create(sharedDomain.Id, sharedDomain.FullName); + } + } +} diff --git a/HousingSearchApi/V1/Factories/ResponseFactory.cs b/HousingSearchApi/V1/Factories/ResponseFactory.cs new file mode 100644 index 00000000..eca9dc47 --- /dev/null +++ b/HousingSearchApi/V1/Factories/ResponseFactory.cs @@ -0,0 +1,43 @@ +using Hackney.Shared.HousingSearch.Domain.Transactions; +using HousingSearchApi.V1.Boundary.Responses.Transactions; +using System.Collections.Generic; +using System.Linq; + +namespace HousingSearchApi.V1.Factories +{ + public static class ResponseFactory + { + public static TransactionResponse ToResponse(this Transaction domain) + { + return domain == null ? null : TransactionResponse.Create( + domain.Id, + domain.TargetId, + domain.TargetType, + domain.PeriodNo, + domain.FinancialYear, + domain.FinancialMonth, + domain.TransactionSource, + domain.TransactionType, + domain.TransactionDate, + domain.TransactionAmount, + domain.PaymentReference, + domain.BankAccountNumber, + domain.IsSuspense, + domain.SuspenseResolutionInfo.ToDomain(), + domain.PaidAmount, + domain.ChargedAmount, + domain.BalanceAmount, + domain.HousingBenefitAmount, + domain.Address, + domain.Sender.ToDomain(), + domain.Fund); + } + + public static List<TransactionResponse> ToResponse(this IEnumerable<Transaction> domainList) + { + return domainList == null ? + new List<TransactionResponse>() : + domainList.Select(domain => domain.ToResponse()).ToList(); + } + } +} diff --git a/HousingSearchApi/V1/Gateways/GetAccountGateway.cs b/HousingSearchApi/V1/Gateways/GetAccountGateway.cs new file mode 100644 index 00000000..c3d3f0e1 --- /dev/null +++ b/HousingSearchApi/V1/Gateways/GetAccountGateway.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Hackney.Core.Logging; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; +using HousingSearchApi.V1.Gateways.Interfaces; +using Microsoft.Extensions.Logging; + +namespace HousingSearchApi.V1.Gateways +{ + public class GetAccountGateway : IGetAccountGateway + { + private readonly ISearchGateway _searchGateway; + private readonly ILogger<GetAccountGateway> _logger; + + public GetAccountGateway(ISearchGateway searchGateway, ILogger<GetAccountGateway> logger) + { + _searchGateway = searchGateway; + _logger = logger; + } + + [LogCall] + public async Task<GetAccountListResponse> Search(GetAccountListRequest parameters) + { + _logger.LogInformation("Housing search api for getting account list called."); + return await _searchGateway.GetListOfAccounts(parameters).ConfigureAwait(false); + } + } +} diff --git a/HousingSearchApi/V1/Gateways/GetPersonGateway.cs b/HousingSearchApi/V1/Gateways/GetPersonGateway.cs index 93d928dd..c1c15d4e 100644 --- a/HousingSearchApi/V1/Gateways/GetPersonGateway.cs +++ b/HousingSearchApi/V1/Gateways/GetPersonGateway.cs @@ -1,6 +1,7 @@ using Hackney.Core.Logging; using Hackney.Shared.HousingSearch.Domain.Person; using HousingSearchApi.V1.Gateways.Domain; +using HousingSearchApi.V1.Gateways.Interfaces; using Microsoft.Extensions.Logging; using Nest; using System.Collections.Generic; @@ -18,6 +19,7 @@ public GetPersonGateway(IElasticClient esClient, ILogger<GetPersonGateway> logge _esClient = esClient; _logger = logger; } + [LogCall] public Task<List<Person>> Search(SearchParameters parameters) { diff --git a/HousingSearchApi/V1/Gateways/Interfaces/IGetAccountGateway.cs b/HousingSearchApi/V1/Gateways/Interfaces/IGetAccountGateway.cs new file mode 100644 index 00000000..55687e8b --- /dev/null +++ b/HousingSearchApi/V1/Gateways/Interfaces/IGetAccountGateway.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; + +namespace HousingSearchApi.V1.Gateways.Interfaces +{ + public interface IGetAccountGateway + { + Task<GetAccountListResponse> Search(GetAccountListRequest parameters); + } +} diff --git a/HousingSearchApi/V1/Gateways/IGetPersonGateway.cs b/HousingSearchApi/V1/Gateways/Interfaces/IGetPersonGateway.cs similarity index 84% rename from HousingSearchApi/V1/Gateways/IGetPersonGateway.cs rename to HousingSearchApi/V1/Gateways/Interfaces/IGetPersonGateway.cs index d8672680..818f1bd0 100644 --- a/HousingSearchApi/V1/Gateways/IGetPersonGateway.cs +++ b/HousingSearchApi/V1/Gateways/Interfaces/IGetPersonGateway.cs @@ -3,7 +3,7 @@ using Hackney.Shared.HousingSearch.Domain.Person; using HousingSearchApi.V1.Gateways.Domain; -namespace HousingSearchApi.V1.Gateways +namespace HousingSearchApi.V1.Gateways.Interfaces { public interface IGetPersonGateway { diff --git a/HousingSearchApi/V1/Gateways/Interfaces/ISearchGateway.cs b/HousingSearchApi/V1/Gateways/Interfaces/ISearchGateway.cs new file mode 100644 index 00000000..256690a8 --- /dev/null +++ b/HousingSearchApi/V1/Gateways/Interfaces/ISearchGateway.cs @@ -0,0 +1,17 @@ +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; +using System.Threading.Tasks; +using HousingSearchApi.V1.Boundary.Responses.Transactions; + +namespace HousingSearchApi.V1.Gateways.Interfaces +{ + public interface ISearchGateway + { + Task<GetPersonListResponse> GetListOfPersons(GetPersonListRequest query); + Task<GetTenureListResponse> GetListOfTenures(GetTenureListRequest query); + Task<GetAssetListResponse> GetListOfAssets(GetAssetListRequest query); + Task<GetAllAssetListResponse> GetListOfAssetsSets(GetAllAssetListRequest query); + Task<GetAccountListResponse> GetListOfAccounts(GetAccountListRequest query); + Task<GetTransactionListResponse> GetListOfTransactions(GetTransactionListRequest request); + } +} diff --git a/HousingSearchApi/V1/Gateways/SearchGateway.cs b/HousingSearchApi/V1/Gateways/SearchGateway.cs new file mode 100644 index 00000000..7b2b747b --- /dev/null +++ b/HousingSearchApi/V1/Gateways/SearchGateway.cs @@ -0,0 +1,128 @@ +using Hackney.Core.Logging; +using Hackney.Shared.HousingSearch.Gateways.Models.Accounts; +using Hackney.Shared.HousingSearch.Gateways.Models.Assets; +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; +using HousingSearchApi.V1.Boundary.Responses.Transactions; +using HousingSearchApi.V1.Factories; +using HousingSearchApi.V1.Gateways.Interfaces; +using HousingSearchApi.V1.Interfaces; +using System; +using System.Linq; +using System.Threading.Tasks; +using QueryablePerson = Hackney.Shared.HousingSearch.Gateways.Models.Persons.QueryablePerson; +using QueryableTenure = Hackney.Shared.HousingSearch.Gateways.Models.Tenures.QueryableTenure; + +namespace HousingSearchApi.V1.Gateways +{ + public class SearchGateway : ISearchGateway + { + private readonly IElasticSearchWrapper _elasticSearchWrapper; + + public SearchGateway(IElasticSearchWrapper elasticSearchWrapper) + { + _elasticSearchWrapper = elasticSearchWrapper; + } + + [LogCall] + public async Task<GetPersonListResponse> GetListOfPersons(GetPersonListRequest query) + { + var searchResponse = await _elasticSearchWrapper.Search<QueryablePerson, GetPersonListRequest>(query).ConfigureAwait(false); + var personListResponse = new GetPersonListResponse(); + + personListResponse.Persons.AddRange(searchResponse.Documents.Select(queryablePerson => + queryablePerson.Create()) + ); + + personListResponse.SetTotal(searchResponse.Total); + + return personListResponse; + } + + [LogCall] + public async Task<GetTenureListResponse> GetListOfTenures(GetTenureListRequest query) + { + var searchResponse = await _elasticSearchWrapper.Search<QueryableTenure, GetTenureListRequest>(query).ConfigureAwait(false); + var tenureListResponse = new GetTenureListResponse(); + + tenureListResponse.Tenures.AddRange(searchResponse.Documents.Select(queryableTenure => + queryableTenure.Create()) + ); + + tenureListResponse.SetTotal(searchResponse.Total); + + return tenureListResponse; + } + + [LogCall] + public async Task<GetAssetListResponse> GetListOfAssets(GetAssetListRequest query) + { + var searchResponse = await _elasticSearchWrapper.Search<QueryableAsset, GetAssetListRequest>(query).ConfigureAwait(false); + var assetListResponse = new GetAssetListResponse(); + + assetListResponse.Assets.AddRange(searchResponse.Documents.Select(queryableAsset => + queryableAsset.Create()) + ); + + assetListResponse.SetTotal(searchResponse.Total); + + return assetListResponse; + } + + [LogCall] + public async Task<GetAllAssetListResponse> GetListOfAssetsSets(GetAllAssetListRequest query) + { + var searchResponse = await _elasticSearchWrapper.SearchSets<QueryableAsset, GetAllAssetListRequest>(query).ConfigureAwait(false); + var assetListResponse = new GetAllAssetListResponse(); + + if (searchResponse == null) return assetListResponse; + assetListResponse.Assets.AddRange(searchResponse.Documents.Select(queryableAsset => + queryableAsset.Create()) + ); + + assetListResponse.SetTotal(searchResponse.Total); + if (searchResponse.Documents.Count > 0) + { + assetListResponse.SetLastHitId(searchResponse.Hits.Last().Id); + } + + return assetListResponse; + } + + public async Task<GetAccountListResponse> GetListOfAccounts(GetAccountListRequest query) + { + var searchResponse = await _elasticSearchWrapper.Search<QueryableAccount, GetAccountListRequest>(query).ConfigureAwait(false); + var accountListResponse = GetAccountListResponse.Create(searchResponse.Documents.Select(queryableAccount => + queryableAccount.ToAccount())?.ToList()); + + accountListResponse.SetTotal(searchResponse.Total); + + return accountListResponse; + } + + public async Task<GetTransactionListResponse> GetListOfTransactions(GetTransactionListRequest request) + { + var searchRequest = new GetTransactionListRequest + { + SearchText = request.SearchText, + Page = request.Page, + PageSize = request.PageSize, + StartDate = request.StartDate, + EndDate = request.EndDate + }; + + var searchResponse = await _elasticSearchWrapper.Search<QueryableTransaction, GetTransactionListRequest>(searchRequest).ConfigureAwait(false); + + if (searchResponse == null) throw new Exception("Cannot get response from ElasticSearch instance"); + + if (!searchResponse.IsValid) throw new Exception($"Cannot load transactions list. Error: {searchResponse.ServerError}"); + + if (searchResponse.Documents == null) throw new Exception($"ElasticSearch instance returns no documents. Error: {searchResponse.ServerError}"); + + var transactions = searchResponse.Documents.Select(queryableTransaction => queryableTransaction.ToTransaction()); + + return GetTransactionListResponse.Create(searchResponse.Total, transactions.ToResponse()); + } + } +} diff --git a/HousingSearchApi/V1/Infrastructure/ElasticElasticSearchWrapper.cs b/HousingSearchApi/V1/Infrastructure/ElasticElasticSearchWrapper.cs new file mode 100644 index 00000000..44445c32 --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/ElasticElasticSearchWrapper.cs @@ -0,0 +1,130 @@ +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Sorting; +using Microsoft.Extensions.Logging; +using Nest; +using System; +using System.Linq; +using System.Threading.Tasks; +using Elasticsearch.Net; +using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.Interfaces.Factories; +using HousingSearchApi.V1.Interfaces.Filtering; + +namespace HousingSearchApi.V1.Infrastructure +{ + public class ElasticSearchWrapper : IElasticSearchWrapper + { + private readonly IElasticClient _esClient; + private readonly IQueryFactory _queryFactory; + private readonly IPagingHelper _pagingHelper; + private readonly ISortFactory _sortFactory; + private readonly IFilterFactory _filterFactory; + private readonly ILogger<ElasticSearchWrapper> _logger; + private readonly IIndexSelector _indexSelector; + + public ElasticSearchWrapper(IElasticClient esClient, IQueryFactory queryFactory, + IPagingHelper pagingHelper, ISortFactory sortFactory, ILogger<ElasticSearchWrapper> logger, IIndexSelector indexSelector, + IFilterFactory filterFactory) + { + _esClient = esClient; + _queryFactory = queryFactory; + _pagingHelper = pagingHelper; + _sortFactory = sortFactory; + _filterFactory = filterFactory; + _logger = logger; + _indexSelector = indexSelector; + } + + public async Task<ISearchResponse<T>> Search<T, TRequest>(TRequest request) where T : class where TRequest : class + { + try + { + var esNodes = string.Join(';', _esClient.ConnectionSettings.ConnectionPool.Nodes.Select(x => x.Uri)); + _logger.LogDebug($"ElasticSearch Search begins {esNodes}"); + + if (request == null) + return new SearchResponse<T>(); + + HousingSearchRequest searchRequest = (HousingSearchRequest) (object) request; + + var pageOffset = _pagingHelper.GetPageOffset(searchRequest.PageSize, searchRequest.Page); + + var result = await _esClient.SearchAsync<T>(x => x.Index(_indexSelector.Create<T>()) + .Query(q => BaseQuery<T>().Create(request, q)) + .PostFilter(q => _filterFactory.Filter(request, q)) + .Sort(_sortFactory.Create<T, TRequest>(request).GetSortDescriptor) + .Size(searchRequest.PageSize) + .Skip(pageOffset) + .TrackTotalHits()).ConfigureAwait(false); + + _logger.LogDebug("ElasticSearch Search ended"); + + return result; + } + catch (ElasticsearchClientException e) + { + _logger.LogError(e, "ElasticSearch Search threw an ElasticSearchClientException. DebugInfo: " + e.DebugInformation); + throw; + } + catch (Exception e) + { + _logger.LogError(e, "ElasticSearch Search threw an exception"); + throw; + } + } + + public async Task<ISearchResponse<T>> SearchSets<T, TRequest>(TRequest request) where T : class where TRequest : class + { + var esNodes = string.Join(';', _esClient.ConnectionSettings.ConnectionPool.Nodes.Select(x => x.Uri)); + _logger.LogDebug($"ElasticSearch Search Sets begins {esNodes}"); + + if (request == null) + return new SearchResponse<T>(); + var searchRequest = (GetAllAssetListRequest) (object) request; + + var elements = !string.IsNullOrEmpty(searchRequest.LastHitId) ? new string[] { searchRequest.LastHitId } : new string[] { string.Empty }; + var lastSortedItem = !string.IsNullOrEmpty(searchRequest.LastHitId) ? elements.Cast<object>().ToArray() : null; + + ISearchResponse<T> result = null; + + try + { + if (string.IsNullOrEmpty(searchRequest.LastHitId) && searchRequest.Page == 1) + { + result = await _esClient.SearchAsync<T>(x => x.Index(_indexSelector.Create<T>()) + .Query(q => BaseQuery<T>().Create(request, q)) + .Size(searchRequest.PageSize) + .Sort(_sortFactory.Create<T, TRequest>(request).GetSortDescriptor) + .TrackTotalHits() + ).ConfigureAwait(false); + } + else if (!string.IsNullOrEmpty(searchRequest.LastHitId)) + { + result = await _esClient.SearchAsync<T>(x => x.Index(_indexSelector.Create<T>()) + .Query(q => BaseQuery<T>().Create(request, q)) + .Size(searchRequest.PageSize) + .TrackTotalHits() + .SearchAfter(lastSortedItem) + .Sort(_sortFactory.Create<T, TRequest>(request).GetSortDescriptor) + ).ConfigureAwait(false); + } + + _logger.LogDebug("ElasticSearch Search Sets ended"); + + return result; + } + catch (Exception e) + { + + _logger.LogError(e, "ElasticSearch Search Sets threw an exception"); + throw; + } + + } + + private IQueryGenerator<T> BaseQuery<T>() where T : class + { + return _queryFactory.CreateQuery<T>(); + } + } +} diff --git a/HousingSearchApi/V1/Interfaces/ExactSearchQuerystringProcessor.cs b/HousingSearchApi/V1/Infrastructure/ExactSearchQuerystringProcessor.cs similarity index 91% rename from HousingSearchApi/V1/Interfaces/ExactSearchQuerystringProcessor.cs rename to HousingSearchApi/V1/Infrastructure/ExactSearchQuerystringProcessor.cs index 29f5679c..8bdf41df 100644 --- a/HousingSearchApi/V1/Interfaces/ExactSearchQuerystringProcessor.cs +++ b/HousingSearchApi/V1/Infrastructure/ExactSearchQuerystringProcessor.cs @@ -1,7 +1,7 @@ using System; using Hackney.Core.ElasticSearch.Interfaces; -namespace HousingSearchApi.V1.Interfaces +namespace HousingSearchApi.V1.Infrastructure { public class ExactSearchQuerystringProcessor : IExactSearchQuerystringProcessor { diff --git a/HousingSearchApi/V1/Infrastructure/ElasticSearchExtensions.cs b/HousingSearchApi/V1/Infrastructure/Extensions/ElasticSearchExtensions.cs similarity index 85% rename from HousingSearchApi/V1/Infrastructure/ElasticSearchExtensions.cs rename to HousingSearchApi/V1/Infrastructure/Extensions/ElasticSearchExtensions.cs index f745a711..4cbd1ba2 100644 --- a/HousingSearchApi/V1/Infrastructure/ElasticSearchExtensions.cs +++ b/HousingSearchApi/V1/Infrastructure/Extensions/ElasticSearchExtensions.cs @@ -5,7 +5,7 @@ using Nest; using System; -namespace HousingSearchApi.V1.Infrastructure +namespace HousingSearchApi.V1.Infrastructure.Extensions { public static class ElasticSearchExtensions { @@ -19,9 +19,12 @@ public static void ConfigureElasticSearch(this IServiceCollection services, ICon url = "http://localhost:9200"; var pool = new SingleNodeConnectionPool(new Uri(url)); + var connectionSettings = new ConnectionSettings(pool) - .PrettyJson().ThrowExceptions().DisableDirectStreaming(); + .PrettyJson() + .ThrowExceptions() + .DisableDirectStreaming(); var esClient = new ElasticClient(connectionSettings); services.TryAddSingleton<IElasticClient>(esClient); diff --git a/HousingSearchApi/V1/Infrastructure/Factories/AccountQueryGenerator.cs b/HousingSearchApi/V1/Infrastructure/Factories/AccountQueryGenerator.cs new file mode 100644 index 00000000..6747b4d5 --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/Factories/AccountQueryGenerator.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Hackney.Core.ElasticSearch.Interfaces; +using Hackney.Shared.HousingSearch.Gateways.Models.Accounts; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Factories; +using Nest; + +namespace HousingSearchApi.V1.Infrastructure.Factories +{ + public class AccountQueryGenerator : IQueryGenerator<QueryableAccount> + { + private readonly IQueryBuilder<QueryableAccount> _queryBuilder; + + public AccountQueryGenerator(IQueryBuilder<QueryableAccount> queryBuilder) + { + _queryBuilder = queryBuilder; + } + + + public QueryContainer Create<TRequest>(TRequest request, QueryContainerDescriptor<QueryableAccount> q) + { + GetAccountListRequest accountListRequest = request as GetAccountListRequest; + if (accountListRequest == null) + throw new ArgumentNullException($"{nameof(request).ToString()} shouldn't be null."); + + + if (!string.IsNullOrEmpty(accountListRequest.SearchText)) + _queryBuilder + .WithWildstarQuery(accountListRequest.SearchText, + new List<string> { "paymentReference", "tenure.fullAddress", "tenure.primaryTenants.fullName" }) + .WithExactQuery(accountListRequest.SearchText, + new List<string> { "paymentReference", "tenure.fullAddress", "tenure.primaryTenants.fullName" }); + + return _queryBuilder.Build(q); + } + } +} diff --git a/HousingSearchApi/V1/Infrastructure/Factories/AssetQueryGenerator.cs b/HousingSearchApi/V1/Infrastructure/Factories/AssetQueryGenerator.cs new file mode 100644 index 00000000..f607e6bd --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/Factories/AssetQueryGenerator.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Hackney.Core.ElasticSearch.Interfaces; +using Hackney.Shared.HousingSearch.Gateways.Models.Assets; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Factories; +using Nest; + +namespace HousingSearchApi.V1.Infrastructure.Factories +{ + public class AssetQueryGenerator : IQueryGenerator<QueryableAsset> + { + private readonly IQueryBuilder<QueryableAsset> _queryBuilder; + + public AssetQueryGenerator(IQueryBuilder<QueryableAsset> queryBuilder) + { + _queryBuilder = queryBuilder; + } + + public QueryContainer Create<TRequest>(TRequest request, QueryContainerDescriptor<QueryableAsset> q) + { + GetAssetListRequest assetListRequest = request as GetAssetListRequest; + if (assetListRequest == null) + throw new ArgumentNullException($"{nameof(request).ToString()} shouldn't be null."); + + return _queryBuilder + .WithWildstarQuery(assetListRequest.SearchText, + new List<string> { "assetAddress.addressLine1", "assetAddress.postCode", "assetAddress.uprn" }) + .WithExactQuery(assetListRequest.SearchText, + new List<string> + { + "assetAddress.addressLine1", + "assetAddress.uprn", + "assetAddress.postCode" + }) + .WithFilterQuery(assetListRequest.AssetTypes, new List<string> { "assetType" }) + .Build(q); + } + } +} diff --git a/HousingSearchApi/V1/Interfaces/PersonQueryGenerator.cs b/HousingSearchApi/V1/Infrastructure/Factories/PersonQueryGenerator.cs similarity index 70% rename from HousingSearchApi/V1/Interfaces/PersonQueryGenerator.cs rename to HousingSearchApi/V1/Infrastructure/Factories/PersonQueryGenerator.cs index d77f1a39..960cd5f1 100644 --- a/HousingSearchApi/V1/Interfaces/PersonQueryGenerator.cs +++ b/HousingSearchApi/V1/Infrastructure/Factories/PersonQueryGenerator.cs @@ -1,13 +1,12 @@ +using Hackney.Core.ElasticSearch.Interfaces; +using Hackney.Shared.HousingSearch.Gateways.Models.Persons; using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Infrastructure; +using HousingSearchApi.V1.Interfaces.Factories; using Nest; +using System; using System.Collections.Generic; -using System.Linq; -using Amazon.Runtime.Internal; -using Hackney.Core.ElasticSearch.Interfaces; -using Hackney.Shared.HousingSearch.Gateways.Models.Persons; -namespace HousingSearchApi.V1.Interfaces +namespace HousingSearchApi.V1.Infrastructure.Factories { public class PersonQueryGenerator : IQueryGenerator<QueryablePerson> { @@ -18,17 +17,20 @@ public PersonQueryGenerator(IQueryBuilder<QueryablePerson> queryBuilder) _queryBuilder = queryBuilder; } - public QueryContainer Create(HousingSearchRequest request, QueryContainerDescriptor<QueryablePerson> q) + + + public QueryContainer Create<TRequest>(TRequest request, QueryContainerDescriptor<QueryablePerson> q) { + if (!(request is GetPersonListRequest personListRequest)) { - return null; + throw new ArgumentNullException($"{nameof(request).ToString()} shouldn't be null."); } _queryBuilder - .WithWildstarQuery(request.SearchText, + .WithWildstarQuery(personListRequest.SearchText, new List<string> { "firstname", "surname" }) - .WithExactQuery(request.SearchText, + .WithExactQuery(personListRequest.SearchText, new List<string> { "firstname", "surname" }, new ExactSearchQuerystringProcessor()); if (personListRequest.PersonType.HasValue) diff --git a/HousingSearchApi/V1/Interfaces/QueryFactory.cs b/HousingSearchApi/V1/Infrastructure/Factories/QueryFactory.cs similarity index 59% rename from HousingSearchApi/V1/Interfaces/QueryFactory.cs rename to HousingSearchApi/V1/Infrastructure/Factories/QueryFactory.cs index 015ce419..5f87922b 100644 --- a/HousingSearchApi/V1/Interfaces/QueryFactory.cs +++ b/HousingSearchApi/V1/Infrastructure/Factories/QueryFactory.cs @@ -1,12 +1,17 @@ using System; using Hackney.Core.ElasticSearch.Interfaces; +using Hackney.Shared.HousingSearch.Gateways.Models.Accounts; using Hackney.Shared.HousingSearch.Gateways.Models.Assets; -using Hackney.Shared.HousingSearch.Gateways.Models.Persons; using Hackney.Shared.HousingSearch.Gateways.Models.Tenures; +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; using HousingSearchApi.V1.Boundary.Requests; +using Hackney.Shared.HousingSearch.Gateways.Models.Persons; +using HousingSearchApi.V1.Interfaces.Factories; using Microsoft.Extensions.DependencyInjection; +using QueryableTenure = Hackney.Shared.HousingSearch.Gateways.Models.Tenures.QueryableTenure; +using QueryablePerson = Hackney.Shared.HousingSearch.Gateways.Models.Persons.QueryablePerson; -namespace HousingSearchApi.V1.Interfaces +namespace HousingSearchApi.V1.Infrastructure.Factories { public class QueryFactory : IQueryFactory { @@ -17,7 +22,7 @@ public QueryFactory(IServiceProvider serviceProvider) _serviceProvider = serviceProvider; } - public IQueryGenerator<T> CreateQuery<T>(HousingSearchRequest request) where T : class + public IQueryGenerator<T> CreateQuery<T>() where T : class { if (typeof(T) == typeof(QueryablePerson)) { @@ -33,7 +38,15 @@ public IQueryGenerator<T> CreateQuery<T>(HousingSearchRequest request) where T : { return (IQueryGenerator<T>) new AssetQueryGenerator(_serviceProvider.GetService<IQueryBuilder<QueryableAsset>>()); } + if (typeof(T) == typeof(QueryableAccount)) + { + return (IQueryGenerator<T>) new AccountQueryGenerator(_serviceProvider.GetService<IQueryBuilder<QueryableAccount>>()); + } + if (typeof(T) == typeof(QueryableTransaction)) + { + return (IQueryGenerator<T>) new TransactionsQueryGenerator(_serviceProvider.GetService<IQueryBuilder<QueryableTransaction>>()); + } throw new System.NotImplementedException($"Query type {typeof(T)} is not implemented"); } diff --git a/HousingSearchApi/V1/Interfaces/TenureQueryGenerator.cs b/HousingSearchApi/V1/Infrastructure/Factories/TenureQueryGenerator.cs similarity index 58% rename from HousingSearchApi/V1/Interfaces/TenureQueryGenerator.cs rename to HousingSearchApi/V1/Infrastructure/Factories/TenureQueryGenerator.cs index 48daa1d4..6ef2ec8b 100644 --- a/HousingSearchApi/V1/Interfaces/TenureQueryGenerator.cs +++ b/HousingSearchApi/V1/Infrastructure/Factories/TenureQueryGenerator.cs @@ -1,10 +1,12 @@ +using System; using System.Collections.Generic; using Hackney.Core.ElasticSearch.Interfaces; using Hackney.Shared.HousingSearch.Gateways.Models.Tenures; using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Factories; using Nest; -namespace HousingSearchApi.V1.Interfaces +namespace HousingSearchApi.V1.Infrastructure.Factories { public class TenureQueryGenerator : IQueryGenerator<QueryableTenure> { @@ -15,12 +17,15 @@ public TenureQueryGenerator(IQueryBuilder<QueryableTenure> queryBuilder) _queryBuilder = queryBuilder; } - public QueryContainer Create(HousingSearchRequest request, QueryContainerDescriptor<QueryableTenure> q) + public QueryContainer Create<TRequest>(TRequest request, QueryContainerDescriptor<QueryableTenure> q) { - if (string.IsNullOrWhiteSpace(request.SearchText)) return null; + if (!(request is GetTenureListRequest tenureListRequest)) + throw new ArgumentNullException($"{nameof(request).ToString()} shouldn't be null."); + + if (string.IsNullOrWhiteSpace(tenureListRequest.SearchText)) return null; return _queryBuilder - .WithWildstarQuery(request.SearchText, new List<string> + .WithWildstarQuery(tenureListRequest.SearchText, new List<string> { "paymentReference", "tenuredAsset.fullAddress^3", diff --git a/HousingSearchApi/V1/Infrastructure/Factories/TransactionsQueryGenerator.cs b/HousingSearchApi/V1/Infrastructure/Factories/TransactionsQueryGenerator.cs new file mode 100644 index 00000000..15813ff7 --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/Factories/TransactionsQueryGenerator.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using Hackney.Core.ElasticSearch.Interfaces; +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Factories; +using Nest; + +namespace HousingSearchApi.V1.Infrastructure.Factories +{ + public class TransactionsQueryGenerator : IQueryGenerator<QueryableTransaction> + { + private readonly IQueryBuilder<QueryableTransaction> _queryBuilder; + + public TransactionsQueryGenerator(IQueryBuilder<QueryableTransaction> queryBuilder) + { + _queryBuilder = queryBuilder; + } + + public QueryContainer Create<TRequest>(TRequest request, QueryContainerDescriptor<QueryableTransaction> q) + { + if (!(request is GetTransactionListRequest transactionSearchRequest)) + throw new ArgumentNullException(nameof(request)); + + if (string.IsNullOrWhiteSpace(transactionSearchRequest.SearchText)) return null; + + return _queryBuilder + .WithWildstarQuery(transactionSearchRequest.SearchText, + new List<string> + { + "sender.fullName", + "transactionType", + "paymentReference", + "bankAccountNumber" + }) + .WithExactQuery(transactionSearchRequest.SearchText, + new List<string> + { + "sender.fullName", + "transactionType", + "paymentReference", + "bankAccountNumber" + }) + .Build(q); + } + } +} diff --git a/HousingSearchApi/V1/Infrastructure/Filtering/FilterFactory.cs b/HousingSearchApi/V1/Infrastructure/Filtering/FilterFactory.cs new file mode 100644 index 00000000..0d4e3b5a --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/Filtering/FilterFactory.cs @@ -0,0 +1,32 @@ +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Filtering; +using Nest; + +namespace HousingSearchApi.V1.Infrastructure.Filtering +{ + public class FilterFactory : IFilterFactory + { + /// <summary> + /// Method to perform filtering from start to end dates + /// </summary> + public QueryContainer Filter<T, TRequest>(TRequest request, QueryContainerDescriptor<T> q) where T : class where TRequest : class + { + // ToDo: implement factory for choosing type of filter + if (request is GetTransactionListRequest transactionSearchRequest && + q is QueryContainerDescriptor<QueryableTransaction> transactionDescriptor) + { + if (transactionSearchRequest.StartDate.HasValue && + transactionSearchRequest.EndDate.HasValue) + { + return transactionDescriptor.DateRange(c => c + .Field(p => p.CreatedAt) + .GreaterThanOrEquals(transactionSearchRequest.StartDate) + .LessThanOrEquals(transactionSearchRequest.EndDate)); + } + } + + return null; + } + } +} diff --git a/HousingSearchApi/V1/Infrastructure/GuardExtensions.cs b/HousingSearchApi/V1/Infrastructure/GuardExtensions.cs new file mode 100644 index 00000000..b453979a --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/GuardExtensions.cs @@ -0,0 +1,15 @@ +namespace HousingSearchApi.V1.Infrastructure +{ + /// <summary> + /// Guard that will help to avoid some common exceptions + /// </summary> + public static class GuardExtensions + { + /// <summary> + /// Checks if parentText contains innerText. Returns null if parentText is null or empty + /// </summary> + /// <returns></returns> + public static bool SafeContains(this string parentText, string innerText) + => !string.IsNullOrEmpty(parentText) && parentText.Contains(innerText); + } +} diff --git a/HousingSearchApi/V1/Interfaces/IndexSelector.cs b/HousingSearchApi/V1/Infrastructure/IndexSelector.cs similarity index 56% rename from HousingSearchApi/V1/Interfaces/IndexSelector.cs rename to HousingSearchApi/V1/Infrastructure/IndexSelector.cs index 69a90316..691f2043 100644 --- a/HousingSearchApi/V1/Interfaces/IndexSelector.cs +++ b/HousingSearchApi/V1/Infrastructure/IndexSelector.cs @@ -1,11 +1,16 @@ using System; using System.Collections.Generic; +using Hackney.Shared.HousingSearch.Gateways.Models.Accounts; using Hackney.Shared.HousingSearch.Gateways.Models.Assets; -using Hackney.Shared.HousingSearch.Gateways.Models.Persons; using Hackney.Shared.HousingSearch.Gateways.Models.Tenures; +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; +using Hackney.Shared.HousingSearch.Gateways.Models.Persons; +using HousingSearchApi.V1.Interfaces; using Nest; +using QueryableTenure = Hackney.Shared.HousingSearch.Gateways.Models.Tenures.QueryableTenure; +using QueryablePerson = Hackney.Shared.HousingSearch.Gateways.Models.Persons.QueryablePerson; -namespace HousingSearchApi.V1.Interfaces +namespace HousingSearchApi.V1.Infrastructure { public class IndexSelector : IIndexSelector { @@ -22,6 +27,12 @@ public Indices.ManyIndices Create<T>() if (type == typeof(QueryableAsset)) return Indices.Index(new List<IndexName> { "assets" }); + if (type == typeof(QueryableAccount)) + return Indices.Index(new List<IndexName> { "accounts" }); + + if (type == typeof(QueryableTransaction)) + return Indices.Index(new List<IndexName> { "transactions" }); + throw new NotImplementedException($"No index for type {typeof(T)}"); } } diff --git a/HousingSearchApi/V1/Interfaces/PagingHelper.cs b/HousingSearchApi/V1/Infrastructure/PagingHelper.cs similarity index 72% rename from HousingSearchApi/V1/Interfaces/PagingHelper.cs rename to HousingSearchApi/V1/Infrastructure/PagingHelper.cs index 443e0b9f..974cf30c 100644 --- a/HousingSearchApi/V1/Interfaces/PagingHelper.cs +++ b/HousingSearchApi/V1/Infrastructure/PagingHelper.cs @@ -1,4 +1,6 @@ -namespace HousingSearchApi.V1.Interfaces +using HousingSearchApi.V1.Interfaces; + +namespace HousingSearchApi.V1.Infrastructure { public class PagingHelper : IPagingHelper { diff --git a/HousingSearchApi/V1/Interfaces/Sorting/DefaultSort.cs b/HousingSearchApi/V1/Infrastructure/Sorting/DefaultSort.cs similarity index 68% rename from HousingSearchApi/V1/Interfaces/Sorting/DefaultSort.cs rename to HousingSearchApi/V1/Infrastructure/Sorting/DefaultSort.cs index fb19d89a..2af0d6bd 100644 --- a/HousingSearchApi/V1/Interfaces/Sorting/DefaultSort.cs +++ b/HousingSearchApi/V1/Infrastructure/Sorting/DefaultSort.cs @@ -1,6 +1,7 @@ +using HousingSearchApi.V1.Interfaces.Sorting; using Nest; -namespace HousingSearchApi.V1.Interfaces.Sorting +namespace HousingSearchApi.V1.Infrastructure.Sorting { public class DefaultSort<T> : ISort<T> where T : class { diff --git a/HousingSearchApi/V1/Interfaces/Sorting/LastNameAsc.cs b/HousingSearchApi/V1/Infrastructure/Sorting/LastNameAsc.cs similarity index 79% rename from HousingSearchApi/V1/Interfaces/Sorting/LastNameAsc.cs rename to HousingSearchApi/V1/Infrastructure/Sorting/LastNameAsc.cs index 9e5c73a8..bd222c62 100644 --- a/HousingSearchApi/V1/Interfaces/Sorting/LastNameAsc.cs +++ b/HousingSearchApi/V1/Infrastructure/Sorting/LastNameAsc.cs @@ -1,7 +1,8 @@ using Hackney.Shared.HousingSearch.Gateways.Models.Persons; +using HousingSearchApi.V1.Interfaces.Sorting; using Nest; -namespace HousingSearchApi.V1.Interfaces.Sorting +namespace HousingSearchApi.V1.Infrastructure.Sorting { public class SurnameAsc : ISort<QueryablePerson> { diff --git a/HousingSearchApi/V1/Interfaces/Sorting/LastNameDesc.cs b/HousingSearchApi/V1/Infrastructure/Sorting/LastNameDesc.cs similarity index 79% rename from HousingSearchApi/V1/Interfaces/Sorting/LastNameDesc.cs rename to HousingSearchApi/V1/Infrastructure/Sorting/LastNameDesc.cs index 48c34bb3..7392f277 100644 --- a/HousingSearchApi/V1/Interfaces/Sorting/LastNameDesc.cs +++ b/HousingSearchApi/V1/Infrastructure/Sorting/LastNameDesc.cs @@ -1,7 +1,8 @@ using Hackney.Shared.HousingSearch.Gateways.Models.Persons; +using HousingSearchApi.V1.Interfaces.Sorting; using Nest; -namespace HousingSearchApi.V1.Interfaces.Sorting +namespace HousingSearchApi.V1.Infrastructure.Sorting { public class SurnameDesc : ISort<QueryablePerson> { diff --git a/HousingSearchApi/V1/Infrastructure/Sorting/SortFactory.cs b/HousingSearchApi/V1/Infrastructure/Sorting/SortFactory.cs new file mode 100644 index 00000000..85d7b73f --- /dev/null +++ b/HousingSearchApi/V1/Infrastructure/Sorting/SortFactory.cs @@ -0,0 +1,49 @@ +using Hackney.Shared.HousingSearch.Gateways.Models.Assets; +using Hackney.Shared.HousingSearch.Gateways.Models.Persons; +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Interfaces.Sorting; +using QueryablePerson = Hackney.Shared.HousingSearch.Gateways.Models.Persons.QueryablePerson; + +namespace HousingSearchApi.V1.Infrastructure.Sorting +{ + public class SortFactory : ISortFactory + { + public ISort<T> Create<T, TRequest>(TRequest request) where T : class where TRequest : class + { + if (typeof(T) == typeof(QueryablePerson)) + { + if (string.IsNullOrEmpty(((HousingSearchRequest) (object) request).SortBy)) + return new DefaultSort<T>(); + + switch (((HousingSearchRequest) (object) request).IsDesc) + { + case true: + return (ISort<T>) new SurnameDesc(); + case false: + return (ISort<T>) new SurnameAsc(); + } + } + if (typeof(T) == typeof(QueryableAsset)) + { + if (string.IsNullOrEmpty(((HousingSearchRequest) (object) request).SortBy)) + return new DefaultSort<T>(); + + switch (((HousingSearchRequest) (object) request).IsDesc) + { + case true: + return (ISort<T>) new AssetIdDesc(); + case false: + return (ISort<T>) new AssetIdAsc(); + } + } + + if (typeof(T) == typeof(QueryableTransaction)) + { + return (ISort<T>) new TransactionDateDesc(); + } + + return new DefaultSort<T>(); + } + } +} diff --git a/HousingSearchApi/V1/Interfaces/AssetQueryGenerator.cs b/HousingSearchApi/V1/Interfaces/AssetQueryGenerator.cs deleted file mode 100644 index d6e0b8fc..00000000 --- a/HousingSearchApi/V1/Interfaces/AssetQueryGenerator.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Hackney.Core.ElasticSearch.Interfaces; -using Hackney.Shared.HousingSearch.Gateways.Models.Assets; -using HousingSearchApi.V1.Boundary.Requests; -using Nest; - -namespace HousingSearchApi.V1.Interfaces -{ - public class AssetQueryGenerator : IQueryGenerator<QueryableAsset> - { - private readonly IQueryBuilder<QueryableAsset> _queryBuilder; - - public AssetQueryGenerator(IQueryBuilder<QueryableAsset> queryBuilder) - { - _queryBuilder = queryBuilder; - } - - public QueryContainer Create(HousingSearchRequest request, QueryContainerDescriptor<QueryableAsset> q) - { - return _queryBuilder - .WithWildstarQuery(request.SearchText, - new List<string> { "assetAddress.addressLine1", "assetAddress.postCode", "assetAddress.uprn" }) - .WithExactQuery(request.SearchText, - new List<string> - { - "assetAddress.addressLine1", - "assetAddress.uprn", - "assetAddress.postCode" - }) - .WithFilterQuery(request.AssetTypes, new List<string> { "assetType" }) - .Build(q); - } - } -} diff --git a/HousingSearchApi/V1/Interfaces/ElasticElasticSearchWrapper.cs b/HousingSearchApi/V1/Interfaces/ElasticElasticSearchWrapper.cs deleted file mode 100644 index e7d6eedf..00000000 --- a/HousingSearchApi/V1/Interfaces/ElasticElasticSearchWrapper.cs +++ /dev/null @@ -1,66 +0,0 @@ -using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Interfaces.Sorting; -using Microsoft.Extensions.Logging; -using Nest; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace HousingSearchApi.V1.Interfaces -{ - public class ElasticElasticSearchWrapper : IElasticSearchWrapper - { - private readonly IElasticClient _esClient; - private readonly IQueryFactory _queryFactory; - private readonly IPagingHelper _pagingHelper; - private readonly ISortFactory _iSortFactory; - private readonly ILogger<ElasticElasticSearchWrapper> _logger; - private readonly IIndexSelector _indexSelector; - - public ElasticElasticSearchWrapper(IElasticClient esClient, IQueryFactory queryFactory, - IPagingHelper pagingHelper, ISortFactory iSortFactory, ILogger<ElasticElasticSearchWrapper> logger, IIndexSelector indexSelector) - { - _esClient = esClient; - _queryFactory = queryFactory; - _pagingHelper = pagingHelper; - _iSortFactory = iSortFactory; - _logger = logger; - _indexSelector = indexSelector; - } - - public async Task<ISearchResponse<T>> Search<T>(HousingSearchRequest request) where T : class - { - try - { - var esNodes = string.Join(';', _esClient.ConnectionSettings.ConnectionPool.Nodes.Select(x => x.Uri)); - _logger.LogDebug($"ElasticSearch Search begins {esNodes}"); - - if (request == null) - return new SearchResponse<T>(); - - var pageOffset = _pagingHelper.GetPageOffset(request.PageSize, request.Page); - - var result = await _esClient.SearchAsync<T>(x => x.Index(_indexSelector.Create<T>()) - .Query(q => BaseQuery<T>(request).Create(request, q)) - .Sort(_iSortFactory.Create<T>(request).GetSortDescriptor) - .Size(request.PageSize) - .Skip(pageOffset) - .TrackTotalHits()).ConfigureAwait(false); - - _logger.LogDebug("ElasticSearch Search ended"); - - return result; - } - catch (Exception e) - { - _logger.LogError(e, "ElasticSearch Search threw an exception"); - throw; - } - } - - private IQueryGenerator<T> BaseQuery<T>(HousingSearchRequest request) where T : class - { - return _queryFactory.CreateQuery<T>(request); - } - } -} diff --git a/HousingSearchApi/V1/Interfaces/Factories/IQueryFactory.cs b/HousingSearchApi/V1/Interfaces/Factories/IQueryFactory.cs new file mode 100644 index 00000000..97ffa2d2 --- /dev/null +++ b/HousingSearchApi/V1/Interfaces/Factories/IQueryFactory.cs @@ -0,0 +1,7 @@ +namespace HousingSearchApi.V1.Interfaces.Factories +{ + public interface IQueryFactory + { + IQueryGenerator<T> CreateQuery<T>() where T : class; + } +} diff --git a/HousingSearchApi/V1/Interfaces/Factories/IQueryGenerator.cs b/HousingSearchApi/V1/Interfaces/Factories/IQueryGenerator.cs new file mode 100644 index 00000000..4fb98360 --- /dev/null +++ b/HousingSearchApi/V1/Interfaces/Factories/IQueryGenerator.cs @@ -0,0 +1,9 @@ +using Nest; + +namespace HousingSearchApi.V1.Interfaces.Factories +{ + public interface IQueryGenerator<T> where T : class + { + QueryContainer Create<TRequest>(TRequest request, QueryContainerDescriptor<T> q); + } +} diff --git a/HousingSearchApi/V1/Interfaces/Filtering/IFilterFactory.cs b/HousingSearchApi/V1/Interfaces/Filtering/IFilterFactory.cs new file mode 100644 index 00000000..7725cf6b --- /dev/null +++ b/HousingSearchApi/V1/Interfaces/Filtering/IFilterFactory.cs @@ -0,0 +1,9 @@ +using Nest; + +namespace HousingSearchApi.V1.Interfaces.Filtering +{ + public interface IFilterFactory + { + QueryContainer Filter<T, TRequest>(TRequest request, QueryContainerDescriptor<T> q) where T : class where TRequest : class; + } +} diff --git a/HousingSearchApi/V1/Interfaces/IElasticSearchWrapper.cs b/HousingSearchApi/V1/Interfaces/IElasticSearchWrapper.cs index b0f5119b..1b5b73b9 100644 --- a/HousingSearchApi/V1/Interfaces/IElasticSearchWrapper.cs +++ b/HousingSearchApi/V1/Interfaces/IElasticSearchWrapper.cs @@ -6,6 +6,8 @@ namespace HousingSearchApi.V1.Interfaces { public interface IElasticSearchWrapper { - Task<ISearchResponse<T>> Search<T>(HousingSearchRequest request) where T : class; + Task<ISearchResponse<T>> Search<T, TRequest>(TRequest request) where T : class where TRequest : class; + + Task<ISearchResponse<T>> SearchSets<T, TRequest>(TRequest request) where T : class where TRequest : class; } } diff --git a/HousingSearchApi/V1/Interfaces/IQueryFactory.cs b/HousingSearchApi/V1/Interfaces/IQueryFactory.cs deleted file mode 100644 index 47192b99..00000000 --- a/HousingSearchApi/V1/Interfaces/IQueryFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using HousingSearchApi.V1.Boundary.Requests; - -namespace HousingSearchApi.V1.Interfaces -{ - public interface IQueryFactory - { - IQueryGenerator<T> CreateQuery<T>(HousingSearchRequest request) where T : class; - } -} diff --git a/HousingSearchApi/V1/Interfaces/IQueryGenerator.cs b/HousingSearchApi/V1/Interfaces/IQueryGenerator.cs deleted file mode 100644 index b6941224..00000000 --- a/HousingSearchApi/V1/Interfaces/IQueryGenerator.cs +++ /dev/null @@ -1,12 +0,0 @@ -using HousingSearchApi.V1.Boundary.Requests; -using Nest; - -namespace HousingSearchApi.V1.Interfaces -{ - public interface IQueryGenerator<T> where T : class - - { - QueryContainer Create(HousingSearchRequest request, - QueryContainerDescriptor<T> q); - } -} diff --git a/HousingSearchApi/V1/Interfaces/ISearchGateway.cs b/HousingSearchApi/V1/Interfaces/ISearchGateway.cs deleted file mode 100644 index dbf3b757..00000000 --- a/HousingSearchApi/V1/Interfaces/ISearchGateway.cs +++ /dev/null @@ -1,13 +0,0 @@ -using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Boundary.Responses; -using System.Threading.Tasks; - -namespace HousingSearchApi.V1.Interfaces -{ - public interface ISearchGateway - { - Task<GetPersonListResponse> GetListOfPersons(HousingSearchRequest query); - Task<GetTenureListResponse> GetListOfTenures(HousingSearchRequest query); - Task<GetAssetListResponse> GetListOfAssets(HousingSearchRequest query); - } -} diff --git a/HousingSearchApi/V1/Interfaces/SearchGateway.cs b/HousingSearchApi/V1/Interfaces/SearchGateway.cs deleted file mode 100644 index d17b6ce4..00000000 --- a/HousingSearchApi/V1/Interfaces/SearchGateway.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Hackney.Core.Logging; -using Hackney.Shared.HousingSearch.Gateways.Models.Assets; -using Hackney.Shared.HousingSearch.Gateways.Models.Persons; -using Hackney.Shared.HousingSearch.Gateways.Models.Tenures; -using HousingSearchApi.V1.Boundary.Requests; -using HousingSearchApi.V1.Boundary.Responses; -using System.Linq; -using System.Threading.Tasks; - -namespace HousingSearchApi.V1.Interfaces -{ - public class SearchGateway : ISearchGateway - { - private readonly IElasticSearchWrapper _elasticSearchWrapper; - - public SearchGateway(IElasticSearchWrapper elasticSearchWrapper) - { - _elasticSearchWrapper = elasticSearchWrapper; - } - - [LogCall] - public async Task<GetPersonListResponse> GetListOfPersons(HousingSearchRequest query) - { - var searchResponse = await _elasticSearchWrapper.Search<QueryablePerson>(query).ConfigureAwait(false); - var personListResponse = new GetPersonListResponse(); - - personListResponse.Persons.AddRange(searchResponse.Documents.Select(queryablePerson => - queryablePerson.Create()) - ); - - personListResponse.SetTotal(searchResponse.Total); - - return personListResponse; - } - - [LogCall] - public async Task<GetTenureListResponse> GetListOfTenures(HousingSearchRequest query) - { - var searchResponse = await _elasticSearchWrapper.Search<QueryableTenure>(query).ConfigureAwait(false); - var tenureListResponse = new GetTenureListResponse(); - - tenureListResponse.Tenures.AddRange(searchResponse.Documents.Select(queryableTenure => - queryableTenure.Create()) - ); - - tenureListResponse.SetTotal(searchResponse.Total); - - return tenureListResponse; - } - - [LogCall] - public async Task<GetAssetListResponse> GetListOfAssets(HousingSearchRequest query) - { - var searchResponse = await _elasticSearchWrapper.Search<QueryableAsset>(query).ConfigureAwait(false); - var assetListResponse = new GetAssetListResponse(); - - assetListResponse.Assets.AddRange(searchResponse.Documents.Select(queryableAsset => - queryableAsset.Create()) - ); - - assetListResponse.SetTotal(searchResponse.Total); - - return assetListResponse; - } - } -} diff --git a/HousingSearchApi/V1/Interfaces/Sorting/AssetIdAsc.cs b/HousingSearchApi/V1/Interfaces/Sorting/AssetIdAsc.cs new file mode 100644 index 00000000..f2ade03c --- /dev/null +++ b/HousingSearchApi/V1/Interfaces/Sorting/AssetIdAsc.cs @@ -0,0 +1,14 @@ +using Hackney.Shared.HousingSearch.Gateways.Models.Assets; +using Nest; + +namespace HousingSearchApi.V1.Interfaces.Sorting +{ + public class AssetIdAsc : ISort<QueryableAsset> + { + public SortDescriptor<QueryableAsset> GetSortDescriptor(SortDescriptor<QueryableAsset> descriptor) + { + return descriptor + .Ascending(f => f.Id.Suffix("keyword")); + } + } +} diff --git a/HousingSearchApi/V1/Interfaces/Sorting/AssetIdDesc.cs b/HousingSearchApi/V1/Interfaces/Sorting/AssetIdDesc.cs new file mode 100644 index 00000000..278651eb --- /dev/null +++ b/HousingSearchApi/V1/Interfaces/Sorting/AssetIdDesc.cs @@ -0,0 +1,14 @@ +using Hackney.Shared.HousingSearch.Gateways.Models.Assets; +using Nest; + +namespace HousingSearchApi.V1.Interfaces.Sorting +{ + public class AssetIdDesc : ISort<QueryableAsset> + { + public SortDescriptor<QueryableAsset> GetSortDescriptor(SortDescriptor<QueryableAsset> descriptor) + { + return descriptor + .Descending(f => f.Id.Suffix("keyword")); + } + } +} diff --git a/HousingSearchApi/V1/Interfaces/Sorting/ISortFactory.cs b/HousingSearchApi/V1/Interfaces/Sorting/ISortFactory.cs index 5bdbfdb9..6d0741ad 100644 --- a/HousingSearchApi/V1/Interfaces/Sorting/ISortFactory.cs +++ b/HousingSearchApi/V1/Interfaces/Sorting/ISortFactory.cs @@ -4,6 +4,6 @@ namespace HousingSearchApi.V1.Interfaces.Sorting { public interface ISortFactory { - ISort<T> Create<T>(HousingSearchRequest request) where T : class; + ISort<T> Create<T, TRequest>(TRequest request) where T : class where TRequest : class; } } diff --git a/HousingSearchApi/V1/Interfaces/Sorting/SortFactory.cs b/HousingSearchApi/V1/Interfaces/Sorting/SortFactory.cs deleted file mode 100644 index 23de9bcc..00000000 --- a/HousingSearchApi/V1/Interfaces/Sorting/SortFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Hackney.Shared.HousingSearch.Gateways.Models.Persons; -using HousingSearchApi.V1.Boundary.Requests; - -namespace HousingSearchApi.V1.Interfaces.Sorting -{ - public class SortFactory : ISortFactory - { - public ISort<T> Create<T>(HousingSearchRequest request) where T : class - { - if (typeof(T) == typeof(QueryablePerson)) - { - if (string.IsNullOrEmpty(request.SortBy)) - return new DefaultSort<T>(); - - switch (request.IsDesc) - { - case true: - return (ISort<T>) new SurnameDesc(); - case false: - return (ISort<T>) new SurnameAsc(); - } - } - - return new DefaultSort<T>(); - } - } -} diff --git a/HousingSearchApi/V1/Interfaces/Sorting/TransactionDateDesc.cs b/HousingSearchApi/V1/Interfaces/Sorting/TransactionDateDesc.cs new file mode 100644 index 00000000..2ff8a5c7 --- /dev/null +++ b/HousingSearchApi/V1/Interfaces/Sorting/TransactionDateDesc.cs @@ -0,0 +1,14 @@ +using Hackney.Shared.HousingSearch.Gateways.Models.Transactions; +using Nest; + +namespace HousingSearchApi.V1.Interfaces.Sorting +{ + public class TransactionDateDesc : ISort<QueryableTransaction> + { + public SortDescriptor<QueryableTransaction> GetSortDescriptor(SortDescriptor<QueryableTransaction> descriptor) + { + return descriptor + .Descending(f => f.TransactionDate); + } + } +} diff --git a/HousingSearchApi/V1/UseCase/GetAccountListUseCase.cs b/HousingSearchApi/V1/UseCase/GetAccountListUseCase.cs new file mode 100644 index 00000000..96541b22 --- /dev/null +++ b/HousingSearchApi/V1/UseCase/GetAccountListUseCase.cs @@ -0,0 +1,23 @@ +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.UseCase.Interfaces; +using System.Threading.Tasks; +using HousingSearchApi.V1.Boundary.Responses; +using HousingSearchApi.V1.Gateways.Interfaces; + +namespace HousingSearchApi.V1.UseCase +{ + public class GetAccountListUseCase : IGetAccountListUseCase + { + private readonly IGetAccountGateway _getAccountGateway; + + public GetAccountListUseCase(IGetAccountGateway getAccountGateway) + { + _getAccountGateway = getAccountGateway; + } + + public async Task<GetAccountListResponse> ExecuteAsync(GetAccountListRequest getAccountListRequest) + { + return await _getAccountGateway.Search(getAccountListRequest).ConfigureAwait(false); + } + } +} diff --git a/HousingSearchApi/V1/UseCase/GetAssetListSetsUseCase.cs b/HousingSearchApi/V1/UseCase/GetAssetListSetsUseCase.cs new file mode 100644 index 00000000..6ac75cd5 --- /dev/null +++ b/HousingSearchApi/V1/UseCase/GetAssetListSetsUseCase.cs @@ -0,0 +1,27 @@ +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; +using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.UseCase.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using HousingSearchApi.V1.Gateways.Interfaces; + +namespace HousingSearchApi.V1.UseCase +{ + public class GetAssetListSetsUseCase : IGetAssetListSetsUseCase + { + private readonly ISearchGateway _searchGateway; + + public GetAssetListSetsUseCase(ISearchGateway searchGateway) + { + _searchGateway = searchGateway; + } + + public async Task<GetAllAssetListResponse> ExecuteAsync(GetAllAssetListRequest query) + { + return await _searchGateway.GetListOfAssetsSets(query).ConfigureAwait(false); + } + } +} diff --git a/HousingSearchApi/V1/UseCase/GetAssetListUseCase.cs b/HousingSearchApi/V1/UseCase/GetAssetListUseCase.cs index 58420744..9c83fe64 100644 --- a/HousingSearchApi/V1/UseCase/GetAssetListUseCase.cs +++ b/HousingSearchApi/V1/UseCase/GetAssetListUseCase.cs @@ -2,7 +2,7 @@ using Hackney.Core.Logging; using HousingSearchApi.V1.Boundary.Requests; using HousingSearchApi.V1.Boundary.Responses; -using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.Gateways.Interfaces; using HousingSearchApi.V1.UseCase.Interfaces; namespace HousingSearchApi.V1.UseCase @@ -17,7 +17,7 @@ public GetAssetListUseCase(ISearchGateway searchGateway) } [LogCall] - public async Task<GetAssetListResponse> ExecuteAsync(HousingSearchRequest housingSearchRequest) + public async Task<GetAssetListResponse> ExecuteAsync(GetAssetListRequest housingSearchRequest) { return await _searchGateway.GetListOfAssets(housingSearchRequest).ConfigureAwait(false); } diff --git a/HousingSearchApi/V1/UseCase/GetPersonListUseCase.cs b/HousingSearchApi/V1/UseCase/GetPersonListUseCase.cs index 5ff0d3d0..4c8ba4dd 100644 --- a/HousingSearchApi/V1/UseCase/GetPersonListUseCase.cs +++ b/HousingSearchApi/V1/UseCase/GetPersonListUseCase.cs @@ -1,9 +1,9 @@ using Hackney.Core.Logging; using HousingSearchApi.V1.Boundary.Requests; using HousingSearchApi.V1.Boundary.Responses; -using HousingSearchApi.V1.Interfaces; using HousingSearchApi.V1.UseCase.Interfaces; using System.Threading.Tasks; +using HousingSearchApi.V1.Gateways.Interfaces; namespace HousingSearchApi.V1.UseCase { @@ -16,7 +16,7 @@ public GetPersonListUseCase(ISearchGateway searchGateway) _searchGateway = searchGateway; } [LogCall] - public async Task<GetPersonListResponse> ExecuteAsync(HousingSearchRequest housingSearchRequest) + public async Task<GetPersonListResponse> ExecuteAsync(GetPersonListRequest housingSearchRequest) { return await _searchGateway.GetListOfPersons(housingSearchRequest).ConfigureAwait(false); } diff --git a/HousingSearchApi/V1/UseCase/GetTenureListUseCase.cs b/HousingSearchApi/V1/UseCase/GetTenureListUseCase.cs index 74185761..3af26dbb 100644 --- a/HousingSearchApi/V1/UseCase/GetTenureListUseCase.cs +++ b/HousingSearchApi/V1/UseCase/GetTenureListUseCase.cs @@ -2,7 +2,7 @@ using Hackney.Core.Logging; using HousingSearchApi.V1.Boundary.Requests; using HousingSearchApi.V1.Boundary.Responses; -using HousingSearchApi.V1.Interfaces; +using HousingSearchApi.V1.Gateways.Interfaces; using HousingSearchApi.V1.UseCase.Interfaces; namespace HousingSearchApi.V1.UseCase @@ -17,7 +17,7 @@ public GetTenureListUseCase(ISearchGateway searchGateway) } [LogCall] - public async Task<GetTenureListResponse> ExecuteAsync(HousingSearchRequest housingSearchRequest) + public async Task<GetTenureListResponse> ExecuteAsync(GetTenureListRequest housingSearchRequest) { return await _searchGateway.GetListOfTenures(housingSearchRequest).ConfigureAwait(false); } diff --git a/HousingSearchApi/V1/UseCase/GetTransactionListUseCase.cs b/HousingSearchApi/V1/UseCase/GetTransactionListUseCase.cs new file mode 100644 index 00000000..a8f0a564 --- /dev/null +++ b/HousingSearchApi/V1/UseCase/GetTransactionListUseCase.cs @@ -0,0 +1,25 @@ +using Hackney.Core.Logging; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses.Transactions; +using HousingSearchApi.V1.Gateways.Interfaces; +using HousingSearchApi.V1.UseCase.Interfaces; +using System.Threading.Tasks; + +namespace HousingSearchApi.V1.UseCase +{ + public class GetTransactionListUseCase : IGetTransactionListUseCase + { + private readonly ISearchGateway _searchGateway; + + public GetTransactionListUseCase(ISearchGateway searchGateway) + { + _searchGateway = searchGateway; + } + + [LogCall] + public async Task<GetTransactionListResponse> ExecuteAsync(GetTransactionListRequest getTransactionListRequest) + { + return await _searchGateway.GetListOfTransactions(getTransactionListRequest).ConfigureAwait(false); + } + } +} diff --git a/HousingSearchApi/V1/UseCase/Interfaces/IGetAccountListUseCase.cs b/HousingSearchApi/V1/UseCase/Interfaces/IGetAccountListUseCase.cs new file mode 100644 index 00000000..2d6cee75 --- /dev/null +++ b/HousingSearchApi/V1/UseCase/Interfaces/IGetAccountListUseCase.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; + +namespace HousingSearchApi.V1.UseCase.Interfaces +{ + public interface IGetAccountListUseCase + { + Task<GetAccountListResponse> ExecuteAsync(GetAccountListRequest getAccountListRequest); + } +} diff --git a/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListSetsUseCase.cs b/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListSetsUseCase.cs new file mode 100644 index 00000000..6e9c0628 --- /dev/null +++ b/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListSetsUseCase.cs @@ -0,0 +1,11 @@ +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses; +using System.Threading.Tasks; + +namespace HousingSearchApi.V1.UseCase.Interfaces +{ + public interface IGetAssetListSetsUseCase + { + Task<GetAllAssetListResponse> ExecuteAsync(GetAllAssetListRequest query); + } +} diff --git a/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListUseCase.cs b/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListUseCase.cs index e1246150..cd7adf5a 100644 --- a/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListUseCase.cs +++ b/HousingSearchApi/V1/UseCase/Interfaces/IGetAssetListUseCase.cs @@ -6,6 +6,6 @@ namespace HousingSearchApi.V1.UseCase.Interfaces { public interface IGetAssetListUseCase { - Task<GetAssetListResponse> ExecuteAsync(HousingSearchRequest getPersonListRequest); + Task<GetAssetListResponse> ExecuteAsync(GetAssetListRequest getPersonListRequest); } } diff --git a/HousingSearchApi/V1/UseCase/Interfaces/IGetPersonListUseCase.cs b/HousingSearchApi/V1/UseCase/Interfaces/IGetPersonListUseCase.cs index 4ededf69..33072423 100644 --- a/HousingSearchApi/V1/UseCase/Interfaces/IGetPersonListUseCase.cs +++ b/HousingSearchApi/V1/UseCase/Interfaces/IGetPersonListUseCase.cs @@ -6,6 +6,6 @@ namespace HousingSearchApi.V1.UseCase.Interfaces { public interface IGetPersonListUseCase { - Task<GetPersonListResponse> ExecuteAsync(HousingSearchRequest housingSearchRequest); + Task<GetPersonListResponse> ExecuteAsync(GetPersonListRequest housingSearchRequest); } } diff --git a/HousingSearchApi/V1/UseCase/Interfaces/IGetTenureListUseCase.cs b/HousingSearchApi/V1/UseCase/Interfaces/IGetTenureListUseCase.cs index 72a4405b..21244c7b 100644 --- a/HousingSearchApi/V1/UseCase/Interfaces/IGetTenureListUseCase.cs +++ b/HousingSearchApi/V1/UseCase/Interfaces/IGetTenureListUseCase.cs @@ -6,6 +6,6 @@ namespace HousingSearchApi.V1.UseCase.Interfaces { public interface IGetTenureListUseCase { - Task<GetTenureListResponse> ExecuteAsync(HousingSearchRequest getPersonListRequest); + Task<GetTenureListResponse> ExecuteAsync(GetTenureListRequest getPersonListRequest); } } diff --git a/HousingSearchApi/V1/UseCase/Interfaces/IGetTransactionListUseCase.cs b/HousingSearchApi/V1/UseCase/Interfaces/IGetTransactionListUseCase.cs new file mode 100644 index 00000000..fe774139 --- /dev/null +++ b/HousingSearchApi/V1/UseCase/Interfaces/IGetTransactionListUseCase.cs @@ -0,0 +1,11 @@ +using HousingSearchApi.V1.Boundary.Requests; +using HousingSearchApi.V1.Boundary.Responses.Transactions; +using System.Threading.Tasks; + +namespace HousingSearchApi.V1.UseCase.Interfaces +{ + public interface IGetTransactionListUseCase + { + Task<GetTransactionListResponse> ExecuteAsync(GetTransactionListRequest getTransactionListRequest); + } +} diff --git a/HousingSearchApi/data/elasticsearch/transactionsIndex.json b/HousingSearchApi/data/elasticsearch/transactionsIndex.json new file mode 100644 index 00000000..a4b3f5fc --- /dev/null +++ b/HousingSearchApi/data/elasticsearch/transactionsIndex.json @@ -0,0 +1,191 @@ +{ + "aliases": {}, + "mappings": { + "properties": { + "id": { + "type": "text" + }, + "targetId": { + "type": "text" + }, + "targetType": { + "type": "keyword", + "normalizer": "my_normalizer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "periodNo": { + "type": "short" + }, + "financialYear": { + "type": "short" + }, + "financialMonth": { + "type": "short" + }, + "transactionSource": { + "type": "text" + }, + "transactionType": { + "type": "text" + }, + "transactionDate": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "transactionAmount": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "paymentReference": { + "type": "keyword", + "normalizer": "my_normalizer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "bankAccountNumber": { + "type": "keyword", + "normalizer": "my_normalizer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "isSuspense": { + "type": "boolean", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "paidAmount": { + "type": "float", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "chargedAmount": { + "type": "float", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "balanceAmount": { + "type": "float", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "housingBenefitAmount": { + "type": "float", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "address": { + "type": "keyword", + "normalizer": "my_normalizer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "fund": { + "type": "keyword", + "normalizer": "my_normalizer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "sender": { + "properties": { + "id": { + "type": "text" + }, + "fullName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "suspenseResolutionInfo": { + "properties": { + "note": { + "type": "text" + }, + "isConfirmed": { + "type": "boolean" + }, + "isApproved": { + "type": "boolean" + }, + "resolutionDate": { + "type": "date" + } + } + } + } + }, + "settings": { + "analysis": { + "normalizer": { + "my_normalizer": { + "type": "custom", + "char_filter": [], + "filter": [ + "lowercase", + "asciifolding" + ] + } + } + }, + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + } +} diff --git a/terraform/development/main.tf b/terraform/development/main.tf index 47b1845d..b3b3005f 100644 --- a/terraform/development/main.tf +++ b/terraform/development/main.tf @@ -21,7 +21,7 @@ terraform { data "aws_vpc" "development_vpc" { tags = { - Name = "vpc-housing-development" + Name = "housing-dev" } }