diff --git a/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs b/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs index 0f822bf..333b0b1 100644 --- a/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs +++ b/CallbackHandler.BusinessLogic/Services/CallbackDomainService.cs @@ -25,6 +25,7 @@ public async Task RecordCallback(CallbackCommands.RecordCallbackCommand // split the reference string into an array of strings String[] referenceData = command.Reference?.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty(); + // TODO: Validate the reference data has the correct number of elements if (referenceData.Length == 0) { return Result.Failure("Reference cannot be empty."); } diff --git a/CallbackHandler/Bootstrapper/MiddlewareRegistry.cs b/CallbackHandler/Bootstrapper/MiddlewareRegistry.cs index 636663d..4872cb8 100644 --- a/CallbackHandler/Bootstrapper/MiddlewareRegistry.cs +++ b/CallbackHandler/Bootstrapper/MiddlewareRegistry.cs @@ -2,11 +2,6 @@ namespace CallbackHandler.Bootstrapper; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Reflection; using Common; using EventStore.Client; using Lamar; @@ -20,6 +15,13 @@ namespace CallbackHandler.Bootstrapper; using Shared.General; using Shared.Middleware; using Swashbuckle.AspNetCore.Filters; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Json; [ExcludeFromCodeCoverage] public class MiddlewareRegistry :ServiceRegistry @@ -85,5 +87,24 @@ public MiddlewareRegistry() new(middlewareLogLevel, logRequests, logResponses); this.AddSingleton(config); + + this.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy(); + options.SerializerOptions.PropertyNameCaseInsensitive = true; // optional, but safer + }); + } + + public class SnakeCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + // simple PascalCase to snake_case + return string.Concat( + name.Select((c, i) => + i > 0 && char.IsUpper(c) ? "_" + char.ToLower(c) : char.ToLower(c).ToString() + ) + ); } + } } diff --git a/CallbackHandler/Controllers/CallbackController.cs b/CallbackHandler/Controllers/CallbackController.cs deleted file mode 100644 index 5484816..0000000 --- a/CallbackHandler/Controllers/CallbackController.cs +++ /dev/null @@ -1,103 +0,0 @@ -using CallbackHandlers.Models; -using Shared.EventStore.Aggregate; -using Shared.Results; -using SimpleResults; - -namespace CallbackHandler.Controllers -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Threading; - using System.Threading.Tasks; - using Azure.Core; - using BusinessLogic.Requests; - using DataTransferObjects; - using MediatR; - using Microsoft.AspNetCore.Mvc; - using Newtonsoft.Json; - using Swashbuckle.AspNetCore.Annotations; - - [ExcludeFromCodeCoverage] - [Route(CallbackController.ControllerRoute)] - [ApiController] - public class CallbackController : ControllerBase - { - #region Fields - - private readonly IMediator Mediator; - - #endregion - - #region Constructors - - public CallbackController(IMediator mediator) - { - this.Mediator = mediator; - } - - #endregion - - #region Methods - - [HttpPost] - [SwaggerResponse(200, "OK")] - public async Task>> RecordCallback(Deposit depositCallback, - CancellationToken cancellationToken) - { - Guid callbackId = Guid.NewGuid(); - - CallbackCommands.RecordCallbackCommand request = new(callbackId, - JsonConvert.SerializeObject(depositCallback), - new[] { "TransactionProcessor" }, - MessageFormat.JSON, - depositCallback.GetType().ToString(), - depositCallback.Reference); - - Result result = await this.Mediator.Send(request, cancellationToken); - - if (result.IsFailed) - ResultHelpers.CreateFailure(result); - return Result.Success(callbackId); - } - - [HttpGet] - [Route("{callbackId}")] - [SwaggerResponse(200, "OK")] - public async Task>> GetCallback([FromRoute ]Guid callbackId, CancellationToken cancellationToken) - { - CallbackQueries.GetCallbackQuery query = new CallbackQueries.GetCallbackQuery(callbackId); - - Result getResult = await this.Mediator.Send(query, cancellationToken); - - if (getResult.IsFailed) - ResultHelpers.CreateFailure(getResult).ToActionResult(); - - - Result result = Result.Success( - new CallbackMessage - { - Reference = getResult.Data.Reference, - TypeString = getResult.Data.TypeString, - Message = getResult.Data.Message - }); - - return result.ToActionResult(); - } - - #endregion - - #region Others - - /// - /// The controller name - /// - private const String ControllerName = "callbacks"; - - /// - /// The controller route - /// - private const String ControllerRoute = "api/" + CallbackController.ControllerName; - - #endregion - } -} \ No newline at end of file diff --git a/CallbackHandler/Endpoints/CallbackEndpoints.cs b/CallbackHandler/Endpoints/CallbackEndpoints.cs new file mode 100644 index 0000000..16b979a --- /dev/null +++ b/CallbackHandler/Endpoints/CallbackEndpoints.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using SimpleResults; +using System; + +namespace CallbackHandler.Endpoints +{ + public static class CallbackEndpoints + { + private const string BaseRoute = "/api/callbacks"; + + public static IEndpointRouteBuilder MapCallbackEndpoints(this IEndpointRouteBuilder endpoints) + { + RouteGroupBuilder group = endpoints.MapGroup(BaseRoute) + .WithTags("Callbacks"); + + group.MapPost("/", Handlers.CallbackHandlers.RecordCallback) + .WithName("RecordCallback") + .WithSummary("Records a deposit callback") + .Produces>(StatusCodes.Status200OK); + + group.MapGet("/{callbackId:guid}", Handlers.CallbackHandlers.GetCallback) + .WithName("GetCallback") + .WithSummary("Gets a callback by ID") + .Produces>(StatusCodes.Status200OK); + + return endpoints; + } + } +} diff --git a/CallbackHandler/Handlers/CallbackHandlers.cs b/CallbackHandler/Handlers/CallbackHandlers.cs new file mode 100644 index 0000000..e23435c --- /dev/null +++ b/CallbackHandler/Handlers/CallbackHandlers.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using CallbackHandler.BusinessLogic.Requests; +using CallbackHandler.DataTransferObjects; +using CallbackHandlers.Models; +using MediatR; +using Newtonsoft.Json; +using Shared.Results; +using SimpleResults; + +namespace CallbackHandler.Handlers; + +public static class CallbackHandlers +{ + public static async Task> RecordCallback(Deposit depositCallback, + IMediator mediator, + CancellationToken cancellationToken) + { + Guid callbackId = Guid.NewGuid(); + + CallbackCommands.RecordCallbackCommand request = new( + callbackId, + JsonConvert.SerializeObject(depositCallback), + new[] { "TransactionProcessor" }, + MessageFormat.JSON, + depositCallback.GetType().ToString(), + depositCallback.Reference); + + Result result = await mediator.Send(request, cancellationToken); + + if (result.IsFailed) { + return ResultHelpers.CreateFailure(result); + } + + return Result.Success(callbackId); + } + + public static async Task> GetCallback(Guid callbackId, + IMediator mediator, + CancellationToken cancellationToken) + { + CallbackQueries.GetCallbackQuery query = new(callbackId); + Result getResult = await mediator.Send(query, cancellationToken); + + if (getResult.IsFailed) { + return ResultHelpers.CreateFailure(getResult); + } + + Result result = Result.Success(new DataTransferObjects.CallbackMessage + { + Reference = getResult.Data.Reference, + TypeString = getResult.Data.TypeString, + Message = getResult.Data.Message + }); + + return result; + } +} \ No newline at end of file diff --git a/CallbackHandler/Startup.cs b/CallbackHandler/Startup.cs index bc4cb41..2397603 100644 --- a/CallbackHandler/Startup.cs +++ b/CallbackHandler/Startup.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using CallbackHandler.Endpoints; namespace CallbackHandler { @@ -112,7 +113,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF app.UseEndpoints(endpoints => { - endpoints.MapControllers(); + endpoints.MapCallbackEndpoints(); + endpoints.MapHealthChecks("health", new HealthCheckOptions {