mirror of
https://github.com/bitwarden/server.git
synced 2024-11-29 13:25:17 +01:00
PM-10563: Notification Center API
This commit is contained in:
parent
b196c8bfb9
commit
ef32d33489
@ -0,0 +1,62 @@
|
||||
#nullable enable
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.NotificationCenter.Models.Request;
|
||||
using Bit.Api.NotificationCenter.Models.Response;
|
||||
using Bit.Core.NotificationCenter.Commands.Interfaces;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.NotificationCenter.Controllers;
|
||||
|
||||
[Route("notifications")]
|
||||
[Authorize("Application")]
|
||||
public class NotificationsController : Controller
|
||||
{
|
||||
private readonly IGetNotificationsForUserQuery _getNotificationsForUserQuery;
|
||||
private readonly IMarkNotificationDeletedCommand _markNotificationDeletedCommand;
|
||||
private readonly IMarkNotificationReadCommand _markNotificationReadCommand;
|
||||
|
||||
public NotificationsController(
|
||||
IGetNotificationsForUserQuery getNotificationsForUserQuery,
|
||||
IMarkNotificationDeletedCommand markNotificationDeletedCommand,
|
||||
IMarkNotificationReadCommand markNotificationReadCommand)
|
||||
{
|
||||
_getNotificationsForUserQuery = getNotificationsForUserQuery;
|
||||
_markNotificationDeletedCommand = markNotificationDeletedCommand;
|
||||
_markNotificationReadCommand = markNotificationReadCommand;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<NotificationResponseModel>> List(
|
||||
[FromQuery] NotificationFilterRequestModel filter)
|
||||
{
|
||||
var notificationStatusFilter = new NotificationStatusFilter
|
||||
{
|
||||
Read = filter.ReadStatusFilter,
|
||||
Deleted = filter.DeletedStatusFilter
|
||||
};
|
||||
|
||||
var notifications = await _getNotificationsForUserQuery.GetByUserIdStatusFilterAsync(notificationStatusFilter);
|
||||
|
||||
var filteredNotifications = notifications
|
||||
.Where(n => n.RevisionDate >= filter.Start && n.RevisionDate < filter.End)
|
||||
.Take(filter.PageSize);
|
||||
|
||||
var responses = filteredNotifications.Select(n => new NotificationResponseModel(n));
|
||||
return new ListResponseModel<NotificationResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpPatch("{id}/delete")]
|
||||
public async Task MarkAsDeleted([FromRoute] Guid id)
|
||||
{
|
||||
await _markNotificationDeletedCommand.MarkDeletedAsync(id);
|
||||
}
|
||||
|
||||
[HttpPatch("{id}/read")]
|
||||
public async Task MarkAsRead([FromRoute] Guid id)
|
||||
{
|
||||
await _markNotificationReadCommand.MarkReadAsync(id);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
namespace Bit.Api.NotificationCenter.Models.Request;
|
||||
|
||||
public class NotificationFilterRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters notifications by read status. When not set, includes notifications without a status.
|
||||
/// </summary>
|
||||
public bool? ReadStatusFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Filters notifications by deleted status. When not set, includes notifications without a status.
|
||||
/// </summary>
|
||||
public bool? DeletedStatusFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date. Must be less than the end date. Inclusive.
|
||||
/// </summary>
|
||||
public DateTime Start { get; set; } = DateTime.MinValue;
|
||||
|
||||
/// <summary>
|
||||
/// The end date. Must be greater than the start date. Not inclusive.
|
||||
/// </summary>
|
||||
public DateTime End { get; set; } = DateTime.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Number of items to return. Defaults to 10.
|
||||
/// </summary>
|
||||
public int PageSize { get; set; } = 10;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
|
||||
namespace Bit.Api.NotificationCenter.Models.Response;
|
||||
|
||||
public class NotificationResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "notification";
|
||||
|
||||
public NotificationResponseModel(Notification notification, string obj = _objectName)
|
||||
: base(obj)
|
||||
{
|
||||
if (notification == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(notification));
|
||||
}
|
||||
|
||||
Id = notification.Id;
|
||||
Priority = notification.Priority;
|
||||
Title = notification.Title;
|
||||
Body = notification.Body;
|
||||
Date = notification.RevisionDate;
|
||||
}
|
||||
|
||||
public NotificationResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Priority Priority { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Body { get; set; }
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
using Bit.Core.NotificationCenter.Authorization;
|
||||
using Bit.Core.NotificationCenter.Commands;
|
||||
using Bit.Core.NotificationCenter.Commands.Interfaces;
|
||||
using Bit.Core.NotificationCenter.Queries;
|
||||
using Bit.Core.NotificationCenter.Queries.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Core.NotificationCenter;
|
||||
|
||||
public static class NotificationCenterServiceCollectionExtensions
|
||||
{
|
||||
public static void AddNotificationCenterServices(this IServiceCollection services)
|
||||
{
|
||||
// Authorization Handlers
|
||||
services.AddScoped<IAuthorizationHandler, NotificationAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, NotificationStatusAuthorizationHandler>();
|
||||
// Commands
|
||||
services.AddScoped<ICreateNotificationCommand, CreateNotificationCommand>();
|
||||
services.AddScoped<ICreateNotificationStatusCommand, CreateNotificationStatusCommand>();
|
||||
services.AddScoped<IMarkNotificationDeletedCommand, MarkNotificationDeletedCommand>();
|
||||
services.AddScoped<IMarkNotificationReadCommand, MarkNotificationReadCommand>();
|
||||
services.AddScoped<IUpdateNotificationCommand, UpdateNotificationCommand>();
|
||||
// Queries
|
||||
services.AddScoped<IGetNotificationsForUserQuery, GetNotificationsForUserQuery>();
|
||||
services.AddScoped<IGetNotificationStatusForUserQuery, GetNotificationStatusForUserQuery>();
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.HostedServices;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.IdentityServer;
|
||||
using Bit.Core.NotificationCenter;
|
||||
using Bit.Core.OrganizationFeatures;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Resources;
|
||||
@ -114,6 +115,7 @@ public static class ServiceCollectionExtensions
|
||||
services.AddLoginServices();
|
||||
services.AddScoped<IOrganizationDomainService, OrganizationDomainService>();
|
||||
services.AddVaultServices();
|
||||
services.AddNotificationCenterServices();
|
||||
}
|
||||
|
||||
public static void AddTokenizers(this IServiceCollection services)
|
||||
|
@ -0,0 +1,52 @@
|
||||
#nullable enable
|
||||
using Bit.Api.NotificationCenter.Controllers;
|
||||
using Bit.Api.NotificationCenter.Models.Request;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries.Interfaces;
|
||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(NotificationsController))]
|
||||
[SutProviderCustomize]
|
||||
public class NotificationsControllerTest
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationListCustomize(20)]
|
||||
public async Task List_DefaultFilter_ReturnedMatchingNotifications(SutProvider<NotificationsController> sutProvider,
|
||||
List<Notification> notifications)
|
||||
{
|
||||
sutProvider.GetDependency<IGetNotificationsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>())
|
||||
.Returns(notifications);
|
||||
|
||||
var expectedNotificationGroupedById = notifications
|
||||
.Take(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var filter = new NotificationFilterRequestModel();
|
||||
|
||||
var listResponse = await sutProvider.Sut.List(filter);
|
||||
|
||||
Assert.Equal(10, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
var expectedNotification = expectedNotificationGroupedById[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotification);
|
||||
Assert.Equal(expectedNotification.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotification.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotification.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotification.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotification.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
});
|
||||
Assert.Null(listResponse.ContinuationToken);
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using AutoFixture;
|
||||
using AutoFixture.Dsl;
|
||||
using AutoFixture.Kernel;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
@ -8,19 +10,44 @@ public class NotificationCustomization(bool global) : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<Notification>(composer =>
|
||||
fixture.Customize<Notification>(GetSpecimenBuilder);
|
||||
}
|
||||
|
||||
public ISpecimenBuilder GetSpecimenBuilder(ICustomizationComposer<Notification> customizationComposer)
|
||||
{
|
||||
var postprocessComposer = customizationComposer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.Global, global);
|
||||
|
||||
postprocessComposer = global
|
||||
? postprocessComposer.Without(n => n.UserId)
|
||||
: postprocessComposer.With(n => n.UserId, Guid.NewGuid());
|
||||
|
||||
return global
|
||||
? postprocessComposer.Without(n => n.OrganizationId)
|
||||
: postprocessComposer.With(n => n.OrganizationId, Guid.NewGuid());
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationListCustomization(int count) : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<List<Notification>>(composer => composer.FromFactory(() =>
|
||||
{
|
||||
var postprocessComposer = composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.Global, global);
|
||||
var notificationCustomization = new NotificationCustomization(true);
|
||||
|
||||
postprocessComposer = global
|
||||
? postprocessComposer.Without(n => n.UserId)
|
||||
: postprocessComposer.With(n => n.UserId, Guid.NewGuid());
|
||||
var notifications = new List<Notification>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var customizationComposer = fixture.Build<Notification>();
|
||||
var postprocessComposer =
|
||||
customizationComposer.FromFactory(
|
||||
notificationCustomization.GetSpecimenBuilder(customizationComposer));
|
||||
notifications.Add(postprocessComposer.Create());
|
||||
}
|
||||
|
||||
return global
|
||||
? postprocessComposer.Without(n => n.OrganizationId)
|
||||
: postprocessComposer.With(n => n.OrganizationId, Guid.NewGuid());
|
||||
});
|
||||
return notifications;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,3 +56,9 @@ public class NotificationCustomizeAttribute(bool global = true)
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationCustomization(global);
|
||||
}
|
||||
|
||||
public class NotificationListCustomizeAttribute(int count)
|
||||
: BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationListCustomization(count);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user