diff --git a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs index dd1a7bf..29c7a2d 100644 --- a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs @@ -1,8 +1,13 @@ using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Moq; +using TickAPI.Addresses.Models; using TickAPI.Common.Pagination.Abstractions; using TickAPI.Common.Pagination.Responses; using TickAPI.Common.Results.Generic; +using TickAPI.Customers.Models; +using TickAPI.Events.Models; +using TickAPI.Organizers.Models; using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.DTOs.Response; using TickAPI.Tickets.Models; @@ -326,4 +331,107 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForEvent_ShouldReturnEmp Assert.True(result.IsSuccess); Assert.Empty(result.Value!.Data); } + + [Fact] + public async Task GetTicketDetailsAsync_WhenTicketExistsForTheUser_ShouldReturnTicketDetails() + { + + // Arrange + var eventGuid = Guid.NewGuid(); + var ticket = new Ticket + { + Id = Guid.NewGuid(), + ForResell = false, + NameOnTicket = "NameOnTicket", + Seats = null, + Type = new TicketType + { + Id = eventGuid, + Currency = "USD", + Price = 20, + Event = new Event + { + Name = "EventName", + StartDate = new DateTime(2025, 10, 10), + EndDate = new DateTime(2025, 10, 20), + Organizer = new Organizer + { + DisplayName = "organizerName", + }, + Address = new Address + { + City = "Warsaw", + Country = "Poland", + PostalCode = "12345", + FlatNumber = null, + HouseNumber = null, + Street = "Street", + } + } + }, + }; + string email = "123@123.com"; + + Mock ticketRepositoryMock = new Mock(); + + var paginationServiceMock = new Mock(); + + ticketRepositoryMock.Setup(m => m.GetTicketWithDetailsByIdAndEmailAsync(ticket.Id, email)) + .ReturnsAsync(Result.Success(ticket)); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + + // Act + + var res = await sut.GetTicketDetailsAsync(ticket.Id, email); + + // Assert + + Assert.True(res.IsSuccess); + var details = res.Value; + Assert.NotNull(details); + + Assert.Equal(ticket.NameOnTicket, details.NameOnTicket); + Assert.Equal(ticket.Seats, details.Seats); + Assert.Equal(ticket.Type.Currency, details.Currency); + Assert.Equal(ticket.Type.Price, details.Price); + Assert.Equal(ticket.Type.Event.StartDate, details.StartDate); + Assert.Equal(ticket.Type.Event.EndDate, details.EndDate); + Assert.Equal(ticket.Type.Event.Organizer.DisplayName, details.OrganizerName); + Assert.Equal(ticket.Type.Event.Address.Street, details.Address.Street); + Assert.Equal(ticket.Type.Event.Address.HouseNumber, details.Address.HouseNumber); + Assert.Equal(ticket.Type.Event.Address.FlatNumber, details.Address.FlatNumber); + Assert.Equal(ticket.Type.Event.Address.PostalCode, details.Address.PostalCode); + Assert.Equal(ticket.Type.Event.Address.City, details.Address.City); + Assert.Equal(ticket.Type.Event.Address.Country, details.Address.Country); + + } + + [Fact] + public async Task GetTicketDetailsAsync_WhenTicketDoesNotExistForTheUser_ShouldReturnError() + { + + // Arrange + + Guid ticketId = Guid.NewGuid(); + string email = "123@123.com"; + + Mock ticketRepositoryMock = new Mock(); + ticketRepositoryMock.Setup(m => m.GetTicketWithDetailsByIdAndEmailAsync(ticketId, email)). + ReturnsAsync(Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist " + + "for this user")); + var paginationServiceMock = new Mock(); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + + // Act + + var res = await sut.GetTicketDetailsAsync(ticketId, email); + + // Assert + + Assert.False(res.IsSuccess); + Assert.Equal(StatusCodes.Status404NotFound, res.StatusCode); + Assert.Equal("Ticket with this id doesn't exist for this user", res.ErrorMsg); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs index 8ec26d6..ed24346 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs @@ -1,4 +1,5 @@ -using TickAPI.Tickets.Models; +using TickAPI.Common.Results.Generic; +using TickAPI.Tickets.Models; using TickAPI.TicketTypes.Models; namespace TickAPI.Tickets.Abstractions; @@ -6,5 +7,6 @@ namespace TickAPI.Tickets.Abstractions; public interface ITicketRepository { public IQueryable GetAllTicketsByTicketType(TicketType ticketType); + public Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, string email); public IQueryable GetTicketsByEventId(Guid eventId); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs index e6d0d55..f705dce 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -1,4 +1,4 @@ -using TickAPI.Common.Pagination.Responses; +using TickAPI.Common.Pagination.Responses; using TickAPI.Common.Results.Generic; using TickAPI.Tickets.DTOs.Response; using TickAPI.TicketTypes.Models; @@ -6,7 +6,9 @@ namespace TickAPI.Tickets.Abstractions; public interface ITicketService -{ - Result GetNumberOfAvailableTicketsByType(TicketType ticketType); - Task>> GetTicketsForResellAsync(Guid eventId, int page, int pageSize); +{ + public Result GetNumberOfAvailableTicketsByType(TicketType ticketType); + public Task> GetTicketDetailsAsync(Guid ticketGuid, string email); + public Task>> GetTicketsForResellAsync(Guid eventId, int page, + int pageSize); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs index 005ac16..672368c 100644 --- a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs +++ b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs @@ -1,7 +1,10 @@ using Microsoft.AspNetCore.Mvc; -using TickAPI.Common.Pagination.Responses; +using TickAPI.Common.Auth.Attributes; +using TickAPI.Common.Auth.Enums; +using TickAPI.Common.Claims.Abstractions; using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.DTOs.Response; +using TickAPI.Common.Pagination.Responses; namespace TickAPI.Tickets.Controllers; @@ -9,12 +12,31 @@ namespace TickAPI.Tickets.Controllers; [Route("api/[controller]")] public class TicketsController : ControllerBase { - private readonly ITicketService _ticketService; - - public TicketsController(ITicketService ticketService) + private readonly IClaimsService _claimsService; + private readonly ITicketService _ticketService; + public TicketsController(IClaimsService claimsService, ITicketService ticketService) { + _claimsService = claimsService; _ticketService = ticketService; } + + [AuthorizeWithPolicy(AuthPolicies.CustomerPolicy)] + [HttpGet("{id:guid}")] + public async Task> GetTicketDetails(Guid id) + { + var emailResult = _claimsService.GetEmailFromClaims(User.Claims); + if (emailResult.IsError) + { + return StatusCode(emailResult.StatusCode, emailResult.ErrorMsg); + } + var email = emailResult.Value!; + var ticket = await _ticketService.GetTicketDetailsAsync(id, email); + if (ticket.IsError) + { + return StatusCode(ticket.StatusCode, ticket.ErrorMsg); + } + return Ok(ticket.Value); + } [HttpGet("/for-resell")] public async Task>> GetTicketsForResell([FromQuery] Guid eventId, [FromQuery] int pageSize, [FromQuery] int page) diff --git a/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsAddressDto.cs b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsAddressDto.cs new file mode 100644 index 0000000..c6dd05c --- /dev/null +++ b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsAddressDto.cs @@ -0,0 +1,10 @@ +namespace TickAPI.Tickets.DTOs.Response; + +public record GetTicketDetailsAddressDto( + string Country, + string City, + string PostalCode, + string? Street, + uint? HouseNumber, + uint? FlatNumber +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs new file mode 100644 index 0000000..46032d5 --- /dev/null +++ b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs @@ -0,0 +1,17 @@ +using TickAPI.Addresses.Models; + +namespace TickAPI.Tickets.DTOs.Response; + +public record GetTicketDetailsResponseDto +( + string NameOnTicket, + string? Seats, + decimal Price, + string Currency, + string EventName, + string OrganizerName, + DateTime StartDate, + DateTime EndDate, + GetTicketDetailsAddressDto Address, + Guid eventId +); \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index c85566d..c5a6a0d 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using TickAPI.Common.Results.Generic; using TickAPI.Common.TickApiDbContext; using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.Models; @@ -27,4 +28,16 @@ public IQueryable GetTicketsByEventId(Guid eventId) .Include(t => t.Type.Event) .Where(t => t.Type.Event.Id == eventId); } + + public async Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, string email) + { + 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) + .Where(t => (t.Id == id && t.Owner.Email == email)).FirstOrDefaultAsync(); + if (ticket == null) + { + return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); + } + return Result.Success(ticket); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index 4b5e7b6..f6c77ac 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -47,4 +47,31 @@ public async Task>> GetTicke t => new GetTicketForResellResponseDto(t.Id, t.Type.Price, t.Type.Currency, t.Type.Description, t.Seats)); return Result>.Success(paginatedResult); } + + public async Task> GetTicketDetailsAsync(Guid ticketGuid, string email) + { + var ticketRes = await _ticketRepository.GetTicketWithDetailsByIdAndEmailAsync(ticketGuid, email); + if (ticketRes.IsError) + { + return Result.PropagateError(ticketRes); + } + var ticket = ticketRes.Value!; + var ev = ticket.Type.Event; + var address = new GetTicketDetailsAddressDto(ev.Address.Country, ev.Address.City, ev.Address.PostalCode, + ev.Address.Street, ev.Address.HouseNumber, ev.Address.FlatNumber); + var ticketDetails = new GetTicketDetailsResponseDto + ( + ticket.NameOnTicket, + ticket.Seats, + ticket.Type.Price, + ticket.Type.Currency, + ticket.Type.Event.Name, + ticket.Type.Event.Organizer.DisplayName, + ticket.Type.Event.StartDate, + ticket.Type.Event.EndDate, + address, + ticket.Type.Event.Id + ); + return Result.Success(ticketDetails); + } } \ No newline at end of file