diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..e69de29 diff --git a/EstateReportingAPI.BusinessLogic/EstateReportingAPI.BusinessLogic.csproj b/EstateReportingAPI.BusinessLogic/EstateReportingAPI.BusinessLogic.csproj index 0d65c0a..00be78f 100644 --- a/EstateReportingAPI.BusinessLogic/EstateReportingAPI.BusinessLogic.csproj +++ b/EstateReportingAPI.BusinessLogic/EstateReportingAPI.BusinessLogic.csproj @@ -8,10 +8,10 @@ - + - + diff --git a/EstateReportingAPI.BusinessLogic/Queries/FileImportLogQueries.cs b/EstateReportingAPI.BusinessLogic/Queries/FileImportLogQueries.cs index 360d553..7ff5b68 100644 --- a/EstateReportingAPI.BusinessLogic/Queries/FileImportLogQueries.cs +++ b/EstateReportingAPI.BusinessLogic/Queries/FileImportLogQueries.cs @@ -10,4 +10,10 @@ public record FileImportLogQueries { public record GetFileImportLogListQuery(Guid EstateId, Guid? MerchantId, DateTime StartDate, DateTime EndDate) : IRequest>>; public record GetFileImportLogQuery(Guid EstateId, Guid? MerchantId, Guid FileImportLogId) : IRequest>; +} + +[ExcludeFromCodeCoverage] +public record FileProfileConfigurationQueries +{ + public record GetFileProfileConfigurationListQuery(Guid EstateId) : IRequest>>; } \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 3b9986c..9723b15 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -8,13 +8,11 @@ namespace EstateReportingAPI.BusinessLogic; -using Azure; using Microsoft.EntityFrameworkCore; using Models; using Shared.EntityFramework; using Shared.Results; using System; -using System.ClientModel.Primitives; using System.Linq; using System.Threading; using static Microsoft.EntityFrameworkCore.DbLoggerCategory; @@ -65,6 +63,8 @@ Task>> GetFileImportLogList(FileImportLogQueries.GetF CancellationToken cancellationToken); Task> GetFileImportLog(FileImportLogQueries.GetFileImportLogQuery request, CancellationToken cancellationToken); + Task>> GetFileProfileConfigurationList(FileProfileConfigurationQueries.GetFileProfileConfigurationListQuery request, + CancellationToken cancellationToken); } public class ReportingManager : IReportingManager { @@ -1522,6 +1522,34 @@ join fl in context.FileLines on f.FileId equals fl.FileId return Result.Success(fileImportLogs); } + public async Task>> GetFileProfileConfigurationList(FileProfileConfigurationQueries.GetFileProfileConfigurationListQuery request, + CancellationToken cancellationToken) { + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); + await using EstateManagementContext context = resolvedContext.Context; + + var query = from c in context.FileProfileConfigurations + join h in context.FileFormatHandlers on c.FileFormatHandlerId equals h.FileFormatHandlerId + join r in context.RequestTypes on c.RequestTypeId equals r.RequestTypeId + join o in context.Operators on c.OperatorId equals o.OperatorId + select new FileProfileConfiguration + { + FileProfileId = c.FileProfileId, + Name = c.Name, + ListeningDirectory = c.ListeningDirectory, + RequestType = r.Name, + OperatorName = o.Name, + LineTerminator = c.LineTerminator, + FileFormatHandler = h.Name + }; + + Result> result = await ExecuteQuerySafeToList(query, cancellationToken, "Error retrieving file import logs"); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return Result.Success(result.Data); + } + private async Task GetSettlementSummary(IQueryable query, CancellationToken cancellationToken) { // Get the settleed fees summary diff --git a/EstateReportingAPI.BusinessLogic/RequestHandlers/FileImportLogRequestHandler.cs b/EstateReportingAPI.BusinessLogic/RequestHandlers/FileImportLogRequestHandler.cs index 888c723..68d2a53 100644 --- a/EstateReportingAPI.BusinessLogic/RequestHandlers/FileImportLogRequestHandler.cs +++ b/EstateReportingAPI.BusinessLogic/RequestHandlers/FileImportLogRequestHandler.cs @@ -23,4 +23,20 @@ public async Task> Handle(FileImportLogQueries.GetFileImpo { return await this.Manager.GetFileImportLog(request, cancellationToken); } +} + +public class FileProfileConfigurationRequestHandler : IRequestHandler>> + +{ + private readonly IReportingManager Manager; + public FileProfileConfigurationRequestHandler(IReportingManager manager) + { + this.Manager = manager; + } + + public async Task>> Handle(FileProfileConfigurationQueries.GetFileProfileConfigurationListQuery request, + CancellationToken cancellationToken) + { + return await this.Manager.GetFileProfileConfigurationList(request, cancellationToken); + } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/FileProfileConfiguration.cs b/EstateReportingAPI.DataTrasferObjects/FileProfileConfiguration.cs new file mode 100644 index 0000000..7d4ab54 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/FileProfileConfiguration.cs @@ -0,0 +1,14 @@ +using System; + +namespace EstateReportingAPI.DataTransferObjects; + +public class FileProfileConfiguration +{ + public Guid FileProfileId { get; set; } + public string Name { get; set; } + public string ListeningDirectory { get; set; } + public string RequestType { get; set; } + public string OperatorName { get; set; } + public string LineTerminator { get; set; } + public string FileFormatHandler { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/DatabaseHelper.cs b/EstateReportingAPI.IntegrationTests/DatabaseHelper.cs index bf7b5b5..baf8f0a 100644 --- a/EstateReportingAPI.IntegrationTests/DatabaseHelper.cs +++ b/EstateReportingAPI.IntegrationTests/DatabaseHelper.cs @@ -28,6 +28,7 @@ using DbFile = TransactionProcessor.Database.Entities.File; using FileLine = TransactionProcessor.Database.Entities.FileLine; using EstateSecurityUser = TransactionProcessor.Database.Entities.EstateSecurityUser; +using FileProfileConfiguration = TransactionProcessor.Database.Entities.FileProfileConfiguration; namespace EstateReportingAPI.IntegrationTests; @@ -103,6 +104,48 @@ public async Task AddFileLine(Guid fileId, int lineNumber, string data, string s await this.Context.SaveChangesAsync(CancellationToken.None); } + public async Task AddFileFormatHandler(string name) + { + Guid id = Guid.NewGuid(); + FileFormatHandler h = new() { FileFormatHandlerId = id, Name = name }; + + await this.Context.AddAsync(h); + await this.Context.SaveChangesAsync(CancellationToken.None); + + return id; + } + + public async Task AddRequestType(string name) + { + Guid id = Guid.NewGuid(); + RequestType r = new() { RequestTypeId = id, Name = name }; + + await this.Context.AddAsync(r); + await this.Context.SaveChangesAsync(CancellationToken.None); + + return id; + } + + public async Task AddFileProfileConfiguration(Guid fileProfileId, + string name, + string listeningDirectory, + Guid fileFormatHandlerId, + Guid requestTypeId, + Guid operatorId, + string lineTerminator) { + FileProfileConfiguration configuration = new() { + Name = name, + FileFormatHandlerId = fileFormatHandlerId, + FileProfileId = fileProfileId, + LineTerminator = lineTerminator, + ListeningDirectory = listeningDirectory, + OperatorId = operatorId, + RequestTypeId = requestTypeId + }; + await this.Context.AddAsync(configuration); + await this.Context.SaveChangesAsync(CancellationToken.None); + } + public async Task DeleteAllMerchants(){ List merchants = await this.Context.Merchants.ToListAsync(); this.Context.RemoveRange(merchants); diff --git a/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj b/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj index 1990fb4..b07a0fd 100644 --- a/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj +++ b/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/EstateReportingAPI.IntegrationTests/FileProfileConfigurationEndpointTests.cs b/EstateReportingAPI.IntegrationTests/FileProfileConfigurationEndpointTests.cs new file mode 100644 index 0000000..bdb7e50 --- /dev/null +++ b/EstateReportingAPI.IntegrationTests/FileProfileConfigurationEndpointTests.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using EstateReportingAPI.DataTransferObjects; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using Shouldly; +using Xunit; + +namespace EstateReportingAPI.IntegrationTests +{ + public class FileProfileConfigurationEndpointTests : ControllerTestsBase + { + private const string BaseRoute = "api/fileprofiles"; + + [Fact] + public async Task FileProfileConfiguration_Get_NoData_ReturnsEmptyList() + { + // ensure estate exists + await this.helper.AddEstate("Test Estate", "Ref1"); + + var result = await this.CreateAndSendHttpRequestMessage>($"{BaseRoute}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var list = result.Data; + list.ShouldNotBeNull(); + list.Count.ShouldBe(0); + } + + [Fact] + public async Task FileProfileConfiguration_Get_ReturnsInsertedData() + { + // setup estate and operator + await this.helper.AddEstate("Test Estate", "Ref1"); + var operatorId = await this.helper.AddOperator("Test Estate", "Test Operator"); + + // insert supporting rows and a profile using helper methods + Guid profileId = Guid.NewGuid(); + string handlerName = $"TestHandler-{this.TestId}"; + string requestName = $"TestRequest-{this.TestId}"; + string profileName = $"TestProfile-{this.TestId}"; + + Guid handlerId = await this.helper.AddFileFormatHandler(handlerName); + Guid requestId = await this.helper.AddRequestType(requestName); + + await this.helper.AddFileProfileConfiguration(profileId, profileName, "C:\\listen", handlerId, requestId, operatorId, "\\n"); + + var result = await this.CreateAndSendHttpRequestMessage>($"{BaseRoute}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var list = result.Data; + list.ShouldNotBeNull(); + list.Count.ShouldBeGreaterThan(0); + + var match = list.SingleOrDefault(f => f.FileProfileId == profileId); + match.ShouldNotBeNull(); + match.Name.ShouldBe(profileName); + } + + protected override Task ClearStandingData() + { + return Task.CompletedTask; + } + + protected override Task SetupStandingData() + { + // tests will create their own standing data as needed + return Task.CompletedTask; + } + } +} diff --git a/EstateReportingAPI.Models/FileProfileConfiguration.cs b/EstateReportingAPI.Models/FileProfileConfiguration.cs new file mode 100644 index 0000000..c46905e --- /dev/null +++ b/EstateReportingAPI.Models/FileProfileConfiguration.cs @@ -0,0 +1,12 @@ +namespace EstateReportingAPI.Models; + +public class FileProfileConfiguration +{ + public Guid FileProfileId { get; set; } + public string Name { get; set; } = string.Empty; + public string ListeningDirectory { get; set; } = string.Empty; + public string RequestType { get; set; } = string.Empty; + public string OperatorName { get; set; } = string.Empty; + public string LineTerminator { get; set; } = string.Empty; + public string FileFormatHandler { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj b/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj index f4c0bf1..33343ca 100644 --- a/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj +++ b/EstateReportingAPI.Tests/EstateReportingAPI.Tests.csproj @@ -7,12 +7,12 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,7 +20,7 @@ - + all diff --git a/EstateReportingAPI/Bootstrapper/MiddlewareRegistry.cs b/EstateReportingAPI/Bootstrapper/MiddlewareRegistry.cs index 98741d7..0223c3d 100644 --- a/EstateReportingAPI/Bootstrapper/MiddlewareRegistry.cs +++ b/EstateReportingAPI/Bootstrapper/MiddlewareRegistry.cs @@ -20,7 +20,6 @@ public class MiddlewareRegistry : ServiceRegistry{ public MiddlewareRegistry() { this.ConfigureHealthChecks(); - //this.ConfigureSwagger(); this.ConfigureAuthentication(); this.ConfigureControllers(); this.ConfigureMiddlewareLogging(); @@ -50,34 +49,7 @@ private void ConfigureHealthChecks(){ tags: new[] { "db", "sql", "sqlserver" }); } } - - private void ConfigureSwagger(){ - this.AddSwaggerGen(c => { - c.SwaggerDoc("v1", - new OpenApiInfo{ - Title = "Estate Reporting API", - Version = "1.0", - Description = "A REST Api to manage all aspects of reporting for an estate (merchants, operators and contracts).", - Contact = new OpenApiContact{ - Name = "Stuart Ferguson", - Email = "golfhandicapping@btinternet.com" - } - }); - // add a custom operation filter which sets default values - - c.ExampleFilters(); - - //Locate the XML files being generated by ASP.NET... - var directory = new DirectoryInfo(AppContext.BaseDirectory); - var xmlFiles = directory.GetFiles("*.xml"); - - //... and tell Swagger to use those XML comments. - foreach (FileInfo fileInfo in xmlFiles){ - c.IncludeXmlComments(fileInfo.FullName); - } - }); - } - + private void ConfigureAuthentication(){ String? inTestMode = Environment.GetEnvironmentVariable("InTestMode"); if (String.Compare(inTestMode, Boolean.TrueString, StringComparison.InvariantCultureIgnoreCase) != 0){ diff --git a/EstateReportingAPI/Endpoints/FileImportLogEndpoints.cs b/EstateReportingAPI/Endpoints/FileImportLogEndpoints.cs index 5a7b086..e43cc89 100644 --- a/EstateReportingAPI/Endpoints/FileImportLogEndpoints.cs +++ b/EstateReportingAPI/Endpoints/FileImportLogEndpoints.cs @@ -23,4 +23,22 @@ public static void MapFileImportLogEndpoints(this IEndpointRouteBuilder app) group.MapGet("/{fileimportlogid}", FileImportHandler.GetFileImportLog).WithStandardProduces(); } +} + +public static class FileProfileConfigurationEndpoints +{ + private const string BaseRoute = "api/fileprofiles"; + + public static void MapFileProfileConfigurationEndpoints(this IEndpointRouteBuilder app) + { + RouteGroupBuilder group = app.MapGroup(BaseRoute).WithTags("File Profile Configurations"); + + Boolean disableAuthorisation = ConfigurationReader.GetValueOrDefault("AppSettings", "DisableAuthorisation", false); + if (disableAuthorisation == false) + { + group = group.RequireAuthorization(); + } + + group.MapGet("/", FileProfileConfigurationHandler.GetFileProfileConfigurationList).WithStandardProduces>(); + } } \ No newline at end of file diff --git a/EstateReportingAPI/EstateReportingAPI.csproj b/EstateReportingAPI/EstateReportingAPI.csproj index a7e6c2e..f86bcb2 100644 --- a/EstateReportingAPI/EstateReportingAPI.csproj +++ b/EstateReportingAPI/EstateReportingAPI.csproj @@ -36,7 +36,7 @@ - + diff --git a/EstateReportingAPI/Handlers/FileImportHandler.cs b/EstateReportingAPI/Handlers/FileImportHandler.cs index ab46325..d4a9ff1 100644 --- a/EstateReportingAPI/Handlers/FileImportHandler.cs +++ b/EstateReportingAPI/Handlers/FileImportHandler.cs @@ -7,6 +7,28 @@ namespace EstateReportingAPI.Handlers; +public static class FileProfileConfigurationHandler +{ + public static async Task GetFileProfileConfigurationList([FromHeader] Guid estateId, + IMediator mediator, + CancellationToken cancellationToken) + { + FileProfileConfigurationQueries.GetFileProfileConfigurationListQuery query = new(estateId); + Result> result = await mediator.Send(query, cancellationToken); + + return ResponseFactory.FromResult(result, r => r.Select(m => new DataTransferObjects.FileProfileConfiguration() + { + FileFormatHandler = m.FileFormatHandler, + FileProfileId = m.FileProfileId, + LineTerminator = m.LineTerminator, + ListeningDirectory = m.ListeningDirectory, + Name = m.Name, + OperatorName = m.OperatorName, + RequestType = m.RequestType + }).ToList()); + } +} + public static class FileImportHandler { public static async Task GetFileImportLogList([FromHeader] Guid estateId, diff --git a/EstateReportingAPI/Startup.cs b/EstateReportingAPI/Startup.cs index 86a09bf..d41e4fe 100644 --- a/EstateReportingAPI/Startup.cs +++ b/EstateReportingAPI/Startup.cs @@ -72,6 +72,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF endpoints.MapTransactionEndpoints(); endpoints.MapSettlementEndpoints(); endpoints.MapFileImportLogEndpoints(); + endpoints.MapFileProfileConfigurationEndpoints(); endpoints.MapHealthChecks("health", new HealthCheckOptions() { @@ -84,9 +85,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); }); - //app.UseSwagger(); - - //app.UseSwaggerUI(); } } } \ No newline at end of file