Рефакториг после ревью

1. Обновил классы модели и dto уведомления.
2. Удалил лишние сервисы.
3. Накатил новую миграцию.
4. Поправил репозиторий.
5. Поправил сервис уведомлений.
This commit is contained in:
parent daa780a766
commit b1d3da5f80
19 changed files with 8577 additions and 226 deletions

View File

@ -1,5 +1,4 @@
using System;
using System.Text.Json.Serialization;
namespace AsbCloudApp.Data;
@ -32,61 +31,45 @@ public class NotificationDto : IId
/// Сообщение уведомления
/// </summary>
public string Message { get; set; } = null!;
/// <summary>
/// Время жизни уведомления
/// </summary>
public TimeSpan TimeToLife { get; set; }
/// <summary>
/// Дата отправки уведомления
/// </summary>
public DateTime? SentDate { get; set; }
/// <summary>
/// Состояния уведомления
/// Дата прочтения уведомления
/// </summary>
public NotificationState NotificationState { get; set; }
public DateTime? ReadDate { get; set; }
/// <summary>
/// Состояние уведомления
/// 0 - Зарегистрировано,
/// 1 - Отправлено,
/// 2 - Прочитано
/// </summary>
public int IdState
{
get
{
if (SentDate is not null && ReadDate is not null)
return 2;
if (SentDate is not null)
return 1;
return 0;
}
}
/// <summary>
/// Способ доставки уведомления
/// Id типа доставки уведомления
/// 0 - SignalR
/// </summary>
public NotificationTransport NotificationTransport { get; set; }
public int IdTransportType { get; set; }
/// <summary>
/// DTO категории уведомления
/// </summary>
public NotificationCategoryDto NotificationCategory { get; set; } = null!;
}
/// <summary>
/// Состояние уведомления
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum NotificationState
{
/// <summary>
/// Зарегистрировано
/// </summary>
Registered = 1,
/// <summary>
/// Отправлено
/// </summary>
Sent = 2,
/// <summary>
/// Прочитано
/// </summary>
Read = 3,
}
/// <summary>
/// Способ отправки уведомления
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum NotificationTransport
{
/// <summary>
/// SignalR
/// </summary>
SignalR = 1
}

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
@ -12,18 +11,6 @@ namespace AsbCloudApp.Repositories;
/// </summary>
public interface INotificationRepository : ICrudRepository<NotificationDto>
{
/// <summary>
/// Получение не отправленных уведомлений
/// </summary>
/// <param name="idUser"></param>
/// <param name="notificationTransport"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<NotificationDto>> GetUnsentNotificationsAsync(int idUser,
NotificationTransport notificationTransport,
CancellationToken cancellationToken);
/// <summary>
/// Получение уведомлений по параметрам
/// </summary>

View File

@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
using AsbCloudApp.Data;
using System;
namespace AsbCloudApp.Requests;
@ -9,8 +8,12 @@ namespace AsbCloudApp.Requests;
public class NotificationRequest : RequestBase
{
/// <summary>
/// Способ отправки уведомления
/// Дата отправки уведомления
/// </summary>
[Required]
public NotificationTransport NotificationTransport { get; set; }
public DateTime? SentDate { get; set; }
/// <summary>
/// Id типа доставки уведомления
/// </summary>
public int? IdTransportType { get; set; }
}

View File

@ -1,16 +0,0 @@
using AsbCloudApp.Data;
namespace AsbCloudApp.Services.Notifications;
/// <summary>
/// Сервис для работы с отправителями уведомлений
/// </summary>
public interface INotificationSenderManager
{
/// <summary>
/// Метод получения нужного отправителя уведомлений
/// </summary>
/// <param name="notificationTransport"></param>
/// <returns></returns>
INotificationSender? GetOrDefault(NotificationTransport notificationTransport);
}

View File

@ -1,7 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
namespace AsbCloudApp.Services.Notifications;
@ -17,16 +16,14 @@ public interface INotificationService
/// <param name="idNotificationCategory"></param>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="timeToLife"></param>
/// <param name="notificationTransport"></param>
/// <param name="idTransportType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task NotifyAsync(int idUser,
int idNotificationCategory,
string title,
string message,
TimeSpan timeToLife,
NotificationTransport notificationTransport,
int idTransportType,
CancellationToken cancellationToken);
/// <summary>
@ -44,10 +41,10 @@ public interface INotificationService
/// Отправка уведомлений, которые не были отправлены
/// </summary>
/// <param name="idUser"></param>
/// <param name="notificationTransport"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task ResendNotificationAsync(int idUser,
NotificationTransport notificationTransport,
NotificationRequest request,
CancellationToken cancellationToken);
}

View File

@ -8,12 +8,12 @@ namespace AsbCloudApp.Services.Notifications;
/// <summary>
/// Интерфейс для отправителя уведомлений
/// </summary>
public interface INotificationSender
public interface INotificationTransportService
{
/// <summary>
/// Способ отправки уведомлений
/// Id типа доставки уведомления
/// </summary>
NotificationTransport NotificationTransport { get; }
int IdTransportType { get; }
/// <summary>
/// Отправка одного уведомлений

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Update_Notification : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "notification_state",
table: "t_notification");
migrationBuilder.DropColumn(
name: "notification_transport",
table: "t_notification");
migrationBuilder.DropColumn(
name: "time_to_life",
table: "t_notification");
migrationBuilder.AddColumn<int>(
name: "id_transport_type",
table: "t_notification",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "Id типа доставки уведомления");
migrationBuilder.AddColumn<DateTime>(
name: "read_date",
table: "t_notification",
type: "timestamp with time zone",
nullable: true,
comment: "Дата прочтения уведомления");
migrationBuilder.AlterColumn<int>(
name: "id_category",
table: "t_help_page",
type: "integer",
nullable: false,
comment: "Id категории файла",
oldClrType: typeof(int),
oldType: "integer",
oldComment: "id категории файла");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "id_transport_type",
table: "t_notification");
migrationBuilder.DropColumn(
name: "read_date",
table: "t_notification");
migrationBuilder.AddColumn<string>(
name: "notification_state",
table: "t_notification",
type: "text",
nullable: false,
defaultValue: "",
comment: "Состояние уведомления");
migrationBuilder.AddColumn<string>(
name: "notification_transport",
table: "t_notification",
type: "text",
nullable: false,
defaultValue: "",
comment: "Метод доставки уведомления");
migrationBuilder.AddColumn<TimeSpan>(
name: "time_to_life",
table: "t_notification",
type: "interval",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0),
comment: "Время жизни уведомления");
migrationBuilder.AlterColumn<int>(
name: "id_category",
table: "t_help_page",
type: "integer",
nullable: false,
comment: "id категории файла",
oldClrType: typeof(int),
oldType: "integer",
oldComment: "Id категории файла");
}
}
}

View File

@ -1166,6 +1166,11 @@ namespace AsbCloudDb.Migrations
.HasColumnName("id_notification_category")
.HasComment("Id категории уведомления");
b.Property<int>("IdTransportType")
.HasColumnType("integer")
.HasColumnName("id_transport_type")
.HasComment("Id типа доставки уведомления");
b.Property<int>("IdUser")
.HasColumnType("integer")
.HasColumnName("id_user")
@ -1177,28 +1182,16 @@ namespace AsbCloudDb.Migrations
.HasColumnName("message")
.HasComment("Сообщение уведомления");
b.Property<string>("NotificationState")
.IsRequired()
.HasColumnType("text")
.HasColumnName("notification_state")
.HasComment("Состояние уведомления");
b.Property<string>("NotificationTransport")
.IsRequired()
.HasColumnType("text")
.HasColumnName("notification_transport")
.HasComment("Метод доставки уведомления");
b.Property<DateTime?>("ReadDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("read_date")
.HasComment("Дата прочтения уведомления");
b.Property<DateTime?>("SentDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("sent_date")
.HasComment("Дата отправки уведомления");
b.Property<TimeSpan>("TimeToLife")
.HasColumnType("interval")
.HasColumnName("time_to_life")
.HasComment("Время жизни уведомления");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text")

View File

@ -23,18 +23,15 @@ public class Notification : IId
[Column("message"), Comment("Сообщение уведомления")]
public string Message { get; set; } = null!;
[Column("time_to_life"), Comment("Время жизни уведомления")]
public TimeSpan TimeToLife { get; set; }
[Column("sent_date"), Comment("Дата отправки уведомления")]
public DateTime? SentDate { get; set; }
[Column("notification_state"), Comment("Состояние уведомления")]
public string NotificationState { get; set; } = null!;
[Column("notification_transport"), Comment("Метод доставки уведомления")]
public string NotificationTransport { get; set; } = null!;
[Column("read_date"), Comment("Дата прочтения уведомления")]
public DateTime? ReadDate { get; set; }
[Column("id_transport_type"), Comment("Id типа доставки уведомления")]
public int IdTransportType { get; set; }
[ForeignKey(nameof(IdNotificationCategory))]
public virtual NotificationCategory NotificationCategory { get; set; } = null!;

View File

@ -147,6 +147,8 @@ namespace AsbCloudInfrastructure
services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<ICrudRepository<NotificationCategoryDto>, CrudCacheRepositoryBase<NotificationCategoryDto,
NotificationCategory>>();
// admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>

View File

@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -25,34 +23,19 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
{
}
public async Task<IEnumerable<NotificationDto>> GetUnsentNotificationsAsync(int idUser,
NotificationTransport notificationTransport,
CancellationToken cancellationToken)
{
var notifications = (await GetCacheAsync(cancellationToken))
.Where(x => x.IdUser == idUser &&
x.NotificationTransport == notificationTransport.ToString() &&
x.SentDate == null);
return notifications.Select(Convert);
}
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
NotificationRequest request,
CancellationToken cancellationToken)
{
request.Skip ??= 0;
request.Take ??= 10;
var skip = request.Skip ?? 0;
var take = request.Take ?? 10;
var query = dbContext.Notifications
.Where(x => x.NotificationTransport == request.NotificationTransport.ToString() &&
x.IdUser == idUser &&
x.SentDate != null);
var query = BuildQuery(idUser, request);
var result = new PaginationContainer<NotificationDto>()
{
Skip = request.Skip.Value,
Take = request.Take.Value,
Skip = skip,
Take = take,
Count = await query.CountAsync(cancellationToken),
};
@ -60,9 +43,9 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
return result;
result.Items = await query
.OrderBy(x => x.SentDate)
.SortBy(request.SortFields)
.SkipTake(request.Skip, request.Take)
.Skip(skip)
.Take(take)
.Include(x => x.NotificationCategory)
.Select(x => x.Adapt<NotificationDto>())
.ToListAsync(cancellationToken);
@ -70,23 +53,20 @@ public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, N
return result;
}
protected override Notification Convert(NotificationDto src)
private IQueryable<Notification> BuildQuery(int? idUser,
NotificationRequest request)
{
var entity = src.Adapt<Notification>();
var query = dbContext.Notifications.AsQueryable();
entity.NotificationState = src.NotificationState.ToString();
entity.NotificationTransport = src.NotificationTransport.ToString();
if (!request.SentDate.HasValue)
query = query.Where(n => n.SentDate == null);
if (idUser.HasValue)
query = query.Where(n => n.IdUser == idUser);
return entity;
}
if (request.IdTransportType.HasValue)
query = query.Where(n => n.IdTransportType == request.IdTransportType);
protected override NotificationDto Convert(Notification src)
{
var dto = src.Adapt<NotificationDto>();
dto.NotificationState = (NotificationState)Enum.Parse(typeof(NotificationState), src.NotificationState);
dto.NotificationTransport = (NotificationTransport)Enum.Parse(typeof(NotificationTransport), src.NotificationTransport);
return dto;
return query;
}
}

View File

@ -1,21 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using AsbCloudApp.Data;
using AsbCloudApp.Services.Notifications;
namespace AsbCloudInfrastructure.Services.Notifications;
public class NotificationSenderManager : INotificationSenderManager
{
private readonly IEnumerable<INotificationSender> notificationSenders;
public NotificationSenderManager(IEnumerable<INotificationSender> notificationSenders)
{
this.notificationSenders = notificationSenders;
}
public INotificationSender? GetOrDefault(NotificationTransport notificationTransport)
{
return notificationSenders.FirstOrDefault(x => x.NotificationTransport == notificationTransport);
}
}

View File

@ -1,83 +1,105 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.Notifications;
using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudInfrastructure.Services.Notifications;
public class NotificationService : INotificationService
{
private readonly INotificationSenderManager notificationSenderManager;
private readonly ICrudRepository<NotificationCategoryDto> notificationCategoryRepository;
private readonly INotificationRepository notificationRepository;
private readonly IServiceProvider serviceProvider;
public NotificationService(INotificationSenderManager notificationSenderManager,
INotificationRepository notificationRepository)
public NotificationService(ICrudRepository<NotificationCategoryDto> notificationCategoryRepository,
INotificationRepository notificationRepository,
IServiceProvider serviceProvider)
{
this.notificationSenderManager = notificationSenderManager;
this.notificationCategoryRepository = notificationCategoryRepository;
this.notificationRepository = notificationRepository;
this.serviceProvider = serviceProvider;
}
public async Task NotifyAsync(int idUser,
int idNotificationCategory,
string title,
string message,
TimeSpan timeToLife,
NotificationTransport notificationTransport,
int idTransportType,
CancellationToken cancellationToken)
{
var notificationCategory = await notificationCategoryRepository
.GetOrDefaultAsync(idNotificationCategory, cancellationToken);
if(notificationCategory is null)
throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(idNotificationCategory));
var notification = new NotificationDto()
{
IdUser = idUser,
IdNotificationCategory = idNotificationCategory,
Title = title,
Message = message,
TimeToLife = timeToLife,
NotificationTransport = notificationTransport,
NotificationState = NotificationState.Registered
IdTransportType = idTransportType
};
notification.Id = await notificationRepository.InsertAsync(notification,
cancellationToken);
var notificationSender = notificationSenderManager.GetOrDefault(notificationTransport);
if(notificationSender is null)
throw new ArgumentInvalidException("Метод отправки уведомления не найден", nameof(notificationTransport));
notification.NotificationCategory = notificationCategory;
await notificationSender.SendAsync(notification, cancellationToken);
var notificationTransportService = GetNotificationTransportService(idTransportType);
await notificationTransportService.SendAsync(notification, cancellationToken);
}
public async Task UpdateNotificationAsync(int idNotification,
bool isRead,
bool isRead,
CancellationToken cancellationToken)
{
var notification = await notificationRepository.GetOrDefaultAsync(idNotification,
cancellationToken) ?? throw new ArgumentInvalidException("Уведомление не найдено",
nameof(idNotification));
notification.NotificationState = isRead ? NotificationState.Read : NotificationState.Sent;
if (isRead)
{
if (notification.SentDate == null)
throw new ArgumentInvalidException("Уведомление не может быть прочитано", nameof(isRead));
notification.SentDate = DateTime.UtcNow;
}
await notificationRepository.UpdateAsync(notification,
cancellationToken);
}
public async Task ResendNotificationAsync(int idUser,
NotificationTransport notificationTransport,
NotificationRequest request,
CancellationToken cancellationToken)
{
var notifications = await notificationRepository.GetUnsentNotificationsAsync(idUser,
notificationTransport,
var result = await notificationRepository.GetNotificationsAsync(idUser,
request,
cancellationToken);
var notificationSender = notificationSenderManager.GetOrDefault(notificationTransport);
if(notificationSender is null)
throw new ArgumentInvalidException("Отправитель уведомления не найден", nameof(notificationTransport));
await notificationSender.SendRangeAsync(notifications, cancellationToken);
var notificationTransportService = GetNotificationTransportService(request.IdTransportType!.Value);
await notificationTransportService.SendRangeAsync(result.Items, cancellationToken);
}
private INotificationTransportService GetNotificationTransportService(int idTransportType)
{
var notificationTransportService = serviceProvider.GetServices<INotificationTransportService>()
.FirstOrDefault(s => s.IdTransportType == idTransportType);
if(notificationTransportService is null)
throw new ArgumentInvalidException("Доставщик уведомлений не найден", nameof(idTransportType));
return notificationTransportService;
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
@ -34,29 +33,30 @@ public class NotificationController : ControllerBase
/// Отправка уведомления
/// </summary>
/// <param name="idUser">Id пользователя</param>
/// <param name="idNotificationCategory">Id категории уведомления</param>
/// <param name="idNotificationCategory">Id категории уведомления. Допустимое значение параметра: 1</param>
/// <param name="title">Заголовок уведомления</param>
/// <param name="message">Сообщение уведомления</param>
/// <param name="timeToLife">Время жизни уведомления</param>
/// <param name="notificationTransport">Способ отправки уведомления</param>
/// <param name="idNotificationTransport">Id типа доставки уведомления. Допустимое значение: 0</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost]
[Route("send")]
public async Task<IActionResult> SendAsync([Required] int idUser,
[Required] int idNotificationCategory,
[Required]
[Range(minimum: 1, maximum: 1, ErrorMessage = "Id категории уведомления недоступно. Допустимые: 1")]
int idNotificationCategory,
[Required] string title,
[Required] string message,
[Required] TimeSpan timeToLife,
[Required] NotificationTransport notificationTransport,
[Required]
[Range(minimum: 0, maximum: 0, ErrorMessage = "Id способа отправки уведомления недоступно. Допустимые: 0")]
int idNotificationTransport,
CancellationToken cancellationToken)
{
await notificationService.NotifyAsync(idUser,
idNotificationCategory,
title,
message,
timeToLife,
notificationTransport,
message,
idNotificationTransport,
cancellationToken);
return Ok();

View File

@ -10,7 +10,6 @@ using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Services.Notifications;
using AsbCloudWebApi.SignalR.Services;
using Microsoft.OpenApi.Any;
@ -109,10 +108,9 @@ namespace AsbCloudWebApi
});
}
public static void AddNotificationSenders(this IServiceCollection services)
public static void AddNotificationTransportServices(this IServiceCollection services)
{
services.AddSingleton<INotificationSenderManager, NotificationSenderManager>();
services.AddSingleton<INotificationSender, SignalRNotificationSender>();
services.AddSingleton<INotificationTransportService, SignalRNotificationTransportService>();
}
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using AsbCloudApp.Services.Notifications;
using AsbCloudWebApi.SignalR.ConnectionManager;
using Microsoft.AspNetCore.Authorization;
@ -21,7 +21,7 @@ public class NotificationHub : BaseHub
this.notificationService = notificationService;
}
public async Task OnConnected(int idUser)
public async Task OnConnected(int idUser, NotificationRequest request)
{
try
{
@ -30,7 +30,7 @@ public class NotificationHub : BaseHub
connectionManager.AddConnection(idUser, connectionId);
await notificationService.ResendNotificationAsync(idUser,
NotificationTransport.SignalR,
request,
CancellationToken.None);
await base.OnConnectedAsync();

View File

@ -11,13 +11,13 @@ using Microsoft.Extensions.DependencyInjection;
namespace AsbCloudWebApi.SignalR.Services;
public class SignalRNotificationSender : INotificationSender
public class SignalRNotificationTransportService : INotificationTransportService
{
private readonly IConnectionManager connectionManager;
private readonly IHubContext<NotificationHub> notificationHubContext;
private readonly IServiceProvider serviceProvider;
public SignalRNotificationSender(IConnectionManager connectionManager,
public SignalRNotificationTransportService(IConnectionManager connectionManager,
IHubContext<NotificationHub> notificationHubContext,
IServiceProvider serviceProvider)
{
@ -25,9 +25,9 @@ public class SignalRNotificationSender : INotificationSender
this.notificationHubContext = notificationHubContext;
this.serviceProvider = serviceProvider;
}
public NotificationTransport NotificationTransport => NotificationTransport.SignalR;
public int IdTransportType => 0;
public async Task SendAsync(NotificationDto notification,
CancellationToken cancellationToken)
{
@ -37,16 +37,13 @@ public class SignalRNotificationSender : INotificationSender
if (!string.IsNullOrWhiteSpace(connectionId))
{
string message = $"IdNotification: {notification.Id}";
notification.SentDate = DateTime.UtcNow;
await notificationHubContext.Clients.Client(connectionId)
.SendAsync(method,
message,
notification,
cancellationToken);
notification.SentDate = DateTime.UtcNow;
notification.NotificationState = NotificationState.Sent;
var scope = serviceProvider.CreateScope();
var notificationRepository = scope.ServiceProvider.GetService<INotificationRepository>();

View File

@ -43,7 +43,7 @@ namespace AsbCloudWebApi
services.AddInfrastructure(Configuration);
services.AddNotificationSenders();
services.AddNotificationTransportServices();
services.AddJWTAuthentication();