From be2cd4d94134d7c749eda8a15e8d71c84921aca4 Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Sun, 25 May 2025 12:32:51 +0200 Subject: [PATCH 01/24] 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 02/24] 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); From e0854a47fdc985829d5f8e2fd2570629aaf20e2d Mon Sep 17 00:00:00 2001 From: kTrzcinskii Date: Sun, 8 Jun 2025 11:47:11 +0200 Subject: [PATCH 03/24] Fix filtering by name and description --- .../Events/Filters/EventFilterApplierTests.cs | 9 +++------ .../TickAPI.Tests/Events/Filters/EventFilterTests.cs | 4 ++-- TickAPI/TickAPI/Events/Abstractions/IEventFilter.cs | 3 +-- TickAPI/TickAPI/Events/Filters/EventFilter.cs | 11 +++-------- TickAPI/TickAPI/Events/Filters/EventFilterApplier.cs | 6 +----- 5 files changed, 10 insertions(+), 23 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Events/Filters/EventFilterApplierTests.cs b/TickAPI/TickAPI.Tests/Events/Filters/EventFilterApplierTests.cs index 6256303..47ffd77 100644 --- a/TickAPI/TickAPI.Tests/Events/Filters/EventFilterApplierTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Filters/EventFilterApplierTests.cs @@ -48,8 +48,7 @@ public void ApplyFilters_WithName_ShouldCallFilterByName() _eventFilterApplier.ApplyFilters(filters); // Assert - _mockEventFilter.Verify(ef => ef.FilterByName(filters.SearchQuery!), Times.Once); - _mockEventFilter.Verify(ef => ef.FilterByDescription(filters.SearchQuery!), Times.Once); + _mockEventFilter.Verify(ef => ef.FilterByNameOrDescription(filters.SearchQuery!), Times.Once); _mockEventFilter.Verify(ef => ef.GetEvents(), Times.Once); } @@ -660,8 +659,7 @@ public void ApplyFilters_WithMultipleFilters_ShouldCallAllRelevantFilters() _eventFilterApplier.ApplyFilters(filters); // Assert - _mockEventFilter.Verify(ef => ef.FilterByName(filters.SearchQuery!), Times.Once); - _mockEventFilter.Verify(ef => ef.FilterByDescription(filters.SearchQuery!), Times.Once); + _mockEventFilter.Verify(ef => ef.FilterByNameOrDescription(filters.SearchQuery!), Times.Once); _mockEventFilter.Verify(ef => ef.FilterByStartDate(filters.StartDate!.Value), Times.Once); _mockEventFilter.Verify(ef => ef.FilterByMinPrice(filters.MinPrice!.Value), Times.Once); _mockEventFilter.Verify(ef => ef.FilterByMaxPrice(filters.MaxPrice!.Value), Times.Once); @@ -703,8 +701,7 @@ public void ApplyFilters_WithNoFilters_ShouldOnlyCallGetEvents() var result = _eventFilterApplier.ApplyFilters(filters); // Assert - _mockEventFilter.Verify(ef => ef.FilterByName(It.IsAny()), Times.Never); - _mockEventFilter.Verify(ef => ef.FilterByDescription(It.IsAny()), Times.Never); + _mockEventFilter.Verify(ef => ef.FilterByNameOrDescription(It.IsAny()), Times.Never); _mockEventFilter.Verify(ef => ef.FilterByStartDate(It.IsAny()), Times.Never); _mockEventFilter.Verify(ef => ef.FilterByMinStartDate(It.IsAny()), Times.Never); _mockEventFilter.Verify(ef => ef.FilterByMaxStartDate(It.IsAny()), Times.Never); diff --git a/TickAPI/TickAPI.Tests/Events/Filters/EventFilterTests.cs b/TickAPI/TickAPI.Tests/Events/Filters/EventFilterTests.cs index 6458b6e..90b4052 100644 --- a/TickAPI/TickAPI.Tests/Events/Filters/EventFilterTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Filters/EventFilterTests.cs @@ -102,7 +102,7 @@ public void FilterByName_ShouldReturnMatchingEvents() // Act var eventFilter = new EventFilter(events.AsQueryable()); - eventFilter.FilterByName("concert"); + eventFilter.FilterByNameOrDescription("concert"); var result = eventFilter.GetEvents().ToList(); // Assert @@ -119,7 +119,7 @@ public void FilterByDescription_ShouldReturnMatchingEvents() // Act var eventFilter = new EventFilter(events.AsQueryable()); - eventFilter.FilterByDescription("tech"); + eventFilter.FilterByNameOrDescription("tech"); var result = eventFilter.GetEvents().ToList(); // Assert diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventFilter.cs b/TickAPI/TickAPI/Events/Abstractions/IEventFilter.cs index a4aa175..4681384 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventFilter.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventFilter.cs @@ -5,8 +5,7 @@ namespace TickAPI.Events.Abstractions; public interface IEventFilter { IQueryable GetEvents(); - void FilterByName(string name); - void FilterByDescription(string description); + void FilterByNameOrDescription(string name); void FilterByStartDate(DateTime startDate); void FilterByMinStartDate(DateTime startDate); void FilterByMaxStartDate(DateTime startDate); diff --git a/TickAPI/TickAPI/Events/Filters/EventFilter.cs b/TickAPI/TickAPI/Events/Filters/EventFilter.cs index 0e0f017..f7020a5 100644 --- a/TickAPI/TickAPI/Events/Filters/EventFilter.cs +++ b/TickAPI/TickAPI/Events/Filters/EventFilter.cs @@ -17,16 +17,11 @@ public IQueryable GetEvents() return _events; } - public void FilterByName(string name) + public void FilterByNameOrDescription(string searchQuery) { - _events = _events.Where(e => e.Name.ToLower().Contains(name.ToLower())); + _events = _events.Where(e => e.Name.ToLower().Contains(searchQuery.ToLower()) || e.Description.ToLower().Contains(searchQuery.ToLower())); } - - public void FilterByDescription(string description) - { - _events = _events.Where(e => e.Description.ToLower().Contains(description.ToLower())); - } - + public void FilterByStartDate(DateTime startDate) { _events = _events.Where(e => e.StartDate.Date == startDate.Date); diff --git a/TickAPI/TickAPI/Events/Filters/EventFilterApplier.cs b/TickAPI/TickAPI/Events/Filters/EventFilterApplier.cs index 7a3aa80..1720833 100644 --- a/TickAPI/TickAPI/Events/Filters/EventFilterApplier.cs +++ b/TickAPI/TickAPI/Events/Filters/EventFilterApplier.cs @@ -14,11 +14,7 @@ public EventFilterApplier(IEventFilter eventFilter) _eventFilter = eventFilter; _filterActions = new Dictionary, Action> { - { f => !string.IsNullOrEmpty(f.SearchQuery), f => - { - _eventFilter.FilterByName(f.SearchQuery!); - _eventFilter.FilterByDescription(f.SearchQuery!); - } + { f => !string.IsNullOrEmpty(f.SearchQuery), f => _eventFilter.FilterByNameOrDescription(f.SearchQuery!) }, { f => f.StartDate.HasValue, f => _eventFilter.FilterByStartDate(f.StartDate!.Value) }, { f => f.MinStartDate.HasValue, f => _eventFilter.FilterByMinStartDate(f.MinStartDate!.Value) }, From 86acaa42b1c5acfc6460254d1bcd70ac3d9ba4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:50:22 +0200 Subject: [PATCH 04/24] Add organizer details --- .../Events/Abstractions/IEventRepository.cs | 2 ++ .../Events/Abstractions/IEventService.cs | 1 + .../Events/Controllers/EventsController.cs | 8 +++++++ .../GetEventDetailsOrganizerResponseDto.cs | 7 ++++++ .../Events/Repositories/EventRepository.cs | 22 +++++++++++++++++++ .../TickAPI/Events/Services/EventService.cs | 14 ++++++++++++ 6 files changed, 54 insertions(+) create mode 100644 TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs index a7e4cb1..f7d1127 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs @@ -13,4 +13,6 @@ public interface IEventRepository public Task> GetEventByIdAsync(Guid eventId); public Task SaveEventAsync(Event ev); public Task> GetEventByIdAndOrganizerAsync(Guid eventId, Organizer organizer); + public Task> GetEventRevenue(Guid eventId); + public Task> GetEventSoldTicketsCount(Guid eventId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs index 010a175..9475a4f 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs @@ -23,4 +23,5 @@ public Task> CreateNewEventAsync(string name, string description, 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); + public Task> GetEventDetailsOrganizerAsync(Guid eventId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Controllers/EventsController.cs b/TickAPI/TickAPI/Events/Controllers/EventsController.cs index 94aa206..1662943 100644 --- a/TickAPI/TickAPI/Events/Controllers/EventsController.cs +++ b/TickAPI/TickAPI/Events/Controllers/EventsController.cs @@ -115,6 +115,14 @@ public async Task> GetEventDetails([Fro return eventDetailsResult.ToObjectResult(); } + [AuthorizeWithPolicy(AuthPolicies.VerifiedOrganizerPolicy)] + [HttpGet("organizer/{id:guid}")] + public async Task> GetEventDetailsOrganizer([FromRoute] Guid id) + { + var eventDetailsResult = await _eventService.GetEventDetailsOrganizerAsync(id); + return eventDetailsResult.ToObjectResult(); + } + [AuthorizeWithPolicy(AuthPolicies.VerifiedOrganizerPolicy)] [HttpPatch("{id:guid}")] public async Task> EditEvent([FromRoute] Guid id, [FromBody] EditEventDto request) diff --git a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs new file mode 100644 index 0000000..6169603 --- /dev/null +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs @@ -0,0 +1,7 @@ +namespace TickAPI.Events.DTOs.Response; + +public record GetEventDetailsOrganizerResponseDto( + GetEventDetailsResponseDto EventDetails, + decimal Revenue, + int SoldTicketsCount + ); \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs index bfd667e..e06d03e 100644 --- a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs +++ b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs @@ -75,4 +75,26 @@ public async Task> GetEventByIdAndOrganizerAsync(Guid eventId, Org } return Result.Success(ev); } + + public async Task> GetEventRevenue(Guid eventId) + { + var query = from tickets in _tickApiDbContext.Tickets + join _ticketTypes in _tickApiDbContext.TicketTypes on tickets.Type.Id equals _ticketTypes.Id + join events in _tickApiDbContext.Events on _ticketTypes.Event.Id equals events.Id + where events.Id == eventId + select new { price = _ticketTypes.Price }; + var val = await query.SumAsync(x => x.price); + return Result.Success(val); + } + + public async Task> GetEventSoldTicketsCount(Guid eventId) + { + var query = from tickets in _tickApiDbContext.Tickets + join _ticketTypes in _tickApiDbContext.TicketTypes on tickets.Type.Id equals _ticketTypes.Id + join events in _tickApiDbContext.Events on _ticketTypes.Event.Id equals events.Id + where events.Id == eventId + select new { price = _ticketTypes.Price }; + var val = await query.CountAsync(); + return Result.Success(val); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index a29c419..7b8fa3b 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -184,6 +184,20 @@ public async Task> GetEventDetailsAsync(Guid return Result.Success(details); } + public async Task> GetEventDetailsOrganizerAsync(Guid eventId) + { + var details = await GetEventDetailsAsync(eventId); + if (details.IsError) + { + return Result.PropagateError(details); + } + + var val = await _eventRepository.GetEventRevenue(eventId); + var count = await _eventRepository.GetEventSoldTicketsCount(eventId); + var ret = new GetEventDetailsOrganizerResponseDto(details.Value!, val.Value!, count.Value!); + return Result.Success(ret); + } + public async Task> EditEventAsync(Organizer organizer, Guid eventId, string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto editAddress, List categories, EventStatus eventStatus) { From 91002dbeb082cb8315bfa8e82abb0a63c00e8ebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sun, 8 Jun 2025 20:33:56 +0200 Subject: [PATCH 05/24] Change sold ticket count --- TickAPI/TickAPI/Events/Repositories/EventRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs index e06d03e..3217607 100644 --- a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs +++ b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs @@ -93,7 +93,7 @@ public async Task> GetEventSoldTicketsCount(Guid eventId) join _ticketTypes in _tickApiDbContext.TicketTypes on tickets.Type.Id equals _ticketTypes.Id join events in _tickApiDbContext.Events on _ticketTypes.Event.Id equals events.Id where events.Id == eventId - select new { price = _ticketTypes.Price }; + select new { id = tickets.Id }; var val = await query.CountAsync(); return Result.Success(val); } From 5fbf094c349da8a0b23d4ab5c9cc3f5b0ada84af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sun, 8 Jun 2025 20:36:40 +0200 Subject: [PATCH 06/24] Get rid of result in calls without errors --- TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs | 4 ++-- TickAPI/TickAPI/Events/Repositories/EventRepository.cs | 8 ++++---- TickAPI/TickAPI/Events/Services/EventService.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs index f7d1127..4a2103e 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs @@ -13,6 +13,6 @@ public interface IEventRepository public Task> GetEventByIdAsync(Guid eventId); public Task SaveEventAsync(Event ev); public Task> GetEventByIdAndOrganizerAsync(Guid eventId, Organizer organizer); - public Task> GetEventRevenue(Guid eventId); - public Task> GetEventSoldTicketsCount(Guid eventId); + public Task GetEventRevenue(Guid eventId); + public Task GetEventSoldTicketsCount(Guid eventId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs index 3217607..60c95aa 100644 --- a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs +++ b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs @@ -76,7 +76,7 @@ public async Task> GetEventByIdAndOrganizerAsync(Guid eventId, Org return Result.Success(ev); } - public async Task> GetEventRevenue(Guid eventId) + public async Task GetEventRevenue(Guid eventId) { var query = from tickets in _tickApiDbContext.Tickets join _ticketTypes in _tickApiDbContext.TicketTypes on tickets.Type.Id equals _ticketTypes.Id @@ -84,10 +84,10 @@ join events in _tickApiDbContext.Events on _ticketTypes.Event.Id equals events.I where events.Id == eventId select new { price = _ticketTypes.Price }; var val = await query.SumAsync(x => x.price); - return Result.Success(val); + return val; } - public async Task> GetEventSoldTicketsCount(Guid eventId) + public async Task GetEventSoldTicketsCount(Guid eventId) { var query = from tickets in _tickApiDbContext.Tickets join _ticketTypes in _tickApiDbContext.TicketTypes on tickets.Type.Id equals _ticketTypes.Id @@ -95,6 +95,6 @@ join events in _tickApiDbContext.Events on _ticketTypes.Event.Id equals events.I where events.Id == eventId select new { id = tickets.Id }; var val = await query.CountAsync(); - return Result.Success(val); + return val; } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index 7b8fa3b..e6cd0cf 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -194,7 +194,7 @@ public async Task> GetEventDetailsOr var val = await _eventRepository.GetEventRevenue(eventId); var count = await _eventRepository.GetEventSoldTicketsCount(eventId); - var ret = new GetEventDetailsOrganizerResponseDto(details.Value!, val.Value!, count.Value!); + var ret = new GetEventDetailsOrganizerResponseDto(details.Value!, val, count); return Result.Success(ret); } From 9cd52129312d167fb7e340010d4aaa6e39b1b054 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Wed, 11 Jun 2025 17:18:10 +0200 Subject: [PATCH 07/24] added default name on ticket setting --- TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs | 1 - TickAPI/TickAPI/Tickets/Services/TicketService.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs index b4032e5..c6385ab 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs @@ -225,7 +225,6 @@ private async Task GenerateBoughtTicketsAsync(string customerEmail, stri for (var i = 0; i < ticket.Quantity; i++) { - // TODO: add seats/name on ticket setting var createTicketResult = await _ticketService.CreateTicketAsync(type, owner); if (createTicketResult.IsError) diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index c643424..d9e05c2 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -168,7 +168,7 @@ public async Task CreateTicketAsync(TicketType type, Customer owner, str { Type = type, Owner = owner, - NameOnTicket = nameOnTicket, + NameOnTicket = nameOnTicket ?? owner.FirstName + " " + owner.LastName, Seats = seats, ForResell = false, Used = false, From 9455a968593a1afd7b9992a68dcd6be08e055efc Mon Sep 17 00:00:00 2001 From: staszkiet Date: Thu, 12 Jun 2025 22:16:56 +0200 Subject: [PATCH 08/24] Change organizer event details dto --- .../GetEventDetailsOrganizerResponseDto.cs | 21 ++++++++++++++++--- .../TickAPI/Events/Services/EventService.cs | 15 ++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs index 6169603..94e0feb 100644 --- a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsOrganizerResponseDto.cs @@ -1,7 +1,22 @@ -namespace TickAPI.Events.DTOs.Response; +using TickAPI.Events.Models; +using TickAPI.Events.DTOs.Response; + +namespace TickAPI.Events.DTOs.Response; + public record GetEventDetailsOrganizerResponseDto( - GetEventDetailsResponseDto EventDetails, + Guid Id, + string Name, + string Description, + DateTime StartDate, + DateTime EndDate, + uint? MinimumAge, + List Categories, + List TicketTypes, + EventStatus Status, + GetEventResponseAddressDto Address, decimal Revenue, int SoldTicketsCount - ); \ No newline at end of file + ); + + \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index e6cd0cf..33f236a 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -194,7 +194,20 @@ public async Task> GetEventDetailsOr var val = await _eventRepository.GetEventRevenue(eventId); var count = await _eventRepository.GetEventSoldTicketsCount(eventId); - var ret = new GetEventDetailsOrganizerResponseDto(details.Value!, val, count); + var ev = details.Value!; + + var ret = new GetEventDetailsOrganizerResponseDto( ev.Id, + ev.Name, + ev.Description, + ev.StartDate, + ev.EndDate, + ev.MinimumAge, + ev.Categories, + ev.TicketTypes, + ev.Status, + ev.Address, + val, + count); return Result.Success(ret); } From e033060eac94e2bda193ce77760d3f75772fc415 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 01:35:02 +0200 Subject: [PATCH 09/24] added the (not implemented) methods for both of the new endpoints --- .../Controllers/ShoppingCartsController.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs b/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs index 373d939..5a83052 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs @@ -39,6 +39,13 @@ await _shoppingCartService.AddNewTicketsToCartAsync(addTicketDto.TicketTypeId, a return addTicketResult.ToObjectResult(); } + + [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] + [HttpPost("{ticketId:guid}")] + public async Task AddResellTicket([FromRoute] Guid ticketId) + { + throw new NotImplementedException(); + } [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] [HttpGet] @@ -74,6 +81,13 @@ await _shoppingCartService.RemoveNewTicketsFromCartAsync(removeTicketDto.TicketT return removeTicketResult.ToObjectResult(); } + [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] + [HttpDelete("{ticketId:guid}")] + public async Task RemoveResellTicket([FromRoute] Guid ticketId) + { + throw new NotImplementedException(); + } + [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] [HttpGet("due")] public async Task>> GetDueAmount() From a6e2f76ab1f6bba62a5d69df7a7959c26216966e Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 01:38:25 +0200 Subject: [PATCH 10/24] added (non implemented) resell ticket methods in ShoppingCartsController.cs --- .../Abstractions/IShoppingCartService.cs | 2 ++ .../Controllers/ShoppingCartsController.cs | 22 +++++++++++++++++-- .../Services/ShoppingCartService.cs | 10 +++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartService.cs index 787e62e..609ff79 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartService.cs @@ -8,8 +8,10 @@ namespace TickAPI.ShoppingCarts.Abstractions; public interface IShoppingCartService { public Task AddNewTicketsToCartAsync(Guid ticketTypeId, uint amount, string customerEmail); + public Task AddResellTicketToCartAsync(Guid ticketId, string customerEmail); public Task> GetTicketsFromCartAsync(string customerEmail); public Task RemoveNewTicketsFromCartAsync(Guid ticketTypeId, uint amount, string customerEmail); + public Task RemoveResellTicketFromCartAsync(Guid ticketId, string customerEmail); public Task>> GetDueAmountAsync(string customerEmail); public Task> CheckoutAsync(string customerEmail, decimal amount, string currency, string cardNumber, string cardExpiry, string cvv); diff --git a/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs b/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs index 5a83052..88542c5 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Controllers/ShoppingCartsController.cs @@ -44,7 +44,16 @@ await _shoppingCartService.AddNewTicketsToCartAsync(addTicketDto.TicketTypeId, a [HttpPost("{ticketId:guid}")] public async Task AddResellTicket([FromRoute] Guid ticketId) { - throw new NotImplementedException(); + var emailResult = _claimsService.GetEmailFromClaims(User.Claims); + if (emailResult.IsError) + { + return emailResult.ToObjectResult(); + } + var email = emailResult.Value!; + + var addTicketResult = await _shoppingCartService.AddResellTicketToCartAsync(ticketId, email); + + return addTicketResult.ToObjectResult(); } [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] @@ -85,7 +94,16 @@ await _shoppingCartService.RemoveNewTicketsFromCartAsync(removeTicketDto.TicketT [HttpDelete("{ticketId:guid}")] public async Task RemoveResellTicket([FromRoute] Guid ticketId) { - throw new NotImplementedException(); + var emailResult = _claimsService.GetEmailFromClaims(User.Claims); + if (emailResult.IsError) + { + return emailResult.ToObjectResult(); + } + var email = emailResult.Value!; + + var removeTicketResult = await _shoppingCartService.RemoveResellTicketFromCartAsync(ticketId, email); + + return removeTicketResult.ToObjectResult(); } [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] diff --git a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs index c6385ab..4ca0b55 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs @@ -54,6 +54,11 @@ public async Task AddNewTicketsToCartAsync(Guid ticketTypeId, uint amoun return Result.Success(); } + public Task AddResellTicketToCartAsync(Guid ticketId, string customerEmail) + { + throw new NotImplementedException(); + } + public async Task> GetTicketsFromCartAsync(string customerEmail) { var getShoppingCartResult = await _shoppingCartRepository.GetShoppingCartByEmailAsync(customerEmail); @@ -102,6 +107,11 @@ public async Task RemoveNewTicketsFromCartAsync(Guid ticketTypeId, uint return Result.Success(); } + public Task RemoveResellTicketFromCartAsync(Guid ticketId, string customerEmail) + { + throw new NotImplementedException(); + } + public async Task>> GetDueAmountAsync(string customerEmail) { var getShoppingCartResult = await _shoppingCartRepository.GetShoppingCartByEmailAsync(customerEmail); From 3d771249a1841fbc9230a14ca7c252d4566a33ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:07:20 +0200 Subject: [PATCH 11/24] Add blob --- .../Common/Blob/Abstractions/IBlobService.cs | 6 + .../Common/Blob/Services/BlobService.cs | 29 ++ .../Events/Abstractions/IEventService.cs | 2 +- .../Events/Controllers/EventsController.cs | 2 +- .../Events/DTOs/Request/CreateEventDto.cs | 3 +- TickAPI/TickAPI/Events/Models/Event.cs | 1 + .../TickAPI/Events/Services/EventService.cs | 15 +- .../20250613140026_blobmigration.Designer.cs | 400 ++++++++++++++++++ .../20250613140026_blobmigration.cs | 28 ++ .../TickApiDbContextModelSnapshot.cs | 3 + TickAPI/TickAPI/Program.cs | 3 + TickAPI/TickAPI/TickAPI.csproj | 1 + 12 files changed, 487 insertions(+), 6 deletions(-) create mode 100644 TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs create mode 100644 TickAPI/TickAPI/Common/Blob/Services/BlobService.cs create mode 100644 TickAPI/TickAPI/Migrations/20250613140026_blobmigration.Designer.cs create mode 100644 TickAPI/TickAPI/Migrations/20250613140026_blobmigration.cs diff --git a/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs b/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs new file mode 100644 index 0000000..9ae6b1a --- /dev/null +++ b/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs @@ -0,0 +1,6 @@ +namespace TickAPI.Common.Blob.Abstractions; + +public interface IBlobService +{ + public Task UploadToBlobContainerAsync(string name, IFormFile image); +} \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs new file mode 100644 index 0000000..f042a20 --- /dev/null +++ b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs @@ -0,0 +1,29 @@ +using Azure.Identity; +using Azure.Storage.Blobs; +using TickAPI.Common.Blob.Abstractions; + +namespace TickAPI.Common.Blob.Services; + +public class BlobService : IBlobService +{ + private string _connectionString; + private string _containerName; + + public BlobService(IConfiguration configuration) + { + _connectionString = configuration["BlobStorage:ConnectionString"]; + _containerName = configuration["BlobStorage:ContainerName"]; + } + + public async Task UploadToBlobContainerAsync(string name, IFormFile image) + { + var container = new BlobContainerClient(_connectionString, _containerName); + Guid id = Guid.NewGuid(); + string blobName = name + id; + var blob = container.GetBlobClient(blobName); + var stream = new MemoryStream(); + await image.CopyToAsync(stream); + var response = blob.Upload(stream); + return blob.Uri.ToString(); + } +} \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs index 9475a4f..af2f26c 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs @@ -15,7 +15,7 @@ public interface IEventService { public Task> CreateNewEventAsync(string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto createAddress, List categories, - List ticketTypes,EventStatus eventStatus, string organizerEmail); + List ticketTypes,EventStatus eventStatus, string organizerEmail, IFormFile? images); public Task>> GetOrganizerEventsAsync(Organizer organizer, int page, int pageSize, EventFiltersDto? eventFilters = null); public Task> GetOrganizerEventsPaginationDetailsAsync(Organizer organizer, int pageSize); public Task>> GetEventsAsync(int page, int pageSize, EventFiltersDto? eventFilters = null); diff --git a/TickAPI/TickAPI/Events/Controllers/EventsController.cs b/TickAPI/TickAPI/Events/Controllers/EventsController.cs index 1662943..04307f7 100644 --- a/TickAPI/TickAPI/Events/Controllers/EventsController.cs +++ b/TickAPI/TickAPI/Events/Controllers/EventsController.cs @@ -39,7 +39,7 @@ public async Task> CreateEvent([FromBody] C var newEventResult = await _eventService.CreateNewEventAsync(request.Name, request.Description, request.StartDate, request.EndDate, request.MinimumAge, request.CreateAddress, request.Categories - , request.TicketTypes ,request.EventStatus, email); + , request.TicketTypes ,request.EventStatus, email, request.Image); if (newEventResult.IsError) return newEventResult.ToObjectResult(); diff --git a/TickAPI/TickAPI/Events/DTOs/Request/CreateEventDto.cs b/TickAPI/TickAPI/Events/DTOs/Request/CreateEventDto.cs index 5b6f447..064ae42 100644 --- a/TickAPI/TickAPI/Events/DTOs/Request/CreateEventDto.cs +++ b/TickAPI/TickAPI/Events/DTOs/Request/CreateEventDto.cs @@ -14,5 +14,6 @@ public record CreateEventDto( List Categories, List TicketTypes, EventStatus EventStatus, - CreateAddressDto CreateAddress + CreateAddressDto CreateAddress, + IFormFile? Image ); \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Models/Event.cs b/TickAPI/TickAPI/Events/Models/Event.cs index 50ca434..8bdb9cd 100644 --- a/TickAPI/TickAPI/Events/Models/Event.cs +++ b/TickAPI/TickAPI/Events/Models/Event.cs @@ -18,6 +18,7 @@ public class Event public ICollection TicketTypes { get; set; } public EventStatus EventStatus { get; set; } public Address Address { get; set; } + public string? ImageUrl { get; set; } } public enum EventStatus diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index e6cd0cf..78a3732 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -21,6 +21,7 @@ using TickAPI.Tickets.Abstractions; using TickAPI.TicketTypes.DTOs.Request; using TickAPI.TicketTypes.Models; +using TickAPI.Common.Blob.Abstractions; namespace TickAPI.Events.Services; @@ -35,8 +36,9 @@ public class EventService : IEventService private readonly ITicketService _ticketService; private readonly ICustomerRepository _customerRepository; private readonly IMailService _mailService; + private readonly IBlobService _blobService; - public EventService(IEventRepository eventRepository, IOrganizerService organizerService, IAddressService addressService, IDateTimeService dateTimeService, IPaginationService paginationService, ICategoryService categoryService, ITicketService ticketService, ICustomerRepository customerRepository, IMailService mailService) + public EventService(IEventRepository eventRepository, IOrganizerService organizerService, IAddressService addressService, IDateTimeService dateTimeService, IPaginationService paginationService, ICategoryService categoryService, ITicketService ticketService, ICustomerRepository customerRepository, IMailService mailService, IBlobService blobService) { _eventRepository = eventRepository; _organizerService = organizerService; @@ -47,11 +49,12 @@ public EventService(IEventRepository eventRepository, IOrganizerService organize _ticketService = ticketService; _customerRepository = customerRepository; _mailService = mailService; + _blobService = blobService; } public async Task> CreateNewEventAsync(string name, string description, DateTime startDate, DateTime endDate, uint? minimumAge, CreateAddressDto createAddress, List categories, List ticketTypes, - EventStatus eventStatus, string organizerEmail) + EventStatus eventStatus, string organizerEmail, IFormFile? image) { var organizerResult = await _organizerService.GetOrganizerByEmailAsync(organizerEmail); if (!organizerResult.IsSuccess) @@ -82,7 +85,12 @@ public async Task> CreateNewEventAsync(string name, string descri { return Result.PropagateError(categoriesByNameResult); } - + + string? imageUrl = null; + if (image != null) + { + imageUrl = await _blobService.UploadToBlobContainerAsync(name, image); + } var @event = new Event { Name = name, @@ -95,6 +103,7 @@ public async Task> CreateNewEventAsync(string name, string descri Organizer = organizerResult.Value!, EventStatus = eventStatus, TicketTypes = ticketTypesConverted, + ImageUrl = imageUrl }; await _eventRepository.AddNewEventAsync(@event); return Result.Success(@event); diff --git a/TickAPI/TickAPI/Migrations/20250613140026_blobmigration.Designer.cs b/TickAPI/TickAPI/Migrations/20250613140026_blobmigration.Designer.cs new file mode 100644 index 0000000..255440b --- /dev/null +++ b/TickAPI/TickAPI/Migrations/20250613140026_blobmigration.Designer.cs @@ -0,0 +1,400 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TickAPI.Common.TickApiDbContext; + +#nullable disable + +namespace TickAPI.Migrations +{ + [DbContext(typeof(TickApiDbContext))] + [Migration("20250613140026_blobmigration")] + partial class blobmigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CategoryEvent", b => + { + b.Property("CategoriesId") + .HasColumnType("uniqueidentifier"); + + b.Property("EventsId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("CategoriesId", "EventsId"); + + b.HasIndex("EventsId"); + + b.ToTable("CategoryEvent"); + }); + + modelBuilder.Entity("TickAPI.Addresses.Models.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FlatNumber") + .HasColumnType("bigint"); + + b.Property("HouseNumber") + .HasColumnType("bigint"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("TickAPI.Admins.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Login") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Admins"); + }); + + modelBuilder.Entity("TickAPI.Categories.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + + b.HasData( + new + { + Id = new Guid("ec3daf69-baa9-4fcd-a674-c09884a57272"), + Name = "Music" + }, + new + { + Id = new Guid("de89dd76-3b29-43e1-8f4b-5278b1b8bde2"), + Name = "Sports" + }, + new + { + Id = new Guid("ea58370b-2a17-4770-abea-66399ad69fb8"), + Name = "Conferences" + }, + new + { + Id = new Guid("4a086d9e-59de-4fd1-a1b2-bd9b5eec797c"), + Name = "Theatre" + }, + new + { + Id = new Guid("5f8dbe65-30be-453f-8f22-191a11b2977b"), + Name = "Comedy" + }, + new + { + Id = new Guid("4421327a-4bc8-4706-bec0-666f78ed0c69"), + Name = "Workshops" + }); + }); + + modelBuilder.Entity("TickAPI.Customers.Models.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("TickAPI.Events.Models.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AddressId") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("EventStatus") + .HasColumnType("int"); + + b.Property("ImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("MinimumAge") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizerId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AddressId"); + + b.HasIndex("OrganizerId"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("TickAPI.Organizers.Models.Organizer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Organizers"); + }); + + modelBuilder.Entity("TickAPI.TicketTypes.Models.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvailableFrom") + .HasColumnType("datetime2"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("MaxCount") + .HasColumnType("bigint"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.ToTable("TicketTypes"); + }); + + modelBuilder.Entity("TickAPI.Tickets.Models.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ForResell") + .HasColumnType("bit"); + + b.Property("NameOnTicket") + .HasColumnType("nvarchar(max)"); + + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("ResellCurrency") + .HasColumnType("nvarchar(max)"); + + b.Property("ResellPrice") + .HasColumnType("decimal(18,2)"); + + b.Property("Seats") + .HasColumnType("nvarchar(max)"); + + b.Property("TypeId") + .HasColumnType("uniqueidentifier"); + + b.Property("Used") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("TypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("CategoryEvent", b => + { + b.HasOne("TickAPI.Categories.Models.Category", null) + .WithMany() + .HasForeignKey("CategoriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TickAPI.Events.Models.Event", null) + .WithMany() + .HasForeignKey("EventsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TickAPI.Events.Models.Event", b => + { + b.HasOne("TickAPI.Addresses.Models.Address", "Address") + .WithMany() + .HasForeignKey("AddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TickAPI.Organizers.Models.Organizer", "Organizer") + .WithMany("Events") + .HasForeignKey("OrganizerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Address"); + + b.Navigation("Organizer"); + }); + + modelBuilder.Entity("TickAPI.TicketTypes.Models.TicketType", b => + { + b.HasOne("TickAPI.Events.Models.Event", "Event") + .WithMany("TicketTypes") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Event"); + }); + + modelBuilder.Entity("TickAPI.Tickets.Models.Ticket", b => + { + b.HasOne("TickAPI.Customers.Models.Customer", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TickAPI.TicketTypes.Models.TicketType", "Type") + .WithMany("Tickets") + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("TickAPI.Customers.Models.Customer", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("TickAPI.Events.Models.Event", b => + { + b.Navigation("TicketTypes"); + }); + + modelBuilder.Entity("TickAPI.Organizers.Models.Organizer", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("TickAPI.TicketTypes.Models.TicketType", b => + { + b.Navigation("Tickets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TickAPI/TickAPI/Migrations/20250613140026_blobmigration.cs b/TickAPI/TickAPI/Migrations/20250613140026_blobmigration.cs new file mode 100644 index 0000000..d39274a --- /dev/null +++ b/TickAPI/TickAPI/Migrations/20250613140026_blobmigration.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TickAPI.Migrations +{ + /// + public partial class blobmigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImageUrl", + table: "Events", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImageUrl", + table: "Events"); + } + } +} diff --git a/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs b/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs index 71d888e..18032ed 100644 --- a/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs +++ b/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs @@ -179,6 +179,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EventStatus") .HasColumnType("int"); + b.Property("ImageUrl") + .HasColumnType("nvarchar(max)"); + b.Property("MinimumAge") .HasColumnType("bigint"); diff --git a/TickAPI/TickAPI/Program.cs b/TickAPI/TickAPI/Program.cs index 53441b3..5a983d6 100644 --- a/TickAPI/TickAPI/Program.cs +++ b/TickAPI/TickAPI/Program.cs @@ -33,6 +33,8 @@ using TickAPI.Categories.Abstractions; using TickAPI.Categories.Respositories; using TickAPI.Categories.Services; +using TickAPI.Common.Blob.Abstractions; +using TickAPI.Common.Blob.Services; using TickAPI.Common.Claims.Abstractions; using TickAPI.Common.Claims.Services; using TickAPI.Common.Redis.Abstractions; @@ -146,6 +148,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/TickAPI/TickAPI/TickAPI.csproj b/TickAPI/TickAPI/TickAPI.csproj index fb79365..adc0f04 100644 --- a/TickAPI/TickAPI/TickAPI.csproj +++ b/TickAPI/TickAPI/TickAPI.csproj @@ -8,6 +8,7 @@ + From 7c6c42f7d30004a2df24ab1de84049f9207e47d7 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 19:50:11 +0200 Subject: [PATCH 12/24] fixed GetTicketsForResellAsync to return correct prices --- .../TickAPI/Tickets/Services/TicketService.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index d9e05c2..6ef8277 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -93,10 +93,29 @@ public async Task>> GetTicke { return Result>.PropagateError(paginatedTicketsResult); } + var paginatedResult = _paginationService.MapData(paginatedTicketsResult.Value!, - t => new GetTicketForResellResponseDto(t.Id, t.Type.Price, t.Type.Currency, t.Type.Description, t.Seats)); + t => + { + decimal price; + string currency; + if (t.ResellPrice is not null && t.ResellCurrency is not null) + { + price = t.ResellPrice.Value; + currency = t.ResellCurrency; + } + else + { + price = t.Type.Price; + currency = t.Type.Currency; + } + + return new GetTicketForResellResponseDto(t.Id, price, currency, t.Type.Description, + t.Seats); + }); return Result>.Success(paginatedResult); } + public async Task>> GetTicketsForCustomerAsync(string email, int page, int pageSize, TicketFiltersDto ? ticketFilters = null) { var customerTickets = _ticketRepository.GetTicketsByCustomerEmail(email); From 43f874a09a6dda90d33b5720720187c55a6b4b51 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 20:06:58 +0200 Subject: [PATCH 13/24] finished adding resell tickets to cart --- .../Abstractions/IShoppingCartRepository.cs | 2 + .../Repositories/ShoppingCartRepository.cs | 72 +++++++++++++++++ .../Services/ShoppingCartService.cs | 81 +++++++++++++++++-- .../Tickets/Abstractions/ITicketRepository.cs | 1 + .../Tickets/Abstractions/ITicketService.cs | 3 +- .../Tickets/Repositories/TicketRepository.cs | 17 ++++ .../TickAPI/Tickets/Services/TicketService.cs | 12 +++ 7 files changed, 180 insertions(+), 8 deletions(-) diff --git a/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs b/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs index d8be591..b39b804 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs @@ -15,4 +15,6 @@ public interface IShoppingCartRepository public Task> IncrementAmountOfTicketTypeAsync(Guid ticketTypeId, long amount); public Task> DecrementAmountOfTicketTypeAsync(Guid ticketTypeId, long amount); public Task RemoveAmountOfTicketTypeAsync(Guid ticketTypeId); + public Task AddResellTicketToCartAsync(string customerEmail, Guid ticketId); + public Task> CheckResellTicketAvailabilityAsync(Guid ticketId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs b/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs index 493afa5..cda3920 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs @@ -254,6 +254,73 @@ public async Task RemoveAmountOfTicketTypeAsync(Guid ticketTypeId) return Result.Success(); } + public async Task AddResellTicketToCartAsync(string customerEmail, Guid ticketId) + { + var getShoppingCartResult = await GetShoppingCartByEmailAsync(customerEmail); + + if (getShoppingCartResult.IsError) + { + return Result.PropagateError(getShoppingCartResult); + } + + var cart = getShoppingCartResult.Value!; + + cart.ResellTickets.Add(new ShoppingCartResellTicket {TicketId = ticketId}); + + var setKeyResult = await SetTicketKeyAsync(ticketId); + + if (setKeyResult.IsError) + { + return Result.PropagateError(setKeyResult); + } + + var updateShoppingCartResult = await UpdateShoppingCartAsync(customerEmail, cart); + + if (updateShoppingCartResult.IsError) + { + return Result.PropagateError(updateShoppingCartResult); + } + + return Result.Success(); + } + + public async Task> CheckResellTicketAvailabilityAsync(Guid ticketId) + { + bool exists; + + try + { + exists = await _redisService.KeyExistsAsync(GetResellTicketKey(ticketId)); + } + catch (Exception e) + { + return Result.Failure(StatusCodes.Status500InternalServerError, e.Message); + } + + return Result.Success(!exists); + } + + private async Task SetTicketKeyAsync(Guid ticketId) + { + bool success; + + try + { + success = await _redisService.SetStringAsync(GetResellTicketKey(ticketId), string.Empty); + } + catch (Exception e) + { + return Result.Failure(StatusCodes.Status500InternalServerError, e.Message); + } + + if (!success) + { + return Result.Failure(StatusCodes.Status500InternalServerError, "the ticket key could not be updated"); + } + + return Result.Success(); + } + private static string GetCartKey(string customerEmail) { return $"cart:{customerEmail}"; @@ -263,4 +330,9 @@ private static string GetAmountKey(Guid ticketTypeId) { return $"amount:{ticketTypeId}"; } + + private static string GetResellTicketKey(Guid ticketId) + { + return $"resell:{ticketId}"; + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs index 4ca0b55..d8895e9 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs @@ -54,9 +54,28 @@ public async Task AddNewTicketsToCartAsync(Guid ticketTypeId, uint amoun return Result.Success(); } - public Task AddResellTicketToCartAsync(Guid ticketId, string customerEmail) + public async Task AddResellTicketToCartAsync(Guid ticketId, string customerEmail) { - throw new NotImplementedException(); + var availabilityResult = await _shoppingCartRepository.CheckResellTicketAvailabilityAsync(ticketId); + + if (availabilityResult.IsError) + { + return Result.PropagateError(availabilityResult); + } + + if (!availabilityResult.Value) + { + return Result.Failure(StatusCodes.Status400BadRequest, $"the ticket you are trying to add isn't currently available"); + } + + var addTicketToCartResult = await _shoppingCartRepository.AddResellTicketToCartAsync(customerEmail, ticketId); + + if (addTicketToCartResult.IsError) + { + return Result.PropagateError(addTicketToCartResult); + } + + return Result.Success(); } public async Task> GetTicketsFromCartAsync(string customerEmail) @@ -71,6 +90,7 @@ public async Task> GetTicketsFromCartA var cart = getShoppingCartResult.Value!; var newTickets = new List(); + var resellTickets = new List(); foreach (var ticket in cart.NewTickets) { @@ -87,10 +107,24 @@ public async Task> GetTicketsFromCartA newTickets.Add(newTicket); } + + foreach (var ticket in cart.ResellTickets) + { + var resellTicketResult = await _ticketService.GetTicketByIdAsync(ticket.TicketId); + + if (resellTicketResult.IsError) + { + return Result.PropagateError(resellTicketResult); + } + + var resellTicket = + ShoppingCartMapper.MapTicketToGetShoppingCartTicketsResellTicketDetailsResponseDto(resellTicketResult + .Value!); + + resellTickets.Add(resellTicket); + } - // TODO: Add resell ticket parsing - - var result = new GetShoppingCartTicketsResponseDto(newTickets, []); + var result = new GetShoppingCartTicketsResponseDto(newTickets, resellTickets); return Result.Success(result); } @@ -145,8 +179,41 @@ public async Task>> GetDueAmountAsync(string dueAmount.Add(ticketType.Currency, newTicket.Quantity * ticketType.Price); } } - - // TODO: Add resell tickets to the calculations + + foreach (var resellTicket in cart.ResellTickets) + { + var ticketResult = await _ticketService.GetTicketByIdAsync(resellTicket.TicketId); + + if (ticketResult.IsError) + { + return Result>.PropagateError(ticketResult); + } + + var ticket = ticketResult.Value!; + + if (ticket.ResellPrice is not null && ticket.ResellCurrency is not null) + { + if (dueAmount.ContainsKey(ticket.ResellCurrency)) + { + dueAmount[ticket.ResellCurrency] += ticket.ResellPrice.Value; + } + else + { + dueAmount.Add(ticket.ResellCurrency, ticket.ResellPrice.Value); + } + } + else + { + if (dueAmount.ContainsKey(ticket.Type.Currency)) + { + dueAmount[ticket.Type.Currency] += ticket.Type.Price; + } + else + { + dueAmount.Add(ticket.Type.Currency, ticket.Type.Price); + } + } + } return Result>.Success(dueAmount); } diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs index 4add328..265614a 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs @@ -14,4 +14,5 @@ public interface ITicketRepository public Task MarkTicketAsUsed(Guid id); public Task SetTicketForResell(Guid ticketId, decimal newPrice, string currency); public Task AddTicketAsync(Ticket ticket); + public Task> GetTicketWithDetailsByIdAsync(Guid id); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs index 1fce588..454f95f 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -4,6 +4,7 @@ using TickAPI.Customers.Models; using TickAPI.Tickets.DTOs.Request; using TickAPI.Tickets.DTOs.Response; +using TickAPI.Tickets.Models; using TickAPI.TicketTypes.Models; namespace TickAPI.Tickets.Abstractions; @@ -20,8 +21,8 @@ public Task>> GetTicketsForCustome public Task ScanTicket(Guid ticketGuid); public Task> GetTicketDetailsAsync(Guid ticketGuid, string email, string scanUrl); - public Task SetTicketForResellAsync(Guid ticketId, string email, decimal resellPrice, string resellCurrency); + public Task> GetTicketByIdAsync(Guid ticketId); public Task> GetTicketTypeByIdAsync(Guid ticketTypeId); public Task CreateTicketAsync(TicketType type, Customer owner, string? nameOnTicket = null, string? seats = null); diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index c3d3911..4e9f076 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -88,6 +88,23 @@ public async Task AddTicketAsync(Ticket ticket) return Result.Success(); } + public async Task> GetTicketWithDetailsByIdAsync(Guid id) + { + var ticket = await _tickApiDbContext.Tickets + .Include(t => t.Type) + .Include(t => t.Type.Event) + .Include(t => t.Type.Event.Organizer) + .Include(t => t.Type.Event.Address) + .Include(t => t.Owner) + .Where(t => t.Id == id) + .FirstOrDefaultAsync(); + if (ticket == null) + { + return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); + } + return Result.Success(ticket); + } + public async Task SetTicketForResell(Guid ticketId, decimal newPrice, string currency) { var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == ticketId); diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index 6ef8277..ca33c83 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -168,6 +168,18 @@ public async Task> GetTicketDetailsAsync(Gui return Result.Success(ticketDetails); } + public async Task> GetTicketByIdAsync(Guid ticketId) + { + var ticketResult = await _ticketRepository.GetTicketWithDetailsByIdAsync(ticketId); + + if (ticketResult.IsError) + { + return Result.PropagateError(ticketResult); + } + + return Result.Success(ticketResult.Value!); + } + public async Task> GetTicketTypeByIdAsync(Guid ticketTypeId) { var ticketTypeResult = await _ticketTypeRepository.GetTicketTypeByIdAsync(ticketTypeId); From f4662ed2bee5bde14907a668987ff441ecc7d99e Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 20:57:39 +0200 Subject: [PATCH 14/24] added the ability to remove resell tickets from cart --- .../Abstractions/IShoppingCartRepository.cs | 1 + .../Mappers/ShoppingCartMapper.cs | 4 +- .../Repositories/ShoppingCartRepository.cs | 62 ++++++++++++++++++- .../Services/ShoppingCartService.cs | 11 +++- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs b/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs index b39b804..e93eea9 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Abstractions/IShoppingCartRepository.cs @@ -17,4 +17,5 @@ public interface IShoppingCartRepository public Task RemoveAmountOfTicketTypeAsync(Guid ticketTypeId); public Task AddResellTicketToCartAsync(string customerEmail, Guid ticketId); public Task> CheckResellTicketAvailabilityAsync(Guid ticketId); + public Task RemoveResellTicketFromCartAsync(string customerEmail, Guid ticketId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/ShoppingCarts/Mappers/ShoppingCartMapper.cs b/TickAPI/TickAPI/ShoppingCarts/Mappers/ShoppingCartMapper.cs index 9e75a25..a63bb03 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Mappers/ShoppingCartMapper.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Mappers/ShoppingCartMapper.cs @@ -29,8 +29,8 @@ public static GetShoppingCartTicketsResellTicketDetailsResponseDto ticket.Type.Description, ticket.Type.Event.Organizer.DisplayName, ticket.Owner.Email, - ticket.Type.Price, - ticket.Type.Currency + ticket.ResellPrice ?? ticket.Type.Price, + ticket.ResellCurrency ?? ticket.Type.Currency ); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs b/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs index cda3920..1fb7188 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Repositories/ShoppingCartRepository.cs @@ -299,14 +299,51 @@ public async Task> CheckResellTicketAvailabilityAsync(Guid ticketId return Result.Success(!exists); } - + + public async Task RemoveResellTicketFromCartAsync(string customerEmail, Guid ticketId) + { + var getShoppingCartResult = await GetShoppingCartByEmailAsync(customerEmail); + + if (getShoppingCartResult.IsError) + { + return Result.PropagateError(getShoppingCartResult); + } + + var cart = getShoppingCartResult.Value!; + + var existingEntry = cart.ResellTickets.FirstOrDefault(t => t.TicketId == ticketId); + + if (existingEntry is null) + { + return Result.Failure(StatusCodes.Status404NotFound, "the shopping cart does not contain this ticket"); + } + + cart.ResellTickets.Remove(existingEntry); + + var deleteKeyResult = await DeleteTicketKeyAsync(ticketId); + + if (deleteKeyResult.IsError) + { + return Result.PropagateError(deleteKeyResult); + } + + var updateShoppingCartResult = await UpdateShoppingCartAsync(customerEmail, cart); + + if (updateShoppingCartResult.IsError) + { + return Result.PropagateError(updateShoppingCartResult); + } + + return Result.Success(); + } + private async Task SetTicketKeyAsync(Guid ticketId) { bool success; try { - success = await _redisService.SetStringAsync(GetResellTicketKey(ticketId), string.Empty); + success = await _redisService.SetStringAsync(GetResellTicketKey(ticketId), string.Empty, _defaultExpiry); } catch (Exception e) { @@ -320,6 +357,27 @@ private async Task SetTicketKeyAsync(Guid ticketId) return Result.Success(); } + + private async Task DeleteTicketKeyAsync(Guid ticketId) + { + bool success; + + try + { + success = await _redisService.DeleteKeyAsync(GetResellTicketKey(ticketId)); + } + catch (Exception e) + { + return Result.Failure(StatusCodes.Status500InternalServerError, e.Message); + } + + if (!success) + { + return Result.Failure(StatusCodes.Status500InternalServerError, "the ticket key could not be deleted"); + } + + return Result.Success(); + } private static string GetCartKey(string customerEmail) { diff --git a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs index d8895e9..b379654 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs @@ -141,9 +141,16 @@ public async Task RemoveNewTicketsFromCartAsync(Guid ticketTypeId, uint return Result.Success(); } - public Task RemoveResellTicketFromCartAsync(Guid ticketId, string customerEmail) + public async Task RemoveResellTicketFromCartAsync(Guid ticketId, string customerEmail) { - throw new NotImplementedException(); + var removeTicketFromCartResult = await _shoppingCartRepository.RemoveResellTicketFromCartAsync(customerEmail, ticketId); + + if (removeTicketFromCartResult.IsError) + { + return Result.PropagateError(removeTicketFromCartResult); + } + + return Result.Success(); } public async Task>> GetDueAmountAsync(string customerEmail) From fcb5fbf8e1da28e39257fca83f106058897b9196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:18:55 +0200 Subject: [PATCH 15/24] appsettings example --- TickAPI/TickAPI/appsettings.example.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TickAPI/TickAPI/appsettings.example.json b/TickAPI/TickAPI/appsettings.example.json index c7e46ab..cb999f1 100644 --- a/TickAPI/TickAPI/appsettings.example.json +++ b/TickAPI/TickAPI/appsettings.example.json @@ -37,5 +37,9 @@ "ShoppingCart": { "SyncIntervalMinutes": 5, "LifetimeMinutes": 15 + }, + "BlobStorage": { + "ConnectionString": "string", + "ContainerName": "resellio" } } \ No newline at end of file From c3c28c93104149a9d8fd87f9b6b666c56bb1f362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:41:17 +0200 Subject: [PATCH 16/24] Working blob --- .../TickAPI/Common/Blob/Services/BlobService.cs | 15 ++++++++++++++- .../Events/Controllers/EventsController.cs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs index f042a20..d1af679 100644 --- a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs +++ b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs @@ -1,5 +1,6 @@ using Azure.Identity; using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; using TickAPI.Common.Blob.Abstractions; namespace TickAPI.Common.Blob.Services; @@ -21,9 +22,21 @@ public async Task UploadToBlobContainerAsync(string name, IFormFile imag Guid id = Guid.NewGuid(); string blobName = name + id; var blob = container.GetBlobClient(blobName); + var contentType = image.ContentType; var stream = new MemoryStream(); await image.CopyToAsync(stream); - var response = blob.Upload(stream); + stream.Position = 0; + + var uploadOptions = new BlobUploadOptions + { + HttpHeaders = new BlobHttpHeaders + { + ContentType = contentType + } + }; + + var response = await blob.UploadAsync(stream, uploadOptions); + return blob.Uri.ToString(); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Controllers/EventsController.cs b/TickAPI/TickAPI/Events/Controllers/EventsController.cs index 04307f7..9cdaa44 100644 --- a/TickAPI/TickAPI/Events/Controllers/EventsController.cs +++ b/TickAPI/TickAPI/Events/Controllers/EventsController.cs @@ -28,7 +28,7 @@ public EventsController(IEventService eventService, IClaimsService claimsService [AuthorizeWithPolicy(AuthPolicies.VerifiedOrganizerPolicy)] [HttpPost] - public async Task> CreateEvent([FromBody] CreateEventDto request) + public async Task> CreateEvent([FromForm] CreateEventDto request) { var emailResult = _claimsService.GetEmailFromClaims(User.Claims); if (emailResult.IsError) From 4aa6268264c2f208190c0e18c672afdcc3ee7704 Mon Sep 17 00:00:00 2001 From: kasrow12 Date: Sat, 14 Jun 2025 00:58:12 +0200 Subject: [PATCH 17/24] Return parameter 'Used' in ticket details --- .../Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs | 3 ++- TickAPI/TickAPI/Tickets/Services/TicketService.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs index 17f4fc5..2a5ecc8 100644 --- a/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs +++ b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs @@ -14,5 +14,6 @@ public record GetTicketDetailsResponseDto DateTime EndDate, GetTicketDetailsAddressDto Address, Guid eventId, - string qrcode + string qrcode, + bool Used ); \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index c643424..cf714e0 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -144,7 +144,8 @@ public async Task> GetTicketDetailsAsync(Gui ticket.Type.Event.EndDate, address, ticket.Type.Event.Id, - qrcode + qrcode, + ticket.Used ); return Result.Success(ticketDetails); } From 80c74c1f71352f58a65a47a1cdd4c015c050f78a Mon Sep 17 00:00:00 2001 From: kubapoke Date: Sat, 14 Jun 2025 02:05:14 +0200 Subject: [PATCH 18/24] added correctness checks to AddResellTicketToCartAsync --- .../Services/ShoppingCartService.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs index b379654..360955f 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs @@ -56,6 +56,25 @@ public async Task AddNewTicketsToCartAsync(Guid ticketTypeId, uint amoun public async Task AddResellTicketToCartAsync(Guid ticketId, string customerEmail) { + var ticketResult = await _ticketService.GetTicketByIdAsync(ticketId); + + if (ticketResult.IsError) + { + return Result.PropagateError(ticketResult); + } + + var ticket = ticketResult.Value!; + + if (!ticket.ForResell) + { + return Result.Failure(StatusCodes.Status400BadRequest, $"chosen ticket is not available for resell"); + } + + if (ticket.Owner.Email == customerEmail) + { + return Result.Failure(StatusCodes.Status403Forbidden, "you can't buy ticket sold from your account"); + } + var availabilityResult = await _shoppingCartRepository.CheckResellTicketAvailabilityAsync(ticketId); if (availabilityResult.IsError) From 78098e6b8661b104f68904d48a997813733b3de7 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Sat, 14 Jun 2025 03:08:44 +0200 Subject: [PATCH 19/24] added checkout logic for resell tickets --- .../Services/ShoppingCartService.cs | 86 +++++++++++++++---- .../Tickets/Abstractions/ITicketRepository.cs | 2 + .../Tickets/Abstractions/ITicketService.cs | 1 + .../Tickets/Repositories/TicketRepository.cs | 30 +++++++ .../TickAPI/Tickets/Services/TicketService.cs | 7 ++ 5 files changed, 108 insertions(+), 18 deletions(-) diff --git a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs index 360955f..2f01bc6 100644 --- a/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs +++ b/TickAPI/TickAPI/ShoppingCarts/Services/ShoppingCartService.cs @@ -4,10 +4,12 @@ using TickAPI.Common.Results; using TickAPI.Common.Results.Generic; using TickAPI.Customers.Abstractions; +using TickAPI.Customers.Models; using TickAPI.Events.Models; using TickAPI.ShoppingCarts.Abstractions; using TickAPI.ShoppingCarts.DTOs.Response; using TickAPI.ShoppingCarts.Mappers; +using TickAPI.ShoppingCarts.Models; using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.Models; using TickAPI.TicketTypes.Abstractions; @@ -277,26 +279,11 @@ await _paymentGatewayService.ProcessPayment(new PaymentRequestPG(amount, currenc return Result.PropagateError(paymentResult); } - var generateTicketsResult = await GenerateBoughtTicketsAsync(customerEmail, currency); - // TODO: Add passing ownership of resell tickets - - if (generateTicketsResult.IsError) - { - return Result.PropagateError(generateTicketsResult); - } - - var payment = paymentResult.Value!; - - return Result.Success(payment); - } - - private async Task GenerateBoughtTicketsAsync(string customerEmail, string currency) - { var getShoppingCartResult = await _shoppingCartRepository.GetShoppingCartByEmailAsync(customerEmail); if (getShoppingCartResult.IsError) { - return Result.PropagateError(getShoppingCartResult); + return Result.PropagateError(getShoppingCartResult); } var cart = getShoppingCartResult.Value!; @@ -305,10 +292,32 @@ private async Task GenerateBoughtTicketsAsync(string customerEmail, stri if (getCustomerResult.IsError) { - return Result.PropagateError(getCustomerResult); + return Result.PropagateError(getCustomerResult); } var owner = getCustomerResult.Value!; + + var generateTicketsResult = await GenerateBoughtTicketsAsync(cart, owner, currency); + + if (generateTicketsResult.IsError) + { + return Result.PropagateError(generateTicketsResult); + } + + var passOwnershipResult = await PassTicketOwnershipAsync(cart, owner, currency); + + if (passOwnershipResult.IsError) + { + return Result.PropagateError(passOwnershipResult); + } + + var payment = paymentResult.Value!; + + return Result.Success(payment); + } + + private async Task GenerateBoughtTicketsAsync(ShoppingCart cart, Customer owner, string currency) + { var removals = new List<(Guid id, uint amount)>(); foreach (var ticket in cart.NewTickets) @@ -340,7 +349,48 @@ private async Task GenerateBoughtTicketsAsync(string customerEmail, stri foreach (var (id, amount) in removals) { - var removalResult = await RemoveNewTicketsFromCartAsync(id, amount, customerEmail); + var removalResult = await RemoveNewTicketsFromCartAsync(id, amount, owner.Email); + + if (removalResult.IsError) + { + return Result.PropagateError(removalResult); + } + } + + return Result.Success(); + } + + private async Task PassTicketOwnershipAsync(ShoppingCart cart, Customer newOwner, string currency) + { + var removals = new List(); + + foreach (var resellTicket in cart.ResellTickets) + { + var ticketResult = await _ticketService.GetTicketByIdAsync(resellTicket.TicketId); + + if (ticketResult.IsError) + { + return Result.PropagateError(ticketResult); + } + + var ticket = ticketResult.Value!; + + if ((ticket.ResellCurrency ?? ticket.Type.Currency) == currency) + { + removals.Add(ticket.Id); + + var createTicketResult = await _ticketService.ChangeTicketOwnershipViaResellAsync(ticket, newOwner); + + if (createTicketResult.IsError) + { + return Result.PropagateError(createTicketResult); + } + } + } + + foreach (var id in removals) + { + var removalResult = await RemoveResellTicketFromCartAsync(id, newOwner.Email); if (removalResult.IsError) { diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs index 265614a..e441b09 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs @@ -1,5 +1,6 @@ using TickAPI.Common.Results; using TickAPI.Common.Results.Generic; +using TickAPI.Customers.Models; using TickAPI.Tickets.Models; using TickAPI.TicketTypes.Models; @@ -15,4 +16,5 @@ public interface ITicketRepository public Task SetTicketForResell(Guid ticketId, decimal newPrice, string currency); public Task AddTicketAsync(Ticket ticket); public Task> GetTicketWithDetailsByIdAsync(Guid id); + public Task ChangeTicketOwnershipAsync(Ticket ticket, Customer newOwner, string? nameOnTicket = null); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs index 454f95f..30c46a2 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -26,4 +26,5 @@ public Task> GetTicketDetailsAsync(Guid tick public Task> GetTicketTypeByIdAsync(Guid ticketTypeId); public Task CreateTicketAsync(TicketType type, Customer owner, string? nameOnTicket = null, string? seats = null); + public Task ChangeTicketOwnershipViaResellAsync(Ticket ticket, Customer newOwner, string? nameOnTicket = null); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index 4e9f076..537747a 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -105,6 +105,36 @@ public async Task> GetTicketWithDetailsByIdAsync(Guid id) return Result.Success(ticket); } + public async Task ChangeTicketOwnershipAsync(Ticket ticket, Customer newOwner, string? nameOnTicket = null) + { + var ticketFromDb = await _tickApiDbContext.Tickets + .Include(t => t.Owner) // Include if needed + .FirstOrDefaultAsync(t => t.Id == ticket.Id); + + var newOwnerFromDb = await _tickApiDbContext.Customers + .FirstOrDefaultAsync(c => c.Id == newOwner.Id); + + if (ticketFromDb == null || newOwnerFromDb == null) + { + return Result.Failure(StatusCodes.Status404NotFound, "Ticket or new owner not found"); + } + if (!ticketFromDb.ForResell) + { + return Result.Failure(StatusCodes.Status400BadRequest, "This ticket can't have its ownership passed"); + } + if (ticketFromDb.Owner.Id == newOwnerFromDb.Id) + { + return Result.Failure(StatusCodes.Status400BadRequest, "You can't change the owner of the ticket to be the same"); + } + + ticketFromDb.Owner = newOwnerFromDb; + ticketFromDb.NameOnTicket = nameOnTicket ?? $"{newOwnerFromDb.FirstName} {newOwnerFromDb.LastName}"; + + await _tickApiDbContext.SaveChangesAsync(); + + return Result.Success(); + } + public async Task SetTicketForResell(Guid ticketId, decimal newPrice, string currency) { var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == ticketId); diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index ca33c83..189e75e 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -210,6 +210,13 @@ public async Task CreateTicketAsync(TicketType type, Customer owner, str return addTicketResult; } + public async Task ChangeTicketOwnershipViaResellAsync(Ticket ticket, Customer newOwner, string? nameOnTicket = null) + { + var updateTicketResult = await _ticketRepository.ChangeTicketOwnershipAsync(ticket, newOwner); + + return updateTicketResult; + } + public async Task ScanTicket(Guid ticketGuid) { var res = await _ticketRepository.MarkTicketAsUsed(ticketGuid); From 1a34144a10ea11ebb192b9a60e0d09f1468a75bd Mon Sep 17 00:00:00 2001 From: kubapoke Date: Sat, 14 Jun 2025 03:21:03 +0200 Subject: [PATCH 20/24] added some missing logic from the end of resell process --- TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index 537747a..90d3244 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -129,6 +129,9 @@ public async Task ChangeTicketOwnershipAsync(Ticket ticket, Customer new ticketFromDb.Owner = newOwnerFromDb; ticketFromDb.NameOnTicket = nameOnTicket ?? $"{newOwnerFromDb.FirstName} {newOwnerFromDb.LastName}"; + ticketFromDb.ForResell = false; + ticketFromDb.ResellCurrency = null; + ticketFromDb.ResellPrice = null; await _tickApiDbContext.SaveChangesAsync(); From b71fea8a96e5bf02c67fac981eb9307c44564b05 Mon Sep 17 00:00:00 2001 From: staszkiet Date: Sat, 14 Jun 2025 18:06:28 +0200 Subject: [PATCH 21/24] minor fixes --- .../Controllers/EventsControllerTests.cs | 6 +- .../Events/Services/EventServiceTests.cs | 74 ++++++++++++------- TickAPI/TickAPI.Tests/Events/Utils.cs | 6 +- .../Common/Blob/Abstractions/IBlobService.cs | 2 +- .../Common/Blob/Services/BlobService.cs | 14 +--- .../Response/GetEventDetailsResponseDto.cs | 3 +- .../DTOs/Response/GetEventResponseDto.cs | 3 +- .../TickAPI/Events/Services/EventService.cs | 7 +- 8 files changed, 66 insertions(+), 49 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs b/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs index 6d8b112..ef3fad5 100644 --- a/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs @@ -45,11 +45,11 @@ public async Task CreateEvent_WhenDataIsValid_ShouldReturnSuccess() new CreateEventTicketTypeDto("V.I.P", 10, 500.9m, "zł", new DateTime(2025, 5, 10)), ]; CreateAddressDto createAddress = new CreateAddressDto("United States", "New York", "Main st", 20, null, "00-000"); - CreateEventDto eventDto = new CreateEventDto(name, description, startDate, endDate, minimumAge, categories, ticketTypes, eventStatus, createAddress); + CreateEventDto eventDto = new CreateEventDto(name, description, startDate, endDate, minimumAge, categories, ticketTypes, eventStatus, createAddress, null); var eventServiceMock = new Mock(); eventServiceMock - .Setup(m => m.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories , ticketTypes, eventStatus, email)) + .Setup(m => m.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories , ticketTypes, eventStatus, email, null)) .ReturnsAsync(Result.Success(new Event())); var claims = new List @@ -123,7 +123,7 @@ public async Task CreateEvent_WhenMissingEmailClaims_ShouldReturnBadRequest() }; // act - var res = await sut.CreateEvent(new CreateEventDto(name, description, startDate, endDate, minimumAge, categories, ticketTypes, eventStatus, createAddress)); + var res = await sut.CreateEvent(new CreateEventDto(name, description, startDate, endDate, minimumAge, categories, ticketTypes, eventStatus, createAddress, null)); // Assert var result = Assert.IsType>(res); diff --git a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs index 9d335c6..ad14287 100644 --- a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs @@ -9,6 +9,7 @@ using TickAPI.Categories.Abstractions; using TickAPI.Categories.DTOs.Request; using TickAPI.Categories.Models; +using TickAPI.Common.Blob.Abstractions; using TickAPI.Common.Mail.Abstractions; using TickAPI.Common.Results; using TickAPI.Events.Models; @@ -116,10 +117,10 @@ public async Task CreateNewEventAsync_WhenEventDataIsValid_ShouldReturnNewEvent( 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act - var result = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); + var result = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail, null); // Assert Assert.True(result.IsSuccess); @@ -191,12 +192,13 @@ public async Task CreateNewEventAsync_WhenEndDateIsBeforeStartDate_ShouldReturnB var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act - var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); + var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail, null); // Assert Assert.False(res.IsSuccess); @@ -249,11 +251,11 @@ public async Task CreateNewEventAsync_WhenTicketTypeAvailabilityIsAfterEventsEnd 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act - var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); + var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail, null); // Assert Assert.False(res.IsSuccess); @@ -305,11 +307,12 @@ public async Task CreateNewEventAsync_WhenStartDateIsBeforeNow_ShouldReturnBadRe var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act - var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); + var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail, null); // Assert Assert.False(res.IsSuccess); @@ -376,8 +379,9 @@ public async Task GetOrganizerEvents_WhenPaginationSucceeds_ShouldReturnPaginate var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetOrganizerEventsAsync(organizer, page, pageSize); @@ -429,8 +433,9 @@ public async Task GetOrganizerEvents_WhenPaginationFails_ShouldPropagateError() var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetOrganizerEventsAsync(organizer, page, pageSize); @@ -495,8 +500,9 @@ public async Task GetEventsAsync_WhenPaginationSucceeds_ShouldReturnPaginatedEve var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetEventsAsync(page, pageSize); @@ -543,8 +549,9 @@ public async Task GetEventsAsync_WhenPaginationFails_ShouldPropagateError() var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetEventsAsync(page, pageSize); @@ -585,8 +592,9 @@ public async Task GetEventsPaginationDetailsAsync_WhenSuccessful_ShouldReturnPag var customerRepositoryMock = new Mock(); var mailServiceMock = new Mock(); + var blobServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetEventsPaginationDetailsAsync(pageSize); @@ -626,8 +634,9 @@ public async Task GetEventsPaginationDetailsAsync_WhenFails_ShouldReturnError() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetEventsPaginationDetailsAsync(pageSize); @@ -665,8 +674,9 @@ public async Task GetEventDetailsAsync_WhenSuccessful_ShouldReturnEventDetails() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetEventDetailsAsync(@event.Id); @@ -704,8 +714,9 @@ public async Task GetEventDetailsAsync_WhenFails_ShouldReturnEventError() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.GetEventDetailsAsync(@event.Id); @@ -811,8 +822,9 @@ public async Task EditEventAsync_WhenDataValid_ShouldUpdateEvent() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -880,8 +892,9 @@ public async Task EditEventAsync_WhenEventNotFound_ShouldReturnError() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -952,8 +965,9 @@ public async Task EditEventAsync_WhenEndDateBeforeStartDate_ShouldReturnBadReque 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -1025,8 +1039,9 @@ public async Task EditEventAsync_WhenStartDateChangedAndInPast_ShouldReturnBadRe 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -1142,8 +1157,9 @@ public async Task EditEventAsync_StartDateNotChangedAndInPast_ShouldUpdateEvent( 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -1228,8 +1244,9 @@ public async Task EditEventAsync_WhenTicketTypeAvailableFromAfterEndDate_ShouldR 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -1304,8 +1321,9 @@ public async Task EditEventAsync_WhenAddressServiceFails_ShouldPropagateError() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -1393,8 +1411,9 @@ public async Task EditEventAsync_WhenCategoryServiceFails_ShouldPropagateError() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, @@ -1490,8 +1509,9 @@ public async Task EditEventAsync_WhenSaveEventFails_ShouldPropagateError() 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); + var blobServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object, customerRepositoryMock.Object, mailServiceMock.Object, blobServiceMock.Object); // Act var result = await sut.EditEventAsync( organizer, diff --git a/TickAPI/TickAPI.Tests/Events/Utils.cs b/TickAPI/TickAPI.Tests/Events/Utils.cs index 1fa0c76..7980a47 100644 --- a/TickAPI/TickAPI.Tests/Events/Utils.cs +++ b/TickAPI/TickAPI.Tests/Events/Utils.cs @@ -51,7 +51,8 @@ public static GetEventResponseDto CreateSampleEventResponseDto(string name) new GetEventResponsePriceInfoDto(300, "PLN"), [new GetEventResponseCategoryDto("Test")], EventStatus.TicketsAvailable, - new GetEventResponseAddressDto("United States", "New York", "10001", "Main St", 123, null) + new GetEventResponseAddressDto("United States", "New York", "10001", "Main St", 123, null), + null ); } @@ -69,7 +70,8 @@ [new GetEventResponseCategoryDto("Test")], new GetEventDetailsResponseTicketTypeDto(Guid.Parse("7ecfc61a-32d2-4124-a95c-cb5834a49990"), "Description #2", 300, "PLN", new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 30), new GetEventDetailsResponseTicketTypeDto(Guid.Parse("7be2ae57-2394-4854-bf11-9567ce7e0ab6"), "Description #3", 200, "PLN", new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 20)], EventStatus.TicketsAvailable, - new GetEventResponseAddressDto("United States", "New York", "10001", "Main St", 123, null) + new GetEventResponseAddressDto("United States", "New York", "10001", "Main St", 123, null), + null ); } } diff --git a/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs b/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs index 9ae6b1a..f253b67 100644 --- a/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs +++ b/TickAPI/TickAPI/Common/Blob/Abstractions/IBlobService.cs @@ -2,5 +2,5 @@ public interface IBlobService { - public Task UploadToBlobContainerAsync(string name, IFormFile image); + public Task UploadToBlobContainerAsync(IFormFile image); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs index d1af679..9d79bfa 100644 --- a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs +++ b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs @@ -16,26 +16,18 @@ public BlobService(IConfiguration configuration) _containerName = configuration["BlobStorage:ContainerName"]; } - public async Task UploadToBlobContainerAsync(string name, IFormFile image) + public async Task UploadToBlobContainerAsync(IFormFile image) { var container = new BlobContainerClient(_connectionString, _containerName); Guid id = Guid.NewGuid(); - string blobName = name + id; + string blobName = id.ToString(); var blob = container.GetBlobClient(blobName); - var contentType = image.ContentType; var stream = new MemoryStream(); await image.CopyToAsync(stream); stream.Position = 0; - var uploadOptions = new BlobUploadOptions - { - HttpHeaders = new BlobHttpHeaders - { - ContentType = contentType - } - }; - var response = await blob.UploadAsync(stream, uploadOptions); + var response = await blob.UploadAsync(stream); return blob.Uri.ToString(); } diff --git a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs index c5ea721..60e2862 100644 --- a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs @@ -12,5 +12,6 @@ public record GetEventDetailsResponseDto( List Categories, List TicketTypes, EventStatus Status, - GetEventResponseAddressDto Address + GetEventResponseAddressDto Address, + string? ImageUrl ); \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/DTOs/Response/GetEventResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/GetEventResponseDto.cs index a280251..6c15242 100644 --- a/TickAPI/TickAPI/Events/DTOs/Response/GetEventResponseDto.cs +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventResponseDto.cs @@ -13,5 +13,6 @@ public record GetEventResponseDto( GetEventResponsePriceInfoDto MaximumPrice, List Categories, EventStatus Status, - GetEventResponseAddressDto Address + GetEventResponseAddressDto Address, + string? ImageUrl ); diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index 032ba37..286ddce 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -89,7 +89,7 @@ public async Task> CreateNewEventAsync(string name, string descri string? imageUrl = null; if (image != null) { - imageUrl = await _blobService.UploadToBlobContainerAsync(name, image); + imageUrl = await _blobService.UploadToBlobContainerAsync(image); } var @event = new Event { @@ -187,7 +187,8 @@ public async Task> GetEventDetailsAsync(Guid categories, ticketTypes, ev.EventStatus, - address + address, + ev.ImageUrl ); return Result.Success(details); @@ -318,7 +319,7 @@ private static GetEventResponseDto MapEventToGetEventResponseDto(Event ev) var maximumPrice = new GetEventResponsePriceInfoDto(ttMaximumPrice.Price, ttMaximumPrice.Currency); return new GetEventResponseDto(ev.Id, ev.Name, ev.Description, ev.StartDate, ev.EndDate, ev.MinimumAge, - minimumPrice, maximumPrice, categories, ev.EventStatus, address); + minimumPrice, maximumPrice, categories, ev.EventStatus, address, ev.ImageUrl); } private Result CheckEventDates(DateTime startDate, DateTime endDate, IEnumerable ticketTypes, bool skipStartDateEvaluation = false) From 85457a027be23a24f0869b93a0a198b445066ee3 Mon Sep 17 00:00:00 2001 From: staszkiet Date: Sat, 14 Jun 2025 19:04:35 +0200 Subject: [PATCH 22/24] try catch block --- .../Common/Blob/Services/BlobService.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs index 9d79bfa..d56dd49 100644 --- a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs +++ b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs @@ -18,17 +18,23 @@ public BlobService(IConfiguration configuration) public async Task UploadToBlobContainerAsync(IFormFile image) { - var container = new BlobContainerClient(_connectionString, _containerName); - Guid id = Guid.NewGuid(); - string blobName = id.ToString(); - var blob = container.GetBlobClient(blobName); - var stream = new MemoryStream(); - await image.CopyToAsync(stream); - stream.Position = 0; - - - var response = await blob.UploadAsync(stream); - - return blob.Uri.ToString(); + try + { + var container = new BlobContainerClient(_connectionString, _containerName); + Guid id = Guid.NewGuid(); + string blobName = id.ToString(); + var blob = container.GetBlobClient(blobName); + var stream = new MemoryStream(); + await image.CopyToAsync(stream); + stream.Position = 0; + var response = await blob.UploadAsync(stream); + + return blob.Uri.ToString(); + } + catch (Exception e) + { + return null; + } + } } \ No newline at end of file From 3fd5c9c4bb65cb8910376a7e9159c94d5f6aa49f Mon Sep 17 00:00:00 2001 From: staszkiet Date: Sat, 14 Jun 2025 19:08:47 +0200 Subject: [PATCH 23/24] better try catch --- .../Common/Blob/Services/BlobService.cs | 28 +++++++------------ .../TickAPI/Events/Services/EventService.cs | 9 +++++- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs index d56dd49..b58a5c8 100644 --- a/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs +++ b/TickAPI/TickAPI/Common/Blob/Services/BlobService.cs @@ -18,23 +18,15 @@ public BlobService(IConfiguration configuration) public async Task UploadToBlobContainerAsync(IFormFile image) { - try - { - var container = new BlobContainerClient(_connectionString, _containerName); - Guid id = Guid.NewGuid(); - string blobName = id.ToString(); - var blob = container.GetBlobClient(blobName); - var stream = new MemoryStream(); - await image.CopyToAsync(stream); - stream.Position = 0; - var response = await blob.UploadAsync(stream); - - return blob.Uri.ToString(); - } - catch (Exception e) - { - return null; - } - + var container = new BlobContainerClient(_connectionString, _containerName); + Guid id = Guid.NewGuid(); + string blobName = id.ToString(); + var blob = container.GetBlobClient(blobName); + var stream = new MemoryStream(); + await image.CopyToAsync(stream); + stream.Position = 0; + var response = await blob.UploadAsync(stream); + + return blob.Uri.ToString(); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index 286ddce..546da79 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -89,7 +89,14 @@ public async Task> CreateNewEventAsync(string name, string descri string? imageUrl = null; if (image != null) { - imageUrl = await _blobService.UploadToBlobContainerAsync(image); + try + { + imageUrl = await _blobService.UploadToBlobContainerAsync(image); + } + catch (Exception e) + { + return Result.Failure(statusCode:500, e.Message); + } } var @event = new Event { From b8da76a65cc1343253d1f97a389d7735b98502c1 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Sat, 14 Jun 2025 19:47:16 +0200 Subject: [PATCH 24/24] resolved comment --- TickAPI/TickAPI/Tickets/Services/TicketService.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index 189e75e..ceaa7de 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -172,12 +172,7 @@ public async Task> GetTicketByIdAsync(Guid ticketId) { var ticketResult = await _ticketRepository.GetTicketWithDetailsByIdAsync(ticketId); - if (ticketResult.IsError) - { - return Result.PropagateError(ticketResult); - } - - return Result.Success(ticketResult.Value!); + return ticketResult; } public async Task> GetTicketTypeByIdAsync(Guid ticketTypeId)