Сервисы для уведомлений

1. Добавил репозиторий для уведомлений
2. Добавил сервисы для уведомлений
This commit is contained in:
parent 399a8a6c59
commit 96786b1be7
9 changed files with 455 additions and 0 deletions

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Services;
namespace AsbCloudApp.Repositories;
/// <summary>
/// Репозиторий для уведомлений
/// </summary>
public interface INotificationRepository : ICrudRepository<NotificationDto>
{
/// <summary>
/// Метод для получения не отправленных уведомлений
/// </summary>
/// <param name="idUser"></param>
/// <param name="idNotificationTransport"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<NotificationDto>> GetUnsentNotificationsAsync(int idUser,
int idNotificationTransport,
CancellationToken cancellationToken);
/// <summary>
/// Метод получения уведомлений по параметрам
/// </summary>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="idUser"></param>
/// <param name="idNotificationTransport"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int? skip,
int? take,
int idUser,
int idNotificationTransport,
CancellationToken cancellationToken);
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Threading;
using AsbCloudApp.Data;
namespace AsbCloudApp.Services;
/// <summary>
/// Сервис для добавление уведомлений в очередь
/// </summary>
public interface INotificationSendingQueueService
{
/// <summary>
/// Флаг для проверки пустая ли коллекция
/// </summary>
bool IsEmpty { get; }
/// <summary>
/// Добавление одного уведомления в очередь
/// </summary>
/// <param name="notificationDto"></param>
void Enqueue(NotificationDto notificationDto);
/// <summary>
/// Добавление нескольких уведомлений в очередь
/// </summary>
/// <param name="notifications"></param>
void EnqueueRange(IEnumerable<NotificationDto> notifications);
/// <summary>
/// Извлечение элемента из очереди и его удаление
/// </summary>
/// <param name="notification"></param>
/// <returns></returns>
bool TryDequeue(out NotificationDto notification);
/// <summary>
/// Метод ожидания нового уведомления
/// </summary>
/// <param name="cancellationToken"></param>
void Wait(CancellationToken cancellationToken);
}

View File

@ -0,0 +1,52 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudApp.Services;
/// <summary>
/// Интерфейс для работы с уведомлениями
/// </summary>
public interface INotificationService
{
/// <summary>
/// Метод отправки нового уведомления
/// </summary>
/// <param name="idUser"></param>
/// <param name="idNotificationTransport"></param>
/// <param name="idNotificationCategory"></param>
/// <param name="title"></param>
/// <param name="subject"></param>
/// <param name="timeToLife"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SendNotificationAsync(int idUser,
int idNotificationTransport,
int idNotificationCategory,
string title,
string subject,
TimeSpan timeToLife,
CancellationToken cancellationToken);
/// <summary>
/// Метод обновления уведомления
/// </summary>
/// <param name="idNotification"></param>
/// <param name="isRead"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task UpdateNotificationAsync(int idNotification,
bool isRead,
CancellationToken cancellationToken);
/// <summary>
/// Метод отправки уведомлений, которые не были отправлены
/// </summary>
/// <param name="idUser"></param>
/// <param name="idNotificationTransport"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task ResendNotificationAsync(int idUser,
int idNotificationTransport,
CancellationToken cancellationToken);
}

View File

@ -143,6 +143,10 @@ namespace AsbCloudInfrastructure
services.AddTransient<IGtrRepository, GtrWitsRepository>();
services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddSingleton<INotificationSendingQueueService, NotificationSendingQueueService>();
// admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheRepositoryBase<TelemetryDto, Telemetry>(

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudDb;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
namespace AsbCloudInfrastructure.Repository;
public class NotificationRepository : CrudRepositoryBase<NotificationDto, Notification>, INotificationRepository
{
public NotificationRepository(IAsbCloudDbContext context) : base(context)
{
}
public NotificationRepository(IAsbCloudDbContext context,
Func<DbSet<Notification>, IQueryable<Notification>> makeQuery) : base(context, makeQuery)
{
}
public async Task<IEnumerable<NotificationDto>> GetUnsentNotificationsAsync(int idUser,
int idNotificationTransport,
CancellationToken cancellationToken)
{
var notifications = await dbContext.Notifications
.Where(x => x.IdUser == idUser &&
x.IdNotificationTransport == idNotificationTransport &&
x.SentDateAtUtc == null)
.Include(x => x.NotificationTransport)
.Include(x => x.NotificationCategory)
.ToListAsync(cancellationToken);
return notifications.Select(x => x.Adapt<NotificationDto>());
}
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int? skip,
int? take,
int idUser,
int idNotificationTransport,
CancellationToken cancellationToken)
{
skip ??= 0;
take ??= 10;
var query = dbContext.Notifications
.Where(x => x.IdNotificationTransport == idNotificationTransport &&
x.IdUser == idUser &&
x.SentDateAtUtc != null);
var result = new PaginationContainer<NotificationDto>()
{
Skip = skip.Value,
Take = take.Value,
Count = await query.CountAsync(cancellationToken),
};
if (result.Count == 0)
return result;
result.Items = await query
.OrderBy(x => x.SentDateAtUtc)
.SkipTake(skip, take)
.Include(x => x.NotificationCategory)
.Include(x => x.NotificationTransport)
.Select(x => x.Adapt<NotificationDto>())
.ToListAsync(cancellationToken);
return result;
}
}

View File

@ -0,0 +1,42 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using AsbCloudApp.Data;
using AsbCloudApp.Services;
namespace AsbCloudInfrastructure.Services;
public class NotificationSendingQueueService : INotificationSendingQueueService
{
private readonly ManualResetEventSlim manualResetEventSlim = new();
private readonly ConcurrentQueue<NotificationDto> notificationsQueue = new();
public bool IsEmpty => notificationsQueue.IsEmpty;
public void Enqueue(NotificationDto notification)
{
notificationsQueue.Enqueue(notification);
manualResetEventSlim.Set();
}
public void EnqueueRange(IEnumerable<NotificationDto> notifications)
{
foreach (var notification in notifications)
{
notificationsQueue.Enqueue(notification);
}
manualResetEventSlim.Set();
}
public bool TryDequeue(out NotificationDto notification)
{
return notificationsQueue.TryDequeue(out notification!);
}
public void Wait(CancellationToken cancellationToken)
{
manualResetEventSlim.Wait(cancellationToken);
manualResetEventSlim.Reset();
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
namespace AsbCloudInfrastructure.Services;
public class NotificationService : INotificationService
{
private readonly INotificationSendingQueueService notificationSendingQueueService;
private readonly INotificationRepository notificationRepository;
public NotificationService(INotificationSendingQueueService notificationSendingQueueService,
INotificationRepository notificationRepository)
{
this.notificationSendingQueueService = notificationSendingQueueService;
this.notificationRepository = notificationRepository;
}
public async Task SendNotificationAsync(int idUser,
int idNotificationTransport,
int idNotificationCategory,
string title,
string subject,
TimeSpan timeToLife,
CancellationToken cancellationToken)
{
NotificationDto notification = new()
{
IdUser = idUser,
IdNotificationTransport = idNotificationTransport,
IdNotificationCategory = idNotificationCategory,
Title = title,
Subject = subject,
TimeToLife = timeToLife
};
await notificationRepository.InsertAsync(notification,
cancellationToken);
notificationSendingQueueService.Enqueue(notification);
}
public async Task UpdateNotificationAsync(int idNotification,
bool isRead,
CancellationToken cancellationToken)
{
var notification = await notificationRepository.GetOrDefaultAsync(idNotification,
cancellationToken) ?? throw new ArgumentInvalidException("Уведомление не найдено",
nameof(idNotification));
notification.IsRead = isRead;
await notificationRepository.UpdateAsync(notification,
cancellationToken);
}
public async Task ResendNotificationAsync(int idUser,
int idNotificationTransport,
CancellationToken cancellationToken)
{
var notifications = await notificationRepository.GetUnsentNotificationsAsync(idUser,
idNotificationTransport,
cancellationToken);
notificationSendingQueueService.EnqueueRange(notifications);
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.Controllers;
/// <summary>
/// Уведомления
/// </summary>
[ApiController]
[Authorize]
[Route("api/notification")]
public class NotificationController : ControllerBase
{
private readonly INotificationService notificationService;
private readonly INotificationRepository notificationRepository;
public NotificationController(INotificationService notificationService,
INotificationRepository notificationRepository)
{
this.notificationService = notificationService;
this.notificationRepository = notificationRepository;
}
/// <summary>
/// Метод отправки уведомления
/// </summary>
/// <param name="idUser">Id пользователя</param>
/// <param name="idNotificationTransport">Id способа отправки уведомления</param>
/// <param name="idNotificationCategory">Id категории уведомления</param>
/// <param name="title">Заголовок уведомления</param>
/// <param name="subject">Сообщение уведомления</param>
/// <param name="timeToLife">Время жизни уведомления</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost]
[Route("send")]
public async Task<IActionResult> SendAsync([Required] int idUser,
[Required] int idNotificationTransport,
[Required] int idNotificationCategory,
[Required] string title,
[Required] string subject,
[Required] TimeSpan timeToLife,
CancellationToken cancellationToken)
{
await notificationService.SendNotificationAsync(idUser,
idNotificationTransport,
idNotificationCategory,
title,
subject,
timeToLife,
cancellationToken);
return Ok();
}
/// <summary>
/// Метод обновления уведомления
/// </summary>
/// <param name="idNotification">Id уведомления</param>
/// <param name="isRead">Прочитано ли уведомление</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPut]
[Route("update")]
public async Task<IActionResult> UpdateAsync([Required] int idNotification,
[Required] bool isRead,
CancellationToken cancellationToken)
{
await notificationService.UpdateNotificationAsync(idNotification,
isRead,
cancellationToken);
return Ok();
}
/// <summary>
/// Метод получения уведомлений по параметрам
/// </summary>
/// <param name="skip">Кол-во пропускаемых записей</param>
/// <param name="take">Кол-во выбираемых записей</param>
/// <param name="idNotificationTransport">Id способа доставки уведомления</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet]
[Route("getList")]
[ProducesResponseType(typeof(PaginationContainer<NotificationDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetListAsync(int? skip,
int? take,
[Required] int idNotificationTransport,
CancellationToken cancellationToken)
{
int? idUser = User.GetUserId();
if (!idUser.HasValue)
return Forbid();
var result = await notificationRepository.GetNotificationsAsync(skip,
take,
idUser.Value,
idNotificationTransport,
cancellationToken);
return Ok(result);
}
/// <summary>
/// Метод удаления уведомления
/// </summary>
/// <param name="idNotification">Id уведомления</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpDelete]
[Route("delete")]
public async Task<IActionResult> DeleteAsync([Required] int idNotification,
CancellationToken cancellationToken)
{
await notificationRepository.DeleteAsync(idNotification,
cancellationToken);
return Ok();
}
}

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.OpenApi.Any;
namespace AsbCloudWebApi
{
@ -18,6 +19,7 @@ namespace AsbCloudWebApi
{
services.AddSwaggerGen(c =>
{
c.MapType<TimeSpan>(() => new OpenApiSchema { Type = "string", Example = new OpenApiString("00:00:00") });
c.MapType<DateOnly>(() => new OpenApiSchema { Type = "string", Format = "date" });
c.MapType<JsonValue>(() => new OpenApiSchema {
AnyOf = new OpenApiSchema[]