From 419d5e4834e78718fcbaf1e4ff257c44a44b8bc1 Mon Sep 17 00:00:00 2001 From: Paul Fresquet <61119222+paul-fresquet@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:39:31 +0000 Subject: [PATCH 1/3] Add messages API --- docs/server-deployment.md | 7 +++ .../Http/MessageDefinitionFunction.cs | 29 +++++++++++++ .../GetActiveMessagesCommandHandler.cs | 27 ++++++++++++ .../Messages/GetActiveMessagesRequest.cs | 6 +++ .../GetActiveMessagesCommandHandlerTests.cs | 43 +++++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 src/ByteSync.Functions/Http/MessageDefinitionFunction.cs create mode 100644 src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesCommandHandler.cs create mode 100644 src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesRequest.cs create mode 100644 tests/ByteSync.ServerCommon.Tests/Commands/Messages/GetActiveMessagesCommandHandlerTests.cs diff --git a/docs/server-deployment.md b/docs/server-deployment.md index f23397b71..4c8547b9c 100644 --- a/docs/server-deployment.md +++ b/docs/server-deployment.md @@ -154,6 +154,13 @@ Repeat these steps for **ByteSync.Functions.IntegrationTests** and **ByteSync.Se --- +### Messages API + +The function app exposes a `GET /api/messages` endpoint that returns the currently active messages. +Each item includes `id`, `startDate`, `endDate` and a language dictionary `message`. + +--- + ## Final Steps 1. **Deploy** your Azure Function (Windows) to Azure. diff --git a/src/ByteSync.Functions/Http/MessageDefinitionFunction.cs b/src/ByteSync.Functions/Http/MessageDefinitionFunction.cs new file mode 100644 index 000000000..ba7819ba2 --- /dev/null +++ b/src/ByteSync.Functions/Http/MessageDefinitionFunction.cs @@ -0,0 +1,29 @@ +using System.Net; +using ByteSync.ServerCommon.Commands.Messages; +using MediatR; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; + +namespace ByteSync.Functions.Http; + +public class MessageDefinitionFunction +{ + private readonly IMediator _mediator; + + public MessageDefinitionFunction(IMediator mediator) + { + _mediator = mediator; + } + + [Function("GetMessages")] + public async Task GetMessages( + [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "messages")] HttpRequestData req, + FunctionContext executionContext) + { + var messages = await _mediator.Send(new GetActiveMessagesRequest()); + + var response = req.CreateResponse(); + await response.WriteAsJsonAsync(messages, HttpStatusCode.OK); + return response; + } +} diff --git a/src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesCommandHandler.cs b/src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesCommandHandler.cs new file mode 100644 index 000000000..5abadcc0d --- /dev/null +++ b/src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesCommandHandler.cs @@ -0,0 +1,27 @@ +using ByteSync.ServerCommon.Business.Messages; +using ByteSync.ServerCommon.Interfaces.Repositories; +using MediatR; + +namespace ByteSync.ServerCommon.Commands.Messages; + +public class GetActiveMessagesCommandHandler : IRequestHandler> +{ + private readonly IMessageDefinitionRepository _repository; + + public GetActiveMessagesCommandHandler(IMessageDefinitionRepository repository) + { + _repository = repository; + } + + public async Task> Handle(GetActiveMessagesRequest request, CancellationToken cancellationToken) + { + var allMessages = await _repository.GetAll(); + if (allMessages is null) + { + return new List(); + } + + var now = DateTime.UtcNow; + return allMessages.Where(m => m.StartDate <= now && now < m.EndDate).ToList(); + } +} diff --git a/src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesRequest.cs b/src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesRequest.cs new file mode 100644 index 000000000..305805c8a --- /dev/null +++ b/src/ByteSync.ServerCommon/Commands/Messages/GetActiveMessagesRequest.cs @@ -0,0 +1,6 @@ +using ByteSync.ServerCommon.Business.Messages; +using MediatR; + +namespace ByteSync.ServerCommon.Commands.Messages; + +public class GetActiveMessagesRequest : IRequest>; diff --git a/tests/ByteSync.ServerCommon.Tests/Commands/Messages/GetActiveMessagesCommandHandlerTests.cs b/tests/ByteSync.ServerCommon.Tests/Commands/Messages/GetActiveMessagesCommandHandlerTests.cs new file mode 100644 index 000000000..c94558f1a --- /dev/null +++ b/tests/ByteSync.ServerCommon.Tests/Commands/Messages/GetActiveMessagesCommandHandlerTests.cs @@ -0,0 +1,43 @@ +using ByteSync.ServerCommon.Business.Messages; +using ByteSync.ServerCommon.Commands.Messages; +using ByteSync.ServerCommon.Interfaces.Repositories; +using FakeItEasy; +using FluentAssertions; + +namespace ByteSync.ServerCommon.Tests.Commands.Messages; + +[TestFixture] +public class GetActiveMessagesCommandHandlerTests +{ + private IMessageDefinitionRepository _repository = null!; + private GetActiveMessagesCommandHandler _handler = null!; + + [SetUp] + public void Setup() + { + _repository = A.Fake(); + _handler = new GetActiveMessagesCommandHandler(_repository); + } + + [Test] + public async Task Handle_ReturnsOnlyActiveMessages() + { + // Arrange + var now = DateTime.UtcNow; + var messages = new List + { + new() { Id = "1", StartDate = now.AddHours(-1), EndDate = now.AddHours(1) }, + new() { Id = "2", StartDate = now.AddHours(-2), EndDate = now.AddHours(-1) }, + new() { Id = "3", StartDate = now.AddHours(1), EndDate = now.AddHours(2) } + }; + A.CallTo(() => _repository.GetAll()).Returns(messages); + + // Act + var result = await _handler.Handle(new GetActiveMessagesRequest(), CancellationToken.None); + + // Assert + result.Should().HaveCount(1); + result[0].Id.Should().Be("1"); + A.CallTo(() => _repository.GetAll()).MustHaveHappenedOnceExactly(); + } +} From dec2ce16498dfb8375018b7d025873ed348f506e Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Sun, 6 Jul 2025 20:22:52 +0200 Subject: [PATCH 2/3] feat: Messages route is anonymous test: add bruno test --- .../Helpers/Middlewares/JwtMiddleware.cs | 3 ++- .../Bruno/Get Messages.bru | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/ByteSync.Functions.UnitTests/Bruno/Get Messages.bru diff --git a/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs b/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs index e5ddafdaa..173cd5a03 100644 --- a/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs +++ b/src/ByteSync.Functions/Helpers/Middlewares/JwtMiddleware.cs @@ -27,7 +27,8 @@ public class JwtMiddleware : IFunctionsWorkerMiddleware public JwtMiddleware(IOptions appSettings, IClientsRepository clientsRepository, ILogger logger) { var loginFunctionEntryPoint = GetEntryPoint(nameof(AuthFunction.Login)); - _allowedAnonymousFunctionEntryPoints = [loginFunctionEntryPoint]; + var getMessagesFunctionEntryPoint = GetEntryPoint(nameof(MessageDefinitionFunction.GetMessages)); + _allowedAnonymousFunctionEntryPoints = [loginFunctionEntryPoint, getMessagesFunctionEntryPoint]; _secret = appSettings.Value.Secret; _clientsRepository = clientsRepository; diff --git a/tests/ByteSync.Functions.UnitTests/Bruno/Get Messages.bru b/tests/ByteSync.Functions.UnitTests/Bruno/Get Messages.bru new file mode 100644 index 000000000..32101fe65 --- /dev/null +++ b/tests/ByteSync.Functions.UnitTests/Bruno/Get Messages.bru @@ -0,0 +1,11 @@ +meta { + name: Get Messages + type: http + seq: 10 +} + +get { + url: {{root-url}}/messages + body: none + auth: none +} From 89a0b62dec81cfb968783d5778e989938377af66 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Sun, 6 Jul 2025 20:24:05 +0200 Subject: [PATCH 3/3] docs: restore server-deployment.md --- docs/server-deployment.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/server-deployment.md b/docs/server-deployment.md index 4c8547b9c..f23397b71 100644 --- a/docs/server-deployment.md +++ b/docs/server-deployment.md @@ -154,13 +154,6 @@ Repeat these steps for **ByteSync.Functions.IntegrationTests** and **ByteSync.Se --- -### Messages API - -The function app exposes a `GET /api/messages` endpoint that returns the currently active messages. -Each item includes `id`, `startDate`, `endDate` and a language dictionary `message`. - ---- - ## Final Steps 1. **Deploy** your Azure Function (Windows) to Azure.