1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-12 22:10:50 -05:00

mark all notifications associated with a security task as deleted when the task is completed

This commit is contained in:
Nick Krantz 2025-05-28 15:33:30 -05:00
parent 9ad2d61303
commit 0e277adb3e
No known key found for this signature in database
GPG Key ID: FF670021ABCAB82E
9 changed files with 147 additions and 1 deletions

View File

@ -32,4 +32,13 @@ public interface INotificationRepository : IRepository<Notification, Guid>
/// </returns>
Task<PagedResult<NotificationStatusDetails>> GetByUserIdAndStatusAsync(Guid userId, ClientType clientType,
NotificationStatusFilter? statusFilter, PageOptions pageOptions);
/// <summary>
/// Get non-deleted notifications by a task Id.
/// </summary>
/// <param name="taskId">The unique identifier of the task.</param>
/// <returns>
/// A collection of notifications associated with the task that are not marked as deleted.
/// </returns>
Task<IEnumerable<Notification>> GetActiveByTaskIdAsync(Guid taskId);
}

View File

@ -0,0 +1,11 @@
namespace Bit.Core.Vault.Commands.Interfaces;
public interface IMarkNotificationsForTaskAsDeletedCommand
{
/// <summary>
/// Marks notifications associated with a given taskId as deleted.
/// </summary>
/// <param name="taskId">The unique identifier of the task to complete</param>
/// <returns>A task representing the async operation</returns>
Task MarkAsDeletedAsync(Guid taskId);
}

View File

@ -0,0 +1,61 @@
using Bit.Core.Context;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Platform.Push;
using Bit.Core.Vault.Commands.Interfaces;
namespace Bit.Core.Vault.Commands;
public class MarkNotificationsForTaskAsDeletedCommand : IMarkNotificationsForTaskAsDeletedCommand
{
private readonly INotificationRepository _notificationRepository;
private readonly INotificationStatusRepository _notificationStatusRepository;
private readonly ICurrentContext _currentContext;
private readonly IPushNotificationService _pushNotificationService;
public MarkNotificationsForTaskAsDeletedCommand(
INotificationRepository notificationRepository,
INotificationStatusRepository notificationStatusRepository,
ICurrentContext currentContext,
IPushNotificationService pushNotificationService)
{
_notificationRepository = notificationRepository;
_notificationStatusRepository = notificationStatusRepository;
_currentContext = currentContext;
_pushNotificationService = pushNotificationService;
}
public async Task MarkAsDeletedAsync(Guid taskId)
{
var notifications = await _notificationRepository.GetActiveByTaskIdAsync(taskId);
foreach (var notification in notifications)
{
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(notification.Id, _currentContext.UserId.Value);
if (notificationStatus == null)
{
notificationStatus = new NotificationStatus
{
NotificationId = notification.Id,
UserId = _currentContext.UserId.Value,
DeletedDate = DateTime.UtcNow
};
var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus);
await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus);
}
else
{
notificationStatus.DeletedDate = DateTime.UtcNow;
await _notificationStatusRepository.UpdateAsync(notificationStatus);
await _pushNotificationService.PushNotificationStatusAsync(notification, notificationStatus);
}
}
}
}

View File

@ -14,15 +14,19 @@ public class MarkTaskAsCompletedCommand : IMarkTaskAsCompleteCommand
private readonly ISecurityTaskRepository _securityTaskRepository;
private readonly IAuthorizationService _authorizationService;
private readonly ICurrentContext _currentContext;
private readonly IMarkNotificationsForTaskAsDeletedCommand _markNotificationsForTaskAsDeletedAsync;
public MarkTaskAsCompletedCommand(
ISecurityTaskRepository securityTaskRepository,
IAuthorizationService authorizationService,
ICurrentContext currentContext)
ICurrentContext currentContext,
IMarkNotificationsForTaskAsDeletedCommand markNotificationsForTaskAsDeletedAsync)
{
_securityTaskRepository = securityTaskRepository;
_authorizationService = authorizationService;
_currentContext = currentContext;
_markNotificationsForTaskAsDeletedAsync = markNotificationsForTaskAsDeletedAsync;
}
/// <inheritdoc />
@ -46,5 +50,8 @@ public class MarkTaskAsCompletedCommand : IMarkTaskAsCompleteCommand
task.RevisionDate = DateTime.UtcNow;
await _securityTaskRepository.ReplaceAsync(task);
// Mark all notifications related to this task as deleted
await _markNotificationsForTaskAsDeletedAsync.MarkAsDeletedAsync(taskId);
}
}

View File

@ -24,5 +24,6 @@ public static class VaultServiceCollectionExtensions
services.AddScoped<IGetSecurityTasksNotificationDetailsQuery, GetSecurityTasksNotificationDetailsQuery>();
services.AddScoped<ICreateManyTaskNotificationsCommand, CreateManyTaskNotificationsCommand>();
services.AddScoped<ICreateManyTasksCommand, CreateManyTasksCommand>();
services.AddScoped<IMarkNotificationsForTaskAsDeletedCommand, MarkNotificationsForTaskAsDeletedCommand>();
}
}

View File

@ -56,4 +56,21 @@ public class NotificationRepository : Repository<Notification, Guid>, INotificat
ContinuationToken = data.Count < pageOptions.PageSize ? null : (pageNumber + 1).ToString()
};
}
public async Task<IEnumerable<Notification>> GetActiveByTaskIdAsync(Guid taskId)
{
await using var connection = new SqlConnection(ConnectionString);
var results = await connection.QueryAsync<Notification>(
"[dbo].[Notification_ReadActiveByTaskId]",
new
{
TaskId = taskId,
},
commandType: CommandType.StoredProcedure);
var data = results.ToList();
return data;
}
}

View File

@ -74,4 +74,20 @@ public class NotificationRepository : Repository<Core.NotificationCenter.Entitie
ContinuationToken = results.Count < pageOptions.PageSize ? null : (pageNumber + 1).ToString()
};
}
public async Task<IEnumerable<Core.NotificationCenter.Entities.Notification>> GetActiveByTaskIdAsync(Guid taskId)
{
await using var scope = ServiceScopeFactory.CreateAsyncScope();
var dbContext = GetDatabaseContext(scope);
var query = from n in dbContext.Notifications
join ns in dbContext.Set<NotificationStatus>()
on n.Id equals ns.NotificationId
where n.TaskId == taskId
&& ns.DeletedDate == null
select n;
return await query.ToListAsync();
}
}

View File

@ -0,0 +1,12 @@
CREATE PROCEDURE [dbo].[Notification_ReadActiveByTaskId]
@TaskId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT n.*
FROM [dbo].[NotificationView] n
LEFT JOIN [dbo].[NotificationStatus] ns ON n.Id = ns.NotificationId
WHERE n.[TaskId] = @TaskId
AND ns.DeletedDate IS NULL
END

View File

@ -0,0 +1,12 @@
CREATE OR ALTER PROCEDURE [dbo].[Notification_ReadActiveByTaskId]
@TaskId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT n.*
FROM [dbo].[NotificationView] n
LEFT JOIN [dbo].[NotificationStatus] ns ON n.Id = ns.NotificationId
WHERE n.[TaskId] = @TaskId
AND ns.DeletedDate IS NULL
END