Отправка уведомлений через email + рефакторинг

1. Адаптировал EmailService под сервис транспорта отправки уведомлений по Email
2. Заменил использование EmailService на NotificationService
3. Поправил тесты
4. Создал запрос для отправки уведомлений
This commit is contained in:
parent 8f76a911a2
commit 4b2d4f1bba
13 changed files with 232 additions and 135 deletions

View File

@ -17,6 +17,11 @@ public class NotificationDto : IId
/// </summary> /// </summary>
public int IdUser { get; set; } public int IdUser { get; set; }
/// <summary>
/// Email получателя уведомления
/// </summary>
public string? UserEmail { get; set; }
/// <summary> /// <summary>
/// Id категории уведомления /// Id категории уведомления
/// </summary> /// </summary>
@ -65,6 +70,7 @@ public class NotificationDto : IId
/// <summary> /// <summary>
/// Id типа доставки уведомления /// Id типа доставки уведомления
/// 0 - SignalR /// 0 - SignalR
/// 1 - Email
/// </summary> /// </summary>
public int IdTransportType { get; set; } public int IdTransportType { get; set; }

View File

@ -0,0 +1,37 @@
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса для отправки уведомления
/// </summary>
public class NotifyRequest
{
/// <summary>
/// Id пользователя
/// </summary>
public int IdUser { get; set; }
/// <summary>
/// Email пользователя
/// </summary>
public string? UserEmail { get; set; }
/// <summary>
/// Id категории уведомления. Допустимое значение параметра: 1
/// </summary>
public int IdNotificationCategory { get; set; }
/// <summary>
/// Заголовок уведомления
/// </summary>
public string Title { get; set; } = null!;
/// <summary>
/// Сообщение уведомления
/// </summary>
public string Message { get; set; } = null!;
/// <summary>
/// Id типа доставки уведомления. Допустимое значение: 0, 1
/// </summary>
public int IdTransportType { get; set; }
}

View File

@ -1,26 +0,0 @@
using System.Collections.Generic;
namespace AsbCloudApp.Services
{
/// <summary>
/// Сервис отправки сообщений
/// </summary>
public interface IEmailService
{
/// <summary>
/// добавить сообщение на отправку нескольким пользователям
/// </summary>
/// <param name="addresses"></param>
/// <param name="subject"></param>
/// <param name="htmlBody"></param>
void EnqueueSend(IEnumerable<string> addresses, string subject, string htmlBody);
/// <summary>
/// добавить сообщение на отправку одному пользователю
/// </summary>
/// <param name="address"></param>
/// <param name="subject"></param>
/// <param name="htmlBody"></param>
void EnqueueSend(string address, string subject, string htmlBody);
}
}

View File

@ -33,35 +33,27 @@ public class NotificationService
this.notificationRepository = notificationRepository; this.notificationRepository = notificationRepository;
this.notificationTransportServices = notificationTransportServices; this.notificationTransportServices = notificationTransportServices;
} }
/// <summary> /// <summary>
/// Отправка нового уведомления /// Отправка нового уведомления
/// </summary> /// </summary>
/// <param name="idUser"></param> /// <param name="request"></param>
/// <param name="idNotificationCategory"></param>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="idTransportType"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> public async Task NotifyAsync(NotifyRequest request,
public async Task NotifyAsync(int idUser,
int idNotificationCategory,
string title,
string message,
int idTransportType,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var notificationCategory = await notificationCategoryRepository var notificationCategory = await notificationCategoryRepository
.GetOrDefaultAsync(idNotificationCategory, cancellationToken) .GetOrDefaultAsync(request.IdNotificationCategory, cancellationToken)
?? throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(idNotificationCategory)); ?? throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(request.IdNotificationCategory));
var notification = new NotificationDto() var notification = new NotificationDto
{ {
IdUser = idUser, IdUser = request.IdUser,
IdNotificationCategory = idNotificationCategory, UserEmail = request.UserEmail,
Title = title, IdNotificationCategory = request.IdNotificationCategory,
Message = message, Title = request.Title,
IdTransportType = idTransportType Message = request.Message,
IdTransportType = request.IdTransportType
}; };
notification.Id = await notificationRepository.InsertAsync(notification, notification.Id = await notificationRepository.InsertAsync(notification,
@ -69,7 +61,7 @@ public class NotificationService
notification.NotificationCategory = notificationCategory; notification.NotificationCategory = notificationCategory;
var notificationTransportService = GetNotificationTransportService(idTransportType); var notificationTransportService = GetNotificationTransportService(request.IdTransportType);
await notificationTransportService.SendAsync(notification, cancellationToken); await notificationTransportService.SendAsync(notification, cancellationToken);

View File

@ -103,7 +103,6 @@ namespace AsbCloudInfrastructure
services.AddMemoryCache(); services.AddMemoryCache();
services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>()); services.AddScoped<IAsbCloudDbContext>(provider => provider.GetRequiredService<AsbCloudDbContext>());
services.AddScoped<IEmailService, EmailService>();
services.AddSingleton(new WitsInfoService()); services.AddSingleton(new WitsInfoService());
services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider)); services.AddSingleton(provider => TelemetryDataCache<TelemetryDataSaubDto>.GetInstance<TelemetryDataSaub>(provider));

View File

@ -16,6 +16,9 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Requests;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Services.Email;
namespace AsbCloudInfrastructure.Services.DrillingProgram namespace AsbCloudInfrastructure.Services.DrillingProgram
{ {
@ -23,15 +26,18 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
public class DrillingProgramService : IDrillingProgramService public class DrillingProgramService : IDrillingProgramService
{ {
private static readonly Dictionary<string, DrillingProgramCreateError> drillingProgramCreateErrors = new Dictionary<string, DrillingProgramCreateError>(); private static readonly Dictionary<string, DrillingProgramCreateError> drillingProgramCreateErrors = new Dictionary<string, DrillingProgramCreateError>();
private readonly IAsbCloudDbContext context; private readonly IAsbCloudDbContext context;
private readonly FileService fileService; private readonly FileService fileService;
private readonly IUserRepository userRepository; private readonly IUserRepository userRepository;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IConfiguration configuration; private readonly IConfiguration configuration;
private readonly BackgroundWorker backgroundWorker; private readonly BackgroundWorker backgroundWorker;
private readonly IEmailService emailService; private readonly NotificationService notificationService;
private const int idNotificationCategory = 20000;
private const int idTransportType = 1;
private const int idFileCategoryDrillingProgram = 1000; private const int idFileCategoryDrillingProgram = 1000;
private const int idFileCategoryDrillingProgramPartsStart = 1001; private const int idFileCategoryDrillingProgramPartsStart = 1001;
private const int idFileCategoryDrillingProgramPartsEnd = 1100; private const int idFileCategoryDrillingProgramPartsEnd = 1100;
@ -61,7 +67,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
IWellService wellService, IWellService wellService,
IConfiguration configuration, IConfiguration configuration,
BackgroundWorker backgroundWorker, BackgroundWorker backgroundWorker,
IEmailService emailService) NotificationService notificationService)
{ {
this.context = context; this.context = context;
this.fileService = fileService; this.fileService = fileService;
@ -69,7 +75,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
this.wellService = wellService; this.wellService = wellService;
this.configuration = configuration; this.configuration = configuration;
this.backgroundWorker = backgroundWorker; this.backgroundWorker = backgroundWorker;
this.emailService = emailService; this.notificationService = notificationService;
} }
public async Task<IEnumerable<UserDto>> GetAvailableUsers(int idWell, CancellationToken token = default) public async Task<IEnumerable<UserDto>> GetAvailableUsers(int idWell, CancellationToken token = default)
@ -378,7 +384,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
var subject = factory.MakeSubject(well, "Загруженный вами документ полностью согласован"); var subject = factory.MakeSubject(well, "Загруженный вами документ полностью согласован");
var body = factory.MakeMailBodyForPublisherOnFullAccept(well, user.Name ?? string.Empty, file.Id, file.Name); var body = factory.MakeMailBodyForPublisherOnFullAccept(well, user.Name ?? string.Empty, file.Id, file.Name);
emailService.EnqueueSend(user.Email, subject, body); await notificationService.NotifyAsync(new NotifyRequest
{
IdUser = user.Id,
UserEmail = user.Email,
IdNotificationCategory = idNotificationCategory,
Title = subject,
Message = body,
IdTransportType = idTransportType
}, token);
} }
private async Task NotifyPublisherOnRejectAsync(FileMarkDto fileMark, CancellationToken token) private async Task NotifyPublisherOnRejectAsync(FileMarkDto fileMark, CancellationToken token)
@ -393,7 +407,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
var subject = factory.MakeSubject(well, "Загруженный вами документ отклонен"); var subject = factory.MakeSubject(well, "Загруженный вами документ отклонен");
var body = factory.MakeMailBodyForPublisherOnReject(well, user.Name ?? string.Empty, file.Id, file.Name, fileMark); var body = factory.MakeMailBodyForPublisherOnReject(well, user.Name ?? string.Empty, file.Id, file.Name, fileMark);
emailService.EnqueueSend(user.Email, subject, body); await notificationService.NotifyAsync(new NotifyRequest
{
IdUser = user.Id,
UserEmail = user.Email,
IdNotificationCategory = idNotificationCategory,
Title = subject,
Message = body,
IdTransportType = idTransportType
}, token);
} }
private async Task NotifyApproversAsync(DrillingProgramPart part, int idFile, string fileName, CancellationToken token) private async Task NotifyApproversAsync(DrillingProgramPart part, int idFile, string fileName, CancellationToken token)
@ -411,7 +433,15 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
foreach (var user in users) foreach (var user in users)
{ {
var body = factory.MakeMailBodyForApproverNewFile(well, user.Name ?? string.Empty, idFile, fileName); var body = factory.MakeMailBodyForApproverNewFile(well, user.Name ?? string.Empty, idFile, fileName);
emailService.EnqueueSend(user.Email, subject, body); await notificationService.NotifyAsync(new NotifyRequest
{
IdUser = user.Id,
UserEmail = user.Email,
IdNotificationCategory = idNotificationCategory,
Title = subject,
Message = body,
IdTransportType = idTransportType
}, token);
} }
} }
@ -424,7 +454,16 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram
var factory = new DrillingMailBodyFactory(configuration); var factory = new DrillingMailBodyFactory(configuration);
var subject = factory.MakeSubject(well, $"От вас ожидается загрузка на портал документа «{documentCategory}»"); var subject = factory.MakeSubject(well, $"От вас ожидается загрузка на портал документа «{documentCategory}»");
var body = factory.MakeMailBodyForNewPublisher(well, user.Name ?? string.Empty, documentCategory); var body = factory.MakeMailBodyForNewPublisher(well, user.Name ?? string.Empty, documentCategory);
emailService.EnqueueSend(user.Email, subject, body);
await notificationService.NotifyAsync(new NotifyRequest
{
IdUser = user.Id,
UserEmail = user.Email,
IdNotificationCategory = idNotificationCategory,
Title = subject,
Message = body,
IdTransportType = idTransportType
}, token);
} }
private static DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> files, DrillingProgramPart partEntity, double timezoneOffset) private static DrillingProgramPartDto ConvertPart(int idUser, List<FileCategory> fileCategories, List<AsbCloudDb.Model.FileInfo> files, DrillingProgramPart partEntity, double timezoneOffset)

View File

@ -1,10 +1,7 @@
using AsbCloudApp.Data; using AsbCloudApp.Data;
using AsbCloudInfrastructure.Services.Email;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System;
using System.IO;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure.Services.Email
{ {
class DrillingMailBodyFactory : BaseFactory class DrillingMailBodyFactory : BaseFactory

View File

@ -1,19 +1,20 @@
using AsbCloudApp.Exceptions; using System;
using AsbCloudApp.Services;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net.Mail; using System.Net.Mail;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Background;
using Microsoft.Extensions.Configuration;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services.Email
{ {
public class EmailService : IEmailService public class EmailNotificationTransportService : INotificationTransportService
{ {
private readonly BackgroundWorker backgroundWorker; private readonly BackgroundWorker backgroundWorker;
private readonly bool IsConfigured; private readonly bool IsConfigured;
@ -21,7 +22,8 @@ namespace AsbCloudInfrastructure.Services
private readonly string smtpServer; private readonly string smtpServer;
private readonly string smtpPassword; private readonly string smtpPassword;
public EmailService(BackgroundWorker backgroundWorker, IConfiguration configuration) public EmailNotificationTransportService(BackgroundWorker backgroundWorker,
IConfiguration configuration)
{ {
sender = configuration.GetValue("email:sender", string.Empty); sender = configuration.GetValue("email:sender", string.Empty);
smtpPassword = configuration.GetValue("email:password", string.Empty); smtpPassword = configuration.GetValue("email:password", string.Empty);
@ -36,25 +38,42 @@ namespace AsbCloudInfrastructure.Services
this.backgroundWorker = backgroundWorker; this.backgroundWorker = backgroundWorker;
} }
public void EnqueueSend(string address, string subject, string htmlBody) public int IdTransportType => 1;
=> EnqueueSend(new List<string> { address }, subject, htmlBody);
public Task SendAsync(NotificationDto notification, CancellationToken cancellationToken)
public void EnqueueSend(IEnumerable<string> addresses, string subject, string htmlBody)
{ {
if (!IsConfigured) if (!IsConfigured)
{ {
Trace.TraceWarning("smtp is not configured"); Trace.TraceWarning("smtp is not configured");
return; return Task.CompletedTask;
} }
var workId = MakeWorkId(addresses, subject, htmlBody);
if (string.IsNullOrWhiteSpace(notification.UserEmail))
{
Trace.TraceWarning("User email is not null");
return Task.CompletedTask;
}
var workId = MakeWorkId(new []{ notification.UserEmail }, notification.Title, notification.Message);
if (!backgroundWorker.Contains(workId)) if (!backgroundWorker.Contains(workId))
{ {
var workAction = MakeEmailSendWorkAction(addresses, subject, htmlBody); var workAction = MakeEmailSendWorkAction(new []{ notification.UserEmail }, notification.Title, notification.Message);
var work = new WorkBase(workId, workAction); var work = new WorkBase(workId, workAction);
backgroundWorker.Push(work); backgroundWorker.Push(work);
} }
return Task.CompletedTask;
} }
public Task SendRangeAsync(IEnumerable<NotificationDto> notifications, CancellationToken cancellationToken)
{
var tasks = notifications
.Select(notification => SendAsync(notification, cancellationToken));
return Task.WhenAll(tasks);
}
private Func<string, IServiceProvider, CancellationToken, Task> MakeEmailSendWorkAction(IEnumerable<string> addresses, string subject, string htmlBody) private Func<string, IServiceProvider, CancellationToken, Task> MakeEmailSendWorkAction(IEnumerable<string> addresses, string subject, string htmlBody)
{ {
var mailAddresses = new List<MailAddress>(); var mailAddresses = new List<MailAddress>();

View File

@ -10,6 +10,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications;
namespace AsbCloudInfrastructure.Services namespace AsbCloudInfrastructure.Services
{ {
@ -23,9 +24,9 @@ namespace AsbCloudInfrastructure.Services
private readonly IUserRepository userRepository; private readonly IUserRepository userRepository;
private readonly IWellService wellService; private readonly IWellService wellService;
private readonly IConfiguration configuration; private readonly IConfiguration configuration;
private readonly IEmailService emailService;
private readonly IFileCategoryService fileCategoryService; private readonly IFileCategoryService fileCategoryService;
private readonly IWellFinalDocumentsRepository wellFinalDocumentsRepository; private readonly IWellFinalDocumentsRepository wellFinalDocumentsRepository;
private readonly NotificationService notificationService;
private const int FileServiceThrewException = -1; private const int FileServiceThrewException = -1;
@ -33,17 +34,17 @@ namespace AsbCloudInfrastructure.Services
IUserRepository userRepository, IUserRepository userRepository,
IWellService wellService, IWellService wellService,
IConfiguration configuration, IConfiguration configuration,
IEmailService emailService,
IFileCategoryService fileCategoryService, IFileCategoryService fileCategoryService,
IWellFinalDocumentsRepository wellFinalDocumentsRepository) IWellFinalDocumentsRepository wellFinalDocumentsRepository,
NotificationService notificationService)
{ {
this.fileService = fileService; this.fileService = fileService;
this.userRepository = userRepository; this.userRepository = userRepository;
this.wellService = wellService; this.wellService = wellService;
this.configuration = configuration; this.configuration = configuration;
this.emailService = emailService;
this.fileCategoryService = fileCategoryService; this.fileCategoryService = fileCategoryService;
this.wellFinalDocumentsRepository = wellFinalDocumentsRepository; this.wellFinalDocumentsRepository = wellFinalDocumentsRepository;
this.notificationService = notificationService;
} }
///<inheritdoc/> ///<inheritdoc/>
@ -129,27 +130,36 @@ namespace AsbCloudInfrastructure.Services
if(well is null) if(well is null)
throw new ArgumentInvalidException("idWell doesn`t exist", nameof(item.IdWell)); throw new ArgumentInvalidException("idWell doesn`t exist", nameof(item.IdWell));
SendMessage(well, user, category?.Name ?? string.Empty, message); await SendMessageAsync(well, user, category?.Name ?? string.Empty, message,
token);
} }
} }
} }
private void SendMessage(WellDto well, UserDto user, string documentCategory, string message) private async Task SendMessageAsync(WellDto well, UserDto user, string documentCategory, string message,
CancellationToken cancellationToken)
{ {
const int idNotificationCategory = 20000;
const int idTransportType = 1;
var factory = new WellFinalDocumentMailBodyFactory(configuration); var factory = new WellFinalDocumentMailBodyFactory(configuration);
var subject = factory.MakeSubject(well, documentCategory); var subject = factory.MakeSubject(well, documentCategory);
if(!string.IsNullOrEmpty(user.Email)) var body = factory.MakeMailBodyForWellFinalDocument(
{ well,
var body = factory.MakeMailBodyForWellFinalDocument( (user.Name ?? user.Surname ?? string.Empty),
well, string.Format(message, documentCategory)
(user.Name ?? user.Surname ?? string.Empty), );
string.Format(message, documentCategory)
);
emailService.EnqueueSend(user.Email, subject, body); await notificationService.NotifyAsync(new NotifyRequest
} {
IdUser = user.Id,
UserEmail = user.Email,
IdNotificationCategory = idNotificationCategory,
Title = subject,
Message = body,
IdTransportType = idTransportType
}, cancellationToken);
} }
} }

View File

@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications;
using Xunit; using Xunit;
namespace AsbCloudWebApi.Tests.ServicesTests namespace AsbCloudWebApi.Tests.ServicesTests
@ -85,7 +86,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
private readonly Mock<IWellService> wellServiceMock; private readonly Mock<IWellService> wellServiceMock;
private readonly Mock<IConfiguration> configurationMock; private readonly Mock<IConfiguration> configurationMock;
private readonly Mock<BackgroundWorker> backgroundWorkerMock; private readonly Mock<BackgroundWorker> backgroundWorkerMock;
private readonly Mock<IEmailService> emailServiceMock; private readonly Mock<NotificationService> notificationServiceMock;
public DrillingProgramServiceTest() public DrillingProgramServiceTest()
{ {
@ -104,7 +105,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock = new Mock<IWellService>(); wellServiceMock = new Mock<IWellService>();
configurationMock = new Mock<IConfiguration>(); configurationMock = new Mock<IConfiguration>();
backgroundWorkerMock = new Mock<BackgroundWorker>(); backgroundWorkerMock = new Mock<BackgroundWorker>();
emailServiceMock = new Mock<IEmailService>(); notificationServiceMock = new Mock<NotificationService>();
} }
[Fact] [Fact]
@ -117,7 +118,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var users = await service.GetAvailableUsers(idWell, CancellationToken.None); var users = await service.GetAvailableUsers(idWell, CancellationToken.None);
@ -134,7 +135,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var result = await service.AddPartsAsync(idWell, new int[] { 1001, 1002 }, CancellationToken.None); var result = await service.AddPartsAsync(idWell, new int[] { 1001, 1002 }, CancellationToken.None);
@ -153,7 +154,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var result = await service.RemovePartsAsync(idWell, new int[] { 1005 }, CancellationToken.None); var result = await service.RemovePartsAsync(idWell, new int[] { 1005 }, CancellationToken.None);
@ -176,7 +177,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var result = await service.AddUserAsync(idWell, 1001, publisher1.Id, 1, CancellationToken.None); var result = await service.AddUserAsync(idWell, 1001, publisher1.Id, 1, CancellationToken.None);
@ -211,7 +212,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var result = await service.RemoveUserAsync(idWell, idFileCategory, publisher1.Id, idUserRole, CancellationToken.None); var result = await service.RemoveUserAsync(idWell, idFileCategory, publisher1.Id, idUserRole, CancellationToken.None);
@ -237,7 +238,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var fileMark = new FileMarkDto var fileMark = new FileMarkDto
{ {
@ -268,7 +269,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var fileMark = new FileMarkDto var fileMark = new FileMarkDto
{ {
IdFile = file1001.Id, IdFile = file1001.Id,
@ -306,7 +307,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var fileMark = new FileMarkDto var fileMark = new FileMarkDto
{ {
@ -333,7 +334,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
@ -360,7 +361,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);
@ -390,7 +391,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
wellServiceMock.Object, wellServiceMock.Object,
configurationMock.Object, configurationMock.Object,
backgroundWorkerMock.Object, backgroundWorkerMock.Object,
emailServiceMock.Object); notificationServiceMock.Object);
var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None); var state = await service.GetStateAsync(idWell, publisher1.Id, CancellationToken.None);

View File

@ -10,6 +10,7 @@ using System.Linq;
using AsbCloudApp.Repositories; using AsbCloudApp.Repositories;
using System.Collections.Generic; using System.Collections.Generic;
using AsbCloudApp.Data.User; using AsbCloudApp.Data.User;
using AsbCloudApp.Services.Notifications;
namespace AsbCloudWebApi.Tests.ServicesTests namespace AsbCloudWebApi.Tests.ServicesTests
{ {
@ -21,8 +22,10 @@ namespace AsbCloudWebApi.Tests.ServicesTests
private readonly WellFinalDocumentsService service; private readonly WellFinalDocumentsService service;
private readonly Mock<IUserRepository> userRepositoryMock; private readonly Mock<IUserRepository> userRepositoryMock;
private readonly Mock<IWellService> wellServiceMock; private readonly Mock<IWellService> wellServiceMock;
private readonly Mock<IEmailService> emailServiceMock;
private readonly Mock<IFileCategoryService> fileCategoryService; private readonly Mock<IFileCategoryService> fileCategoryService;
private readonly NotificationService notificationService;
private readonly Mock<ICrudRepository<NotificationCategoryDto>> notificationCategoryRepositoryMock;
private readonly Mock<INotificationTransportService> notificationTransportServiceMock;
private static readonly UserExtendedDto[] users = new[]{ private static readonly UserExtendedDto[] users = new[]{
new UserExtendedDto { new UserExtendedDto {
@ -126,7 +129,25 @@ namespace AsbCloudWebApi.Tests.ServicesTests
Deposit = "deposit 1" }); Deposit = "deposit 1" });
var configuration = new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build(); var configuration = new Microsoft.Extensions.Configuration.ConfigurationBuilder().Build();
emailServiceMock = new Mock<IEmailService>(); notificationCategoryRepositoryMock = new Mock<ICrudRepository<NotificationCategoryDto>>();
notificationCategoryRepositoryMock.Setup(r => r.GetOrDefaultAsync(It.IsAny<int>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(new NotificationCategoryDto
{
Id = 20000,
Name = "Системные уведомления"
});
notificationTransportServiceMock = new Mock<INotificationTransportService>();
notificationTransportServiceMock.SetupGet(x => x.IdTransportType)
.Returns(1);
notificationService = new NotificationService(notificationCategoryRepositoryMock.Object,
new Mock<INotificationRepository>().Object,
new [] { notificationTransportServiceMock.Object });
fileCategoryService = new Mock<IFileCategoryService>(); fileCategoryService = new Mock<IFileCategoryService>();
fileCategoryService.Setup(s => s.GetOrDefaultAsync(idWellFinalDocCategory, It.IsAny<CancellationToken>())) fileCategoryService.Setup(s => s.GetOrDefaultAsync(idWellFinalDocCategory, It.IsAny<CancellationToken>()))
.ReturnsAsync((int id, CancellationToken _) => new FileCategoryDto .ReturnsAsync((int id, CancellationToken _) => new FileCategoryDto
@ -140,7 +161,7 @@ namespace AsbCloudWebApi.Tests.ServicesTests
userRepository: userRepositoryMock.Object, userRepository: userRepositoryMock.Object,
wellService: wellServiceMock.Object, wellService: wellServiceMock.Object,
configuration: configuration, configuration: configuration,
emailService: emailServiceMock.Object, notificationService: notificationService,
fileCategoryService: fileCategoryService.Object, fileCategoryService: fileCategoryService.Object,
wellFinalDocumentsRepository: wellFinalDocumentsRepository.Object); wellFinalDocumentsRepository: wellFinalDocumentsRepository.Object);
} }
@ -187,13 +208,5 @@ namespace AsbCloudWebApi.Tests.ServicesTests
var data = await service.ReNotifyPublishersAsync(1, users[0].Id, idWellFinalDocCategory, CancellationToken.None); var data = await service.ReNotifyPublishersAsync(1, users[0].Id, idWellFinalDocCategory, CancellationToken.None);
Assert.Equal(1, data); Assert.Equal(1, data);
} }
[Fact]
public async Task ReNotifyPublishersAsync_returns_2()
{
var emailsCount = await service.ReNotifyPublishersAsync(1, users[0].Id, idWellFinalDocCategory, CancellationToken.None);
Assert.Equal(1, emailsCount);
emailServiceMock.Verify(s => s.EnqueueSend(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()));
}
} }
} }

View File

@ -19,12 +19,15 @@ namespace AsbCloudWebApi.Controllers;
[Route("api/notification")] [Route("api/notification")]
public class NotificationController : ControllerBase public class NotificationController : ControllerBase
{ {
private readonly IUserRepository userRepository;
private readonly NotificationService notificationService; private readonly NotificationService notificationService;
private readonly INotificationRepository notificationRepository; private readonly INotificationRepository notificationRepository;
public NotificationController(NotificationService notificationService, public NotificationController(IUserRepository userRepository,
NotificationService notificationService,
INotificationRepository notificationRepository) INotificationRepository notificationRepository)
{ {
this.userRepository = userRepository;
this.notificationService = notificationService; this.notificationService = notificationService;
this.notificationRepository = notificationRepository; this.notificationRepository = notificationRepository;
} }
@ -33,35 +36,40 @@ public class NotificationController : ControllerBase
/// Отправка уведомления /// Отправка уведомления
/// </summary> /// </summary>
/// <param name="idUser">Id пользователя</param> /// <param name="idUser">Id пользователя</param>
/// <param name="idNotificationCategory">Id категории уведомления. Допустимое значение параметра: 1</param> /// <param name="idNotificationCategory">Id категории уведомления. Допустимые: 1</param>
/// <param name="title">Заголовок уведомления</param> /// <param name="title">Заголовок уведомления</param>
/// <param name="message">Сообщение уведомления</param> /// <param name="message">Сообщение уведомления</param>
/// <param name="idNotificationTransport">Id типа доставки уведомления. Допустимое значение: 0</param> /// <param name="idTransportType">Id типа доставки уведомления. Допустимые: 0, 1</param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[Route("send")] [Route("send")]
public async Task<IActionResult> SendAsync([Required] int idUser, public async Task<IActionResult> SendAsync([Required] int idUser,
[Required] [Required] [Range(minimum: 1, maximum: 1, ErrorMessage = "Id категории уведомления недоступно. Допустимые: 1")]
[Range(minimum: 1, maximum: 1, ErrorMessage = "Id категории уведомления недоступно. Допустимые: 1")]
int idNotificationCategory, int idNotificationCategory,
[Required] string title, [Required] string title,
[Required] string message, [Required] string message,
[Required] [Required]
[Range(minimum: 0, maximum: 0, ErrorMessage = "Id способа отправки уведомления недоступно. Допустимые: 0")] [Range(minimum: 0, maximum: 1, ErrorMessage = "Id способа отправки уведомления недоступно. Допустимые: 0, 1")]
int idNotificationTransport, int idTransportType,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
await notificationService.NotifyAsync(idUser, var user = await userRepository.GetOrDefaultAsync(idUser, cancellationToken)
idNotificationCategory, ?? throw new ArgumentInvalidException("Пользователь не найден", nameof(idUser));
title,
message, await notificationService.NotifyAsync(new NotifyRequest
idNotificationTransport, {
cancellationToken); IdUser = user.Id,
UserEmail = user.Email,
IdNotificationCategory = idNotificationCategory,
Title = title,
Message = message,
IdTransportType = idTransportType
}, cancellationToken);
return Ok(); return Ok();
} }
/// <summary> /// <summary>
/// Обновление уведомления /// Обновление уведомления
/// </summary> /// </summary>
@ -78,10 +86,10 @@ public class NotificationController : ControllerBase
await notificationService.UpdateNotificationAsync(idNotification, await notificationService.UpdateNotificationAsync(idNotification,
isRead, isRead,
cancellationToken); cancellationToken);
return Ok(); return Ok();
} }
/// <summary> /// <summary>
/// Получение уведомления по Id /// Получение уведомления по Id
/// </summary> /// </summary>
@ -99,12 +107,12 @@ public class NotificationController : ControllerBase
if (notification is null) if (notification is null)
{ {
return BadRequest(ArgumentInvalidException.MakeValidationError(nameof(idNotification), return BadRequest(ArgumentInvalidException.MakeValidationError(nameof(idNotification),
"Уведомление не найдено")); "Уведомление не найдено"));
} }
return Ok(notification); return Ok(notification);
} }
/// <summary> /// <summary>
/// Получение списка уведомлений /// Получение списка уведомлений
/// </summary> /// </summary>
@ -121,7 +129,7 @@ public class NotificationController : ControllerBase
if (!idUser.HasValue) if (!idUser.HasValue)
return Forbid(); return Forbid();
var result = await notificationRepository.GetNotificationsAsync(idUser.Value, var result = await notificationRepository.GetNotificationsAsync(idUser.Value,
request, request,
cancellationToken); cancellationToken);

View File

@ -13,6 +13,7 @@ using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications; using AsbCloudApp.Services.Notifications;
using AsbCloudInfrastructure.Services.Email;
using AsbCloudWebApi.SignalR.Services; using AsbCloudWebApi.SignalR.Services;
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
@ -139,6 +140,7 @@ namespace AsbCloudWebApi
public static void AddNotificationTransportServices(this IServiceCollection services) public static void AddNotificationTransportServices(this IServiceCollection services)
{ {
services.AddTransient<INotificationTransportService, SignalRNotificationTransportService>(); services.AddTransient<INotificationTransportService, SignalRNotificationTransportService>();
services.AddTransient<INotificationTransportService, EmailNotificationTransportService>();
} }
} }
} }