From be2cd4d94134d7c749eda8a15e8d71c84921aca4 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Sun, 25 May 2025 12:32:51 +0200 Subject: [PATCH 1/2] Change `MailService` to allow sending same mail to many recipients --- .../Common/Mail/Abstractions/IMailService.cs | 5 ++-- .../Common/Mail/Models/MailAttachment.cs | 11 ++++----- .../Common/Mail/Models/MailRecipient.cs | 6 +++++ .../Common/Mail/Services/MailService.cs | 24 +++++++------------ 4 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 TickAPI/TickAPI/Common/Mail/Models/MailRecipient.cs diff --git a/TickAPI/TickAPI/Common/Mail/Abstractions/IMailService.cs b/TickAPI/TickAPI/Common/Mail/Abstractions/IMailService.cs index b7f3965..f4b3444 100644 --- a/TickAPI/TickAPI/Common/Mail/Abstractions/IMailService.cs +++ b/TickAPI/TickAPI/Common/Mail/Abstractions/IMailService.cs @@ -5,8 +5,7 @@ namespace TickAPI.Common.Mail.Abstractions; public interface IMailService { - public Task SendTicketAsync(string toEmail, string toLogin, string eventName, byte[] pdfData); + public Task SendTicketAsync(MailRecipient recipient, string eventName, byte[] pdfData); - public Task SendMailAsync(string toEmail, string toLogin, string subject, string content, - List? attachments); + public Task SendMailAsync(IEnumerable recipients, string subject, string content, List? attachments); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Mail/Models/MailAttachment.cs b/TickAPI/TickAPI/Common/Mail/Models/MailAttachment.cs index d59be62..c25dc4f 100644 --- a/TickAPI/TickAPI/Common/Mail/Models/MailAttachment.cs +++ b/TickAPI/TickAPI/Common/Mail/Models/MailAttachment.cs @@ -1,8 +1,7 @@ namespace TickAPI.Common.Mail.Models; -public class MailAttachment -{ - public string fileName { get; set; } - public string base64Content { get; set; } - public string fileType { get; set; } -} \ No newline at end of file +public record MailAttachment( + string FileName, + string Base64Content, + string FileType +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Mail/Models/MailRecipient.cs b/TickAPI/TickAPI/Common/Mail/Models/MailRecipient.cs new file mode 100644 index 0000000..dd0aa98 --- /dev/null +++ b/TickAPI/TickAPI/Common/Mail/Models/MailRecipient.cs @@ -0,0 +1,6 @@ +namespace TickAPI.Common.Mail.Models; + +public record MailRecipient( + string Email, + string Login +); diff --git a/TickAPI/TickAPI/Common/Mail/Services/MailService.cs b/TickAPI/TickAPI/Common/Mail/Services/MailService.cs index 24456bb..b35b5c4 100644 --- a/TickAPI/TickAPI/Common/Mail/Services/MailService.cs +++ b/TickAPI/TickAPI/Common/Mail/Services/MailService.cs @@ -20,35 +20,29 @@ public MailService(IConfiguration configuration) _fromEmailAddress = new EmailAddress(fromEmail, fromName); } - public async Task SendTicketAsync(string toEmail, string toLogin, string eventName, byte[] pdfData) + public async Task SendTicketAsync(MailRecipient recipient, string eventName, byte[] pdfData) { var subject = $"Ticket for {eventName}"; var htmlContent = "Download your ticket from attachments"; var base64Content = Convert.ToBase64String(pdfData); - List attachments = - [ - new MailAttachment - { - base64Content = base64Content, - fileName = "ticket.pdf", - fileType = "application/pdf" - } + List attachments = [ + new MailAttachment("ticket.pdf", base64Content, "application/pdf") ]; - var res = await SendMailAsync(toEmail, toLogin, subject, htmlContent, attachments); + var res = await SendMailAsync([recipient], subject, htmlContent, attachments); return res; } - public async Task SendMailAsync(string toEmail, string toLogin, string subject, string content, + public async Task SendMailAsync(IEnumerable recipients, string subject, string content, List? attachments = null) { - var toEmailAddress = new EmailAddress(toEmail, toLogin); - var msg = MailHelper.CreateSingleEmail(_fromEmailAddress, toEmailAddress, subject, - null, content); + var toEmailAddresses = recipients.Select(r => new EmailAddress(r.Email, r.Login)).ToList(); + var msg = MailHelper.CreateSingleEmailToMultipleRecipients(_fromEmailAddress, toEmailAddresses, subject, null, content); + if (attachments != null) { foreach (var a in attachments) { - msg.AddAttachment(a.fileName, a.base64Content, a.fileType); + msg.AddAttachment(a.FileName, a.Base64Content, a.FileType); } } From bbdab142e40188e0412655c4ee01909e83f23854 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Sun, 25 May 2025 13:11:32 +0200 Subject: [PATCH 2/2] Implement sending messages by organizer --- .../Events/Services/EventServiceTests.cs | 177 ++++++++---------- .../Abstractions/ICustomerRepository.cs | 1 + .../Repositories/CustomerRepository.cs | 6 + .../Events/Abstractions/IEventService.cs | 2 + .../Events/Controllers/EventsController.cs | 22 +++ .../Request/SendMessageToParticipantsDto.cs | 6 + .../TickAPI/Events/Services/EventService.cs | 27 ++- 7 files changed, 143 insertions(+), 98 deletions(-) create mode 100644 TickAPI/TickAPI/Events/DTOs/Request/SendMessageToParticipantsDto.cs diff --git a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs index 464170b..9d335c6 100644 --- a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs @@ -9,12 +9,14 @@ using TickAPI.Categories.Abstractions; using TickAPI.Categories.DTOs.Request; using TickAPI.Categories.Models; +using TickAPI.Common.Mail.Abstractions; using TickAPI.Common.Results; using TickAPI.Events.Models; using TickAPI.Organizers.Abstractions; using TickAPI.Organizers.Models; using TickAPI.Common.Results.Generic; using TickAPI.Common.Time.Abstractions; +using TickAPI.Customers.Abstractions; using TickAPI.Events.DTOs.Response; using TickAPI.Events.Services; using TickAPI.Tickets.Abstractions; @@ -111,8 +113,11 @@ public async Task CreateNewEventAsync_WhenEventDataIsValid_ShouldReturnNewEvent( var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); @@ -184,7 +189,10 @@ public async Task CreateNewEventAsync_WhenEndDateIsBeforeStartDate_ShouldReturnB var ticketServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act @@ -239,7 +247,10 @@ public async Task CreateNewEventAsync_WhenTicketTypeAvailabilityIsAfterEventsEnd var ticketServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); @@ -292,7 +303,10 @@ public async Task CreateNewEventAsync_WhenStartDateIsBeforeNow_ShouldReturnBadRe var ticketServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); @@ -359,9 +373,11 @@ public async Task GetOrganizerEvents_WhenPaginationSucceeds_ShouldReturnPaginate false, new PaginationDetails(1, 3) )); - - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.GetOrganizerEventsAsync(organizer, page, pageSize); @@ -411,8 +427,10 @@ public async Task GetOrganizerEvents_WhenPaginationFails_ShouldPropagateError() .Setup(p => p.PaginateAsync(organizerEvents, pageSize, page)) .ReturnsAsync(Result>.Failure(StatusCodes.Status400BadRequest, "Invalid page number")); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.GetOrganizerEventsAsync(organizer, page, pageSize); @@ -475,9 +493,11 @@ public async Task GetEventsAsync_WhenPaginationSucceeds_ShouldReturnPaginatedEve new PaginationDetails(1, 3) )); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); - + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + // Act var result = await sut.GetEventsAsync(page, pageSize); @@ -521,8 +541,10 @@ public async Task GetEventsAsync_WhenPaginationFails_ShouldPropagateError() .Setup(p => p.PaginateAsync(eventsQueryable, pageSize, page)) .ReturnsAsync(Result>.Failure(StatusCodes.Status400BadRequest, "Invalid page number")); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.GetEventsAsync(page, pageSize); @@ -561,9 +583,11 @@ public async Task GetEventsPaginationDetailsAsync_WhenSuccessful_ShouldReturnPag .Setup(p => p.GetPaginationDetailsAsync(eventsQueryable, pageSize)) .ReturnsAsync(Result.Success(paginationDetails)); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); - + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + // Act var result = await sut.GetEventsPaginationDetailsAsync(pageSize); @@ -599,9 +623,11 @@ public async Task GetEventsPaginationDetailsAsync_WhenFails_ShouldReturnError() .Setup(p => p.GetPaginationDetailsAsync(eventsQueryable, pageSize)) .ReturnsAsync(Result.Failure(StatusCodes.Status400BadRequest, "Invalid page size")); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); - + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + // Act var result = await sut.GetEventsPaginationDetailsAsync(pageSize); @@ -636,8 +662,10 @@ public async Task GetEventDetailsAsync_WhenSuccessful_ShouldReturnEventDetails() Result.Success((uint)(input.Price / 10)) ); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.GetEventDetailsAsync(@event.Id); @@ -673,8 +701,10 @@ public async Task GetEventDetailsAsync_WhenFails_ShouldReturnEventError() .Setup(m => m.GetEventByIdAsync(@event.Id)) .ReturnsAsync(Result.Failure(StatusCodes.Status404NotFound, $"event with id {@event.Id} not found")); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.GetEventDetailsAsync(@event.Id); @@ -778,15 +808,10 @@ public async Task EditEventAsync_WhenDataValid_ShouldUpdateEvent() var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -852,15 +877,10 @@ public async Task EditEventAsync_WhenEventNotFound_ShouldReturnError() var categoryServiceMock = new Mock(); var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -929,15 +949,10 @@ public async Task EditEventAsync_WhenEndDateBeforeStartDate_ShouldReturnBadReque var categoryServiceMock = new Mock(); var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -1007,15 +1022,10 @@ public async Task EditEventAsync_WhenStartDateChangedAndInPast_ShouldReturnBadRe var categoryServiceMock = new Mock(); var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -1129,15 +1139,10 @@ public async Task EditEventAsync_StartDateNotChangedAndInPast_ShouldUpdateEvent( var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -1220,15 +1225,10 @@ public async Task EditEventAsync_WhenTicketTypeAvailableFromAfterEndDate_ShouldR var categoryServiceMock = new Mock(); var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -1301,15 +1301,10 @@ public async Task EditEventAsync_WhenAddressServiceFails_ShouldPropagateError() var categoryServiceMock = new Mock(); var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -1395,15 +1390,10 @@ public async Task EditEventAsync_WhenCategoryServiceFails_ShouldPropagateError() var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( @@ -1497,15 +1487,10 @@ public async Task EditEventAsync_WhenSaveEventFails_ShouldPropagateError() var paginationServiceMock = new Mock(); var ticketServiceMock = new Mock(); + var customerRepositoryMock = new Mock(); + var mailServiceMock = new Mock(); - var sut = new EventService( - eventRepositoryMock.Object, - organizerServiceMock.Object, - addressServiceMock.Object, - dateTimeServiceMock.Object, - paginationServiceMock.Object, - categoryServiceMock.Object, - ticketServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); // Act var result = await sut.EditEventAsync( diff --git a/TickAPI/TickAPI/Customers/Abstractions/ICustomerRepository.cs b/TickAPI/TickAPI/Customers/Abstractions/ICustomerRepository.cs index 4cd7bab..b41656a 100644 --- a/TickAPI/TickAPI/Customers/Abstractions/ICustomerRepository.cs +++ b/TickAPI/TickAPI/Customers/Abstractions/ICustomerRepository.cs @@ -7,4 +7,5 @@ public interface ICustomerRepository { Task> GetCustomerByEmailAsync(string customerEmail); Task AddNewCustomerAsync(Customer customer); + IQueryable GetCustomersWithTicketForEvent(Guid eventId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Customers/Repositories/CustomerRepository.cs b/TickAPI/TickAPI/Customers/Repositories/CustomerRepository.cs index ed131d5..f1d1c6e 100644 --- a/TickAPI/TickAPI/Customers/Repositories/CustomerRepository.cs +++ b/TickAPI/TickAPI/Customers/Repositories/CustomerRepository.cs @@ -32,4 +32,10 @@ public async Task AddNewCustomerAsync(Customer customer) _tickApiDbContext.Customers.Add(customer); await _tickApiDbContext.SaveChangesAsync(); } + + public IQueryable GetCustomersWithTicketForEvent(Guid eventId) + { + return _tickApiDbContext.Customers + .Where(c => c.Tickets.Any(t => t.Type.Event.Id == eventId)); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs index b029b39..010a175 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs @@ -1,6 +1,7 @@ using TickAPI.Addresses.DTOs.Request; using TickAPI.Common.Pagination.Responses; using TickAPI.Categories.DTOs.Request; +using TickAPI.Common.Results; using TickAPI.Events.Models; using TickAPI.Common.Results.Generic; using TickAPI.Events.DTOs.Request; @@ -21,4 +22,5 @@ public Task> CreateNewEventAsync(string name, string description, public Task> GetEventsPaginationDetailsAsync(int pageSize); public Task> GetEventDetailsAsync(Guid eventId); public Task> EditEventAsync(Organizer organizer, Guid eventId, string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto editAddress, List categories, EventStatus eventStatus); + public Task SendMessageToParticipants(Organizer organizer, Guid eventId, string subject, string message); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Controllers/EventsController.cs b/TickAPI/TickAPI/Events/Controllers/EventsController.cs index 95cbf68..94aa206 100644 --- a/TickAPI/TickAPI/Events/Controllers/EventsController.cs +++ b/TickAPI/TickAPI/Events/Controllers/EventsController.cs @@ -141,4 +141,26 @@ public async Task> EditEvent([FromRoute] Guid return Ok("Event edited succesfully"); } + + [AuthorizeWithPolicy(AuthPolicies.VerifiedOrganizerPolicy)] + [HttpPost("{id:guid}/message-to-participants")] + public async Task SendMessageToEventParticipants([FromRoute] Guid id, [FromBody] SendMessageToParticipantsDto request) + { + var emailResult = _claimsService.GetEmailFromClaims(User.Claims); + if (emailResult.IsError) + { + return emailResult.ToObjectResult(); + } + var email = emailResult.Value!; + + var organizerResult = await _organizerService.GetOrganizerByEmailAsync(email); + if (organizerResult.IsError) + { + return organizerResult.ToObjectResult(); + } + var organizer = organizerResult.Value!; + + var result = await _eventService.SendMessageToParticipants(organizer, id, request.Subject, request.Message); + return result.ToObjectResult(); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/DTOs/Request/SendMessageToParticipantsDto.cs b/TickAPI/TickAPI/Events/DTOs/Request/SendMessageToParticipantsDto.cs new file mode 100644 index 0000000..b3e617e --- /dev/null +++ b/TickAPI/TickAPI/Events/DTOs/Request/SendMessageToParticipantsDto.cs @@ -0,0 +1,6 @@ +namespace TickAPI.Events.DTOs.Request; + +public record SendMessageToParticipantsDto( + string Subject, + string Message +); diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index 91cdda2..a29c419 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -1,14 +1,18 @@ -using TickAPI.Addresses.Abstractions; +using Microsoft.EntityFrameworkCore; +using TickAPI.Addresses.Abstractions; using TickAPI.Addresses.DTOs.Request; using TickAPI.Common.Pagination.Abstractions; using TickAPI.Common.Pagination.Responses; using TickAPI.Categories.Abstractions; using TickAPI.Categories.DTOs.Request; +using TickAPI.Common.Mail.Abstractions; +using TickAPI.Common.Mail.Models; using TickAPI.Common.Results; using TickAPI.Common.Time.Abstractions; using TickAPI.Events.Abstractions; using TickAPI.Events.Models; using TickAPI.Common.Results.Generic; +using TickAPI.Customers.Abstractions; using TickAPI.Events.DTOs.Request; using TickAPI.Events.DTOs.Response; using TickAPI.Events.Filters; @@ -29,8 +33,10 @@ public class EventService : IEventService private readonly IPaginationService _paginationService; private readonly ICategoryService _categoryService; private readonly ITicketService _ticketService; + private readonly ICustomerRepository _customerRepository; + private readonly IMailService _mailService; - public EventService(IEventRepository eventRepository, IOrganizerService organizerService, IAddressService addressService, IDateTimeService dateTimeService, IPaginationService paginationService, ICategoryService categoryService, ITicketService ticketService) + public EventService(IEventRepository eventRepository, IOrganizerService organizerService, IAddressService addressService, IDateTimeService dateTimeService, IPaginationService paginationService, ICategoryService categoryService, ITicketService ticketService, ICustomerRepository customerRepository, IMailService mailService) { _eventRepository = eventRepository; _organizerService = organizerService; @@ -39,6 +45,8 @@ public EventService(IEventRepository eventRepository, IOrganizerService organize _paginationService = paginationService; _categoryService = categoryService; _ticketService = ticketService; + _customerRepository = customerRepository; + _mailService = mailService; } public async Task> CreateNewEventAsync(string name, string description, DateTime startDate, DateTime endDate, @@ -221,6 +229,21 @@ public async Task> EditEventAsync(Organizer organizer, Guid eventI return Result.Success(existingEvent); } + public async Task SendMessageToParticipants(Organizer organizer, Guid eventId, string subject, string message) + { + var eventResult = await _eventRepository.GetEventByIdAndOrganizerAsync(eventId, organizer); + if (eventResult.IsError) + { + return Result.PropagateError(eventResult); + } + var ev = eventResult.Value!; + + var eventParticipants = await _customerRepository.GetCustomersWithTicketForEvent(ev.Id).ToListAsync(); + var recipients = eventParticipants.Select(p => new MailRecipient(p.Email, $"{p.FirstName} {p.LastName}")); + + return await _mailService.SendMailAsync(recipients, subject, message, null); + } + private async Task>> GetPaginatedEventsAsync(IQueryable events, int page, int pageSize) { var paginatedEventsResult = await _paginationService.PaginateAsync(events, pageSize, page);