From 9cd52129312d167fb7e340010d4aaa6e39b1b054 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Wed, 11 Jun 2025 17:18:10 +0200 Subject: [PATCH 01/10] 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 e033060eac94e2bda193ce77760d3f75772fc415 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 01:35:02 +0200 Subject: [PATCH 02/10] 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 03/10] 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 7c6c42f7d30004a2df24ab1de84049f9207e47d7 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Fri, 13 Jun 2025 19:50:11 +0200 Subject: [PATCH 04/10] 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 05/10] 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 06/10] 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 80c74c1f71352f58a65a47a1cdd4c015c050f78a Mon Sep 17 00:00:00 2001 From: kubapoke Date: Sat, 14 Jun 2025 02:05:14 +0200 Subject: [PATCH 07/10] 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 08/10] 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 09/10] 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 b8da76a65cc1343253d1f97a389d7735b98502c1 Mon Sep 17 00:00:00 2001 From: kubapoke Date: Sat, 14 Jun 2025 19:47:16 +0200 Subject: [PATCH 10/10] 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)