diff --git a/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs b/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs index 3ca5b1b..041241f 100644 --- a/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Controllers/EventsControllerTests.cs @@ -31,7 +31,7 @@ public async Task CreateEvent_WhenDataIsValid_ShouldReturnSuccess() uint? minimumAge = 18; const string email = "123@mail.com"; const EventStatus eventStatus = EventStatus.TicketsAvailable; - Guid id = Guid.NewGuid(); + Guid id = Guid.Parse("c5aa4979-af8c-4cf9-a827-b273317fbc70"); List categories = [ new CreateEventCategoryDto("concert"), @@ -576,5 +576,62 @@ public async Task GetEventsPaginationDetails_WhenOperationFails_ShouldReturnErro var objectResult = Assert.IsType(result.Result); Assert.Equal(statusCode, objectResult.StatusCode); Assert.Equal(errorMessage, objectResult.Value); -} + } + + [Fact] + public async Task GetEventDetails_WhenAllOperationsSucceed_ShouldReturnOkWithEventDetails() + { + // Arrange + var id = Guid.Parse("c5aa4979-af8c-4cf9-a827-b273317fbc70"); + var eventDetails = Utils.CreateSampleEventDetailsDto("Event"); + + var eventServiceMock = new Mock(); + var claimsServiceMock = new Mock(); + var organizerServiceMock = new Mock(); + + eventServiceMock + .Setup(m => m.GetEventDetailsAsync(id)) + .ReturnsAsync(Result.Success(eventDetails)); + + var sut = new EventsController(eventServiceMock.Object, claimsServiceMock.Object, organizerServiceMock.Object); + + // Act + var response = await sut.GetEventDetails(id); + + // Assert + var result = Assert.IsType>(response); + var okResult = Assert.IsType(result.Result); + Assert.Equal(StatusCodes.Status200OK, okResult.StatusCode); + + var returnedEventDetails = Assert.IsType(okResult.Value); + Assert.Equal(eventDetails, returnedEventDetails); + } + + [Fact] + public async Task GetEventDetails_WhenOperationFails_ShouldReturnErrorWithCorrectStatusCode() + { + // Arrange + var id = Guid.Parse("c5aa4979-af8c-4cf9-a827-b273317fbc70"); + const string errorMessage = "Failed to retrieve event details"; + const int statusCode = StatusCodes.Status500InternalServerError; + + var eventServiceMock = new Mock(); + var claimsServiceMock = new Mock(); + var organizerServiceMock = new Mock(); + + eventServiceMock + .Setup(m => m.GetEventDetailsAsync(id)) + .ReturnsAsync(Result.Failure(statusCode, errorMessage)); + + var sut = new EventsController(eventServiceMock.Object, claimsServiceMock.Object, organizerServiceMock.Object); + + // Act + var response = await sut.GetEventDetails(id); + + // Assert + var result = Assert.IsType>(response); + var objectResult = Assert.IsType(result.Result); + Assert.Equal(statusCode, objectResult.StatusCode); + Assert.Equal(errorMessage, objectResult.Value); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs index 0989a18..e7bea9c 100644 --- a/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs @@ -16,6 +16,7 @@ using TickAPI.Common.Time.Abstractions; using TickAPI.Events.DTOs.Response; using TickAPI.Events.Services; +using TickAPI.Tickets.Abstractions; using TickAPI.TicketTypes.DTOs.Request; using TickAPI.TicketTypes.Models; @@ -24,7 +25,6 @@ namespace TickAPI.Tests.Events.Services; public class EventServiceTests { [Fact] - public async Task CreateNewEventAsync_WhenEventDataIsValid_ShouldReturnNewEvent() { // Arrange @@ -109,7 +109,9 @@ public async Task CreateNewEventAsync_WhenEventDataIsValid_ShouldReturnNewEvent( var paginationServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + var ticketServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); @@ -176,12 +178,14 @@ public async Task CreateNewEventAsync_WhenEndDateIsBeforeStartDate_ShouldReturnB var dateTimeServiceMock = new Mock(); var categoryServiceMock = new Mock(); + + var paginationServiceMock = new Mock(); + var ticketServiceMock = new Mock(); - // Act - var paginationServiceMock = 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); + // Act var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); @@ -229,11 +233,14 @@ public async Task CreateNewEventAsync_WhenTicketTypeAvailabilityIsAfterEventsEnd var categoryServiceMock = new Mock(); - // Act + var paginationServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + var ticketServiceMock = new Mock(); + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + + // Act var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); // Assert @@ -280,11 +287,13 @@ public async Task CreateNewEventAsync_WhenStartDateIsBeforeNow_ShouldReturnBadRe var categoryServiceMock = new Mock(); - // Act var paginationServiceMock = new Mock(); - var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + var ticketServiceMock = new Mock(); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + // Act var res = await sut.CreateNewEventAsync(name, description, startDate, endDate, minimumAge, createAddress, categories, ticketTypes, eventStatus, organizerEmail); // Assert @@ -317,6 +326,7 @@ public async Task GetOrganizerEvents_WhenPaginationSucceeds_ShouldReturnPaginate var dateTimeServiceMock = new Mock(); var paginationServiceMock = new Mock(); var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); var paginatedEvents = new PaginatedData( organizer.Events.Take(pageSize).ToList(), @@ -350,7 +360,7 @@ public async Task GetOrganizerEvents_WhenPaginationSucceeds_ShouldReturnPaginate )); var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.GetOrganizerEventsAsync(organizer, page, pageSize); @@ -391,6 +401,7 @@ public async Task GetOrganizerEvents_WhenPaginationFails_ShouldPropagateError() var dateTimeServiceMock = new Mock(); var paginationServiceMock = new Mock(); var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); var organizerEvents = organizer.Events.AsQueryable(); eventRepositoryMock.Setup(p => p.GetEventsByOranizer(organizer)).Returns(organizerEvents); @@ -400,7 +411,7 @@ public async Task GetOrganizerEvents_WhenPaginationFails_ShouldPropagateError() .ReturnsAsync(Result>.Failure(StatusCodes.Status400BadRequest, "Invalid page number")); var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.GetOrganizerEventsAsync(organizer, page, pageSize); @@ -430,6 +441,7 @@ public async Task GetEventsAsync_WhenPaginationSucceeds_ShouldReturnPaginatedEve var dateTimeServiceMock = new Mock(); var paginationServiceMock = new Mock(); var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); var paginatedEvents = new PaginatedData( events.Take(pageSize).ToList(), @@ -463,7 +475,7 @@ public async Task GetEventsAsync_WhenPaginationSucceeds_ShouldReturnPaginatedEve )); var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.GetEventsAsync(page, pageSize); @@ -499,6 +511,7 @@ public async Task GetEventsAsync_WhenPaginationFails_ShouldPropagateError() var dateTimeServiceMock = new Mock(); var paginationServiceMock = new Mock(); var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); var eventsQueryable = events.AsQueryable(); eventRepositoryMock.Setup(p => p.GetEvents()).Returns(eventsQueryable); @@ -508,7 +521,7 @@ public async Task GetEventsAsync_WhenPaginationFails_ShouldPropagateError() .ReturnsAsync(Result>.Failure(StatusCodes.Status400BadRequest, "Invalid page number")); var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.GetEventsAsync(page, pageSize); @@ -537,6 +550,7 @@ public async Task GetEventsPaginationDetailsAsync_WhenSuccessful_ShouldReturnPag var dateTimeServiceMock = new Mock(); var paginationServiceMock = new Mock(); var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); var eventsQueryable = events.AsQueryable(); eventRepositoryMock.Setup(p => p.GetEvents()).Returns(eventsQueryable); @@ -547,7 +561,7 @@ public async Task GetEventsPaginationDetailsAsync_WhenSuccessful_ShouldReturnPag .ReturnsAsync(Result.Success(paginationDetails)); var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.GetEventsPaginationDetailsAsync(pageSize); @@ -575,6 +589,7 @@ public async Task GetEventsPaginationDetailsAsync_WhenFails_ShouldReturnError() var dateTimeServiceMock = new Mock(); var paginationServiceMock = new Mock(); var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); var eventsQueryable = events.AsQueryable(); eventRepositoryMock.Setup(p => p.GetEvents()).Returns(eventsQueryable); @@ -584,7 +599,7 @@ public async Task GetEventsPaginationDetailsAsync_WhenFails_ShouldReturnError() .ReturnsAsync(Result.Failure(StatusCodes.Status400BadRequest, "Invalid page size")); var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, - dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object); + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); // Act var result = await sut.GetEventsPaginationDetailsAsync(pageSize); @@ -594,4 +609,78 @@ public async Task GetEventsPaginationDetailsAsync_WhenFails_ShouldReturnError() Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); Assert.Equal("Invalid page size", result.ErrorMsg); } + + [Fact] + public async Task GetEventDetailsAsync_WhenSuccessful_ShouldReturnEventDetails() + { + // Arrange + var @event = Utils.CreateSampleEvent("Event"); + var expectedDetails = Utils.CreateSampleEventDetailsDto("Event"); + + var eventRepositoryMock = new Mock(); + var organizerServiceMock = new Mock(); + var addressServiceMock = new Mock(); + var dateTimeServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + eventRepositoryMock + .Setup(m => m.GetEventById(@event.Id)) + .Returns(Result.Success(@event)); + + ticketServiceMock + .Setup(m => m.GetNumberOfAvailableTicketsByType(It.IsAny())) + .Returns((TicketType input) => + Result.Success((uint)(input.Price / 10)) + ); + + var sut = new EventService(eventRepositoryMock.Object, organizerServiceMock.Object, addressServiceMock.Object, + dateTimeServiceMock.Object, paginationServiceMock.Object, categoryServiceMock.Object, ticketServiceMock.Object); + + // Act + var result = await sut.GetEventDetailsAsync(@event.Id); + + // Assert + Assert.True(result.IsSuccess); + Assert.Equal(expectedDetails.Id, result.Value!.Id); + Assert.Equal(expectedDetails.Description, result.Value!.Description); + Assert.Equal(expectedDetails.StartDate, result.Value!.StartDate); + Assert.Equal(expectedDetails.EndDate, result.Value!.EndDate); + Assert.Equal(expectedDetails.MinimumAge, result.Value!.MinimumAge); + Assert.True(expectedDetails.Categories.SequenceEqual(result.Value!.Categories)); + Assert.True(expectedDetails.TicketTypes.SequenceEqual(result.Value!.TicketTypes)); + Assert.Equal(expectedDetails.Status, result.Value!.Status); + Assert.Equal(expectedDetails.Address, result.Value!.Address); + } + + [Fact] + public async Task GetEventDetailsAsync_WhenFails_ShouldReturnEventError() + { + // Arrange + var @event = Utils.CreateSampleEvent("Event"); + + var eventRepositoryMock = new Mock(); + var organizerServiceMock = new Mock(); + var addressServiceMock = new Mock(); + var dateTimeServiceMock = new Mock(); + var paginationServiceMock = new Mock(); + var categoryServiceMock = new Mock(); + var ticketServiceMock = new Mock(); + + eventRepositoryMock + .Setup(m => m.GetEventById(@event.Id)) + .Returns(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); + + // Act + var result = await sut.GetEventDetailsAsync(@event.Id); + + // Assert + Assert.True(result.IsError); + Assert.Equal(StatusCodes.Status404NotFound, result.StatusCode); + Assert.Equal($"event with id {@event.Id} not found", result.ErrorMsg); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI.Tests/Events/Utils.cs b/TickAPI/TickAPI.Tests/Events/Utils.cs index 8e5c0c8..479568b 100644 --- a/TickAPI/TickAPI.Tests/Events/Utils.cs +++ b/TickAPI/TickAPI.Tests/Events/Utils.cs @@ -2,6 +2,7 @@ using TickAPI.Categories.Models; using TickAPI.Events.DTOs.Response; using TickAPI.Events.Models; +using TickAPI.TicketTypes.Models; namespace TickAPI.Tests.Events; @@ -11,7 +12,7 @@ public static Event CreateSampleEvent(string name) { return new Event { - Id = Guid.NewGuid(), + Id = Guid.Parse("c5aa4979-af8c-4cf9-a827-b273317fbc70"), Name = name, Description = $"Description of {name}", StartDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), @@ -27,6 +28,12 @@ public static Event CreateSampleEvent(string name) Street = "Main St", HouseNumber = 123, FlatNumber = null + }, + TicketTypes = new List + { + new TicketType {Id = Guid.Parse("b2ad06a4-aaff-4cfb-92af-07c971e9aa3b"), Description = "Description #1", Price = 100, Currency = "PLN", AvailableFrom = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)}, + new TicketType {Id = Guid.Parse("7ecfc61a-32d2-4124-a95c-cb5834a49990"), Description = "Description #2", Price = 300, Currency = "PLN", AvailableFrom = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)}, + new TicketType {Id = Guid.Parse("7be2ae57-2394-4854-bf11-9567ce7e0ab6"), Description = "Description #3", Price = 200, Currency = "PLN", AvailableFrom = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)}, } }; } @@ -34,12 +41,33 @@ public static Event CreateSampleEvent(string name) public static GetEventResponseDto CreateSampleEventResponseDto(string name) { return new GetEventResponseDto( + Guid.Parse("c5aa4979-af8c-4cf9-a827-b273317fbc70"), + name, + $"Description of {name}", + new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), + new DateTime(1970, 1, 2, 0, 0, 0, DateTimeKind.Utc), + 18, + 100, + 300, + [new GetEventResponseCategoryDto("Test")], + EventStatus.TicketsAvailable, + new GetEventResponseAddressDto("United States", "New York", "10001", "Main St", 123, null) + ); + } + + public static GetEventDetailsResponseDto CreateSampleEventDetailsDto(string name) + { + return new GetEventDetailsResponseDto( + Guid.Parse("c5aa4979-af8c-4cf9-a827-b273317fbc70"), name, $"Description of {name}", new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), new DateTime(1970, 1, 2, 0, 0, 0, DateTimeKind.Utc), 18, [new GetEventResponseCategoryDto("Test")], + [new GetEventDetailsResponseTicketTypeDto(Guid.Parse("b2ad06a4-aaff-4cfb-92af-07c971e9aa3b"), "Description #1", 100, "PLN", new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 10), + 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) ); diff --git a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs new file mode 100644 index 0000000..068a29f --- /dev/null +++ b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs @@ -0,0 +1,59 @@ +using Microsoft.AspNetCore.Http; +using Moq; +using TickAPI.Common.Results.Generic; +using TickAPI.Tickets.Abstractions; +using TickAPI.Tickets.Models; +using TickAPI.Tickets.Services; +using TickAPI.TicketTypes.Models; + +namespace TickAPI.Tests.Tickets.Services; + +public class TicketServiceTests +{ + [Fact] + public void GetNumberOfAvailableTicketsByType_AmountsAreCorrect_ShouldReturnCorrectNumberOfTickets() + { + // Arrange + var type = new TicketType { MaxCount = 30 }; + var ticketList = new List(new Ticket[10]); + + Mock ticketRepositoryMock = new Mock(); + + ticketRepositoryMock + .Setup(m => m.GetAllTicketsByTicketType(type)) + .Returns(ticketList.AsQueryable()); + + var sut = new TicketService(ticketRepositoryMock.Object); + + // Act + var result = sut.GetNumberOfAvailableTicketsByType(type); + + // Assert + Assert.True(result.IsSuccess); + Assert.Equal(20u, result.Value); + } + + [Fact] + public void GetNumberOfAvailableTicketsByType_WhenMoreTicketExistThanMaxCount_ShouldReturnError() + { + // Arrange + var type = new TicketType { MaxCount = 30 }; + var ticketList = new List(new Ticket[50]); + + Mock ticketRepositoryMock = new Mock(); + + ticketRepositoryMock + .Setup(m => m.GetAllTicketsByTicketType(type)) + .Returns(ticketList.AsQueryable()); + + var sut = new TicketService(ticketRepositoryMock.Object); + + // Act + var result = sut.GetNumberOfAvailableTicketsByType(type); + + // Assert + Assert.True(result.IsError); + Assert.Equal(StatusCodes.Status500InternalServerError, result.StatusCode); + Assert.Equal("The number of available tickets is negative.", result.ErrorMsg); + } +} \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs index 45c8e00..aa3a753 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventRepository.cs @@ -1,4 +1,5 @@ -using TickAPI.Events.Models; +using TickAPI.Common.Results.Generic; +using TickAPI.Events.Models; using TickAPI.Organizers.Models; namespace TickAPI.Events.Abstractions; @@ -8,4 +9,5 @@ public interface IEventRepository public Task AddNewEventAsync(Event @event); public IQueryable GetEvents(); public IQueryable GetEventsByOranizer(Organizer organizer); + public Result GetEventById(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 465defd..130788c 100644 --- a/TickAPI/TickAPI/Events/Abstractions/IEventService.cs +++ b/TickAPI/TickAPI/Events/Abstractions/IEventService.cs @@ -12,10 +12,11 @@ namespace TickAPI.Events.Abstractions; 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); + DateTime endDate, uint? minimumAge, CreateAddressDto createAddress, List categories, + List ticketTypes,EventStatus eventStatus, string organizerEmail); public Task>> GetOrganizerEventsAsync(Organizer organizer, int page, int pageSize); public Task> GetOrganizerEventsPaginationDetailsAsync(Organizer organizer, int pageSize); public Task>> GetEventsAsync(int page, int pageSize); public Task> GetEventsPaginationDetailsAsync(int pageSize); + public Task> GetEventDetailsAsync(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 2a5ac27..c27cbcf 100644 --- a/TickAPI/TickAPI/Events/Controllers/EventsController.cs +++ b/TickAPI/TickAPI/Events/Controllers/EventsController.cs @@ -125,4 +125,16 @@ public async Task> GetEventsPaginationDetails([F } return Ok(paginationDetailsResult.Value!); } + + [AuthorizeWithPolicy(AuthPolicies.VerifiedUserPolicy)] + [HttpGet("{id:guid}")] + public async Task> GetEventDetails([FromRoute] Guid id) + { + var eventDetailsResult = await _eventService.GetEventDetailsAsync(id); + if (eventDetailsResult.IsError) + { + return StatusCode(eventDetailsResult.StatusCode, eventDetailsResult.ErrorMsg); + } + return Ok(eventDetailsResult.Value!); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/DTOs/Response/CreateEventResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/CreateEventResponseDto.cs index a2a6340..a5bcdfb 100644 --- a/TickAPI/TickAPI/Events/DTOs/Response/CreateEventResponseDto.cs +++ b/TickAPI/TickAPI/Events/DTOs/Response/CreateEventResponseDto.cs @@ -1,6 +1,3 @@ namespace TickAPI.Events.DTOs.Response; -public class CreateEventResponseDto -{ - -} \ No newline at end of file +public record CreateEventResponseDto(); \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs new file mode 100644 index 0000000..c5ea721 --- /dev/null +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseDto.cs @@ -0,0 +1,16 @@ +using TickAPI.Events.Models; + +namespace TickAPI.Events.DTOs.Response; + +public record GetEventDetailsResponseDto( + Guid Id, + string Name, + string Description, + DateTime StartDate, + DateTime EndDate, + uint? MinimumAge, + List Categories, + List TicketTypes, + EventStatus Status, + GetEventResponseAddressDto Address +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseTicketTypeDto.cs b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseTicketTypeDto.cs new file mode 100644 index 0000000..379ba2f --- /dev/null +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventDetailsResponseTicketTypeDto.cs @@ -0,0 +1,10 @@ +namespace TickAPI.Events.DTOs.Response; + +public record GetEventDetailsResponseTicketTypeDto( + Guid Id, + string Description, + decimal Price, + string Currency, + DateTime AvailableFrom, + uint AmountAvailable +); \ 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 41d1698..28b42e4 100644 --- a/TickAPI/TickAPI/Events/DTOs/Response/GetEventResponseDto.cs +++ b/TickAPI/TickAPI/Events/DTOs/Response/GetEventResponseDto.cs @@ -3,12 +3,15 @@ namespace TickAPI.Events.DTOs.Response; public record GetEventResponseDto( + Guid Id, string Name, string Description, DateTime StartDate, DateTime EndDate, uint? MinimumAge, + decimal MinimumPrice, + decimal MaximumPrice, List Categories, EventStatus Status, - GetEventResponseAddressDto Addres + GetEventResponseAddressDto Address ); diff --git a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs index 40c05d7..391b613 100644 --- a/TickAPI/TickAPI/Events/Repositories/EventRepository.cs +++ b/TickAPI/TickAPI/Events/Repositories/EventRepository.cs @@ -1,4 +1,5 @@ -using TickAPI.Common.TickApiDbContext; +using TickAPI.Common.Results.Generic; +using TickAPI.Common.TickApiDbContext; using TickAPI.Events.Abstractions; using TickAPI.Events.Models; using TickAPI.Organizers.Models; @@ -29,4 +30,16 @@ public IQueryable GetEventsByOranizer(Organizer organizer) { return _tickApiDbContext.Events.Where(e => e.Organizer.Id == organizer.Id); } + + public Result GetEventById(Guid eventId) + { + var @event = _tickApiDbContext.Events.Find(eventId); + + if (@event == null) + { + return Result.Failure(StatusCodes.Status404NotFound, $"event with id {eventId} not found"); + } + + return Result.Success(@event); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Events/Services/EventService.cs b/TickAPI/TickAPI/Events/Services/EventService.cs index 117c7eb..92cbb80 100644 --- a/TickAPI/TickAPI/Events/Services/EventService.cs +++ b/TickAPI/TickAPI/Events/Services/EventService.cs @@ -12,6 +12,7 @@ using TickAPI.Events.DTOs.Response; using TickAPI.Organizers.Abstractions; using TickAPI.Organizers.Models; +using TickAPI.Tickets.Abstractions; using TickAPI.TicketTypes.DTOs.Request; using TickAPI.TicketTypes.Models; @@ -25,8 +26,9 @@ public class EventService : IEventService private readonly IDateTimeService _dateTimeService; private readonly IPaginationService _paginationService; private readonly ICategoryService _categoryService; + private readonly ITicketService _ticketService; - public EventService(IEventRepository eventRepository, IOrganizerService organizerService, IAddressService addressService, IDateTimeService dateTimeService, IPaginationService paginationService, ICategoryService categoryService) + public EventService(IEventRepository eventRepository, IOrganizerService organizerService, IAddressService addressService, IDateTimeService dateTimeService, IPaginationService paginationService, ICategoryService categoryService, ITicketService ticketService) { _eventRepository = eventRepository; _organizerService = organizerService; @@ -34,6 +36,7 @@ public EventService(IEventRepository eventRepository, IOrganizerService organize _dateTimeService = dateTimeService; _paginationService = paginationService; _categoryService = categoryService; + _ticketService = ticketService; } public async Task> CreateNewEventAsync(string name, string description, DateTime startDate, DateTime endDate, @@ -117,6 +120,45 @@ public async Task> GetEventsPaginationDetailsAsync(int return await _paginationService.GetPaginationDetailsAsync(events, pageSize); } + public async Task> GetEventDetailsAsync(Guid eventId) + { + var eventResult = _eventRepository.GetEventById(eventId); + + if (eventResult.IsError) + { + return Result.PropagateError(eventResult); + } + + var ev = eventResult.Value!; + + var categories = ev.Categories.Count > 0 + ? ev.Categories.Select((c) => new GetEventResponseCategoryDto(c.Name)).ToList() + : new List(); + + var ticketTypes = ev.TicketTypes.Count > 0 + ? ev.TicketTypes.Select((t) => new GetEventDetailsResponseTicketTypeDto(t.Id, t.Description, t.Price, + t.Currency, t.AvailableFrom, _ticketService.GetNumberOfAvailableTicketsByType(t).Value)).ToList() + : new List(); + + var address = new GetEventResponseAddressDto(ev.Address.Country, ev.Address.City, ev.Address.PostalCode, + ev.Address.Street, ev.Address.HouseNumber, ev.Address.FlatNumber); + + var details = new GetEventDetailsResponseDto( + ev.Id, + ev.Name, + ev.Description, + ev.StartDate, + ev.EndDate, + ev.MinimumAge, + categories, + ticketTypes, + ev.EventStatus, + address + ); + + return Result.Success(details); + } + private async Task>> GetPaginatedEventsAsync(IQueryable events, int page, int pageSize) { var paginatedEventsResult = await _paginationService.PaginateAsync(events, pageSize, page); @@ -134,6 +176,11 @@ private static GetEventResponseDto MapEventToGetEventResponseDto(Event ev) { var categories = ev.Categories.Count > 0 ? ev.Categories.Select((c) => new GetEventResponseCategoryDto(c.Name)).ToList() : new List(); var address = new GetEventResponseAddressDto(ev.Address.Country, ev.Address.City, ev.Address.PostalCode, ev.Address.Street, ev.Address.HouseNumber, ev.Address.FlatNumber); - return new GetEventResponseDto(ev.Name, ev.Description, ev.StartDate, ev.EndDate, ev.MinimumAge, categories, ev.EventStatus, address); + + var minimumPrice = ev.TicketTypes.Min(t => t.Price); + var maximumPrice = ev.TicketTypes.Max(t => t.Price); + + return new GetEventResponseDto(ev.Id, ev.Name, ev.Description, ev.StartDate, ev.EndDate, ev.MinimumAge, + minimumPrice, maximumPrice, categories, ev.EventStatus, address); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs index 0a1516a..d3220d8 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs @@ -1,6 +1,9 @@ -namespace TickAPI.Tickets.Abstractions; +using TickAPI.Tickets.Models; +using TickAPI.TicketTypes.Models; + +namespace TickAPI.Tickets.Abstractions; public interface ITicketRepository { - + public IQueryable GetAllTicketsByTicketType(TicketType ticketType); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs index 6afa5e7..85e9ac5 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -1,6 +1,9 @@ -namespace TickAPI.Tickets.Abstractions; +using TickAPI.Common.Results.Generic; +using TickAPI.TicketTypes.Models; + +namespace TickAPI.Tickets.Abstractions; public interface ITicketService { - + public Result GetNumberOfAvailableTicketsByType(TicketType ticketType); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index 0586b0f..42ade22 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -1,8 +1,21 @@ -using TickAPI.Tickets.Abstractions; +using TickAPI.Common.TickApiDbContext; +using TickAPI.Tickets.Abstractions; +using TickAPI.Tickets.Models; +using TickAPI.TicketTypes.Models; namespace TickAPI.Tickets.Repositories; public class TicketRepository : ITicketRepository { + private readonly TickApiDbContext _tickApiDbContext; + + public TicketRepository(TickApiDbContext tickApiDbContext) + { + _tickApiDbContext = tickApiDbContext; + } + public IQueryable GetAllTicketsByTicketType(TicketType ticketType) + { + return _tickApiDbContext.Tickets.Where(t => t.Type == ticketType); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index 2c00257..d8d7855 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -1,8 +1,31 @@ -using TickAPI.Tickets.Abstractions; +using TickAPI.Common.Results.Generic; +using TickAPI.Tickets.Abstractions; +using TickAPI.TicketTypes.Models; namespace TickAPI.Tickets.Services; public class TicketService : ITicketService { + private readonly ITicketRepository _ticketRepository; + + public TicketService(ITicketRepository ticketRepository) + { + _ticketRepository = ticketRepository; + } + // TODO: Update this method to also count tickets cached in Redis as unavailable + public Result GetNumberOfAvailableTicketsByType(TicketType ticketType) + { + var unavailableTickets = _ticketRepository.GetAllTicketsByTicketType(ticketType); + + var availableCount = ticketType.MaxCount - unavailableTickets.Count(); + + if (availableCount < 0) + { + return Result.Failure(StatusCodes.Status500InternalServerError, + "The number of available tickets is negative."); + } + + return Result.Success((uint)availableCount); + } } \ No newline at end of file