From 4758983a862993d5546ded20d0d12352a6da04a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Fri, 9 May 2025 15:24:01 +0200 Subject: [PATCH 1/8] Add qr service and scannin endpoint --- .../Common/QR/Services/QRCodeService.cs | 22 +++++++++++++++++++ TickAPI/TickAPI/TickAPI.csproj | 2 ++ .../Tickets/Abstractions/ITicketRepository.cs | 1 + .../Tickets/Abstractions/ITicketService.cs | 1 + .../Tickets/Controllers/TicketsController.cs | 11 ++++++++++ TickAPI/TickAPI/Tickets/Models/Ticket.cs | 1 + .../Tickets/Repositories/TicketRepository.cs | 15 +++++++++++++ .../TickAPI/Tickets/Services/TicketService.cs | 11 ++++++++++ 8 files changed, 64 insertions(+) create mode 100644 TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs diff --git a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs new file mode 100644 index 0000000..2ead564 --- /dev/null +++ b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Components.RenderTree; +using QRCoder; +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.IO; +using Microsoft.AspNetCore.Mvc; +using QRCoder; + +namespace TickAPI.Common.QR.Services; + +public class QRCodeService +{ + public byte[] GenerateQrCode(Guid ticketId) + { + var qrGenerator = new QRCodeGenerator(); + var qrData = qrGenerator.CreateQrCode(ticketId.ToString(), QRCodeGenerator.ECCLevel.Q); + var qrCode = new PngByteQRCode(qrData); + var qrCodeImage = qrCode.GetGraphic(20); + return qrCodeImage; + } +} \ No newline at end of file diff --git a/TickAPI/TickAPI/TickAPI.csproj b/TickAPI/TickAPI/TickAPI.csproj index a4ca3a8..fb79365 100644 --- a/TickAPI/TickAPI/TickAPI.csproj +++ b/TickAPI/TickAPI/TickAPI.csproj @@ -18,6 +18,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs index a09c57d..8b27c3c 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs @@ -10,4 +10,5 @@ public interface ITicketRepository public Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, string email); public IQueryable GetTicketsByEventId(Guid eventId); public IQueryable GetTicketsByCustomerEmail(string email); + public Task> MarkTicketAsUsed(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 37b6384..6f2c924 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -13,4 +13,5 @@ public Task>> GetTicketsForR int pageSize); public Task>> GetTicketsForCustomerAsync(string email, int page, int pageSize); + public Task> ScanTicket(Guid ticketGuid); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs index 78979fb..eec9221 100644 --- a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs +++ b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs @@ -65,4 +65,15 @@ public async Task>> GetTicke } return Ok(tickets.Value); } + + [HttpPost("/scan/{id:guid}")] + public async Task> ScanTicket(Guid id) + { + var res = await _ticketService.ScanTicket(id); + if (res.IsError) + { + return StatusCode(res.StatusCode, res.ErrorMsg); + } + return Ok(res.Value); + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Models/Ticket.cs b/TickAPI/TickAPI/Tickets/Models/Ticket.cs index 21f765f..5106f9f 100644 --- a/TickAPI/TickAPI/Tickets/Models/Ticket.cs +++ b/TickAPI/TickAPI/Tickets/Models/Ticket.cs @@ -11,4 +11,5 @@ public class Ticket public string NameOnTicket { get; set; } public string? Seats { get; set; } public bool ForResell { get; set; } + public bool Used {get; set;} } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index 2ec02c2..c2cb18c 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -49,4 +49,19 @@ public async Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, } return Result.Success(ticket); } + + public async Task> MarkTicketAsUsed(Guid id) + { + var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id); + + if (ticket != null) + { + ticket.Used = true; + await _tickApiDbContext.SaveChangesAsync(); // 🔄 This actually writes changes to the DB + return Result.Success(true); + } + + return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); + + } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index d003a6e..abf3ec1 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -89,4 +89,15 @@ public async Task> GetTicketDetailsAsync(Gui ); return Result.Success(ticketDetails); } + + public async Task> ScanTicket(Guid ticketGuid) + { + var res = await _ticketRepository.MarkTicketAsUsed(ticketGuid); + if (res.IsError) + { + return Result.PropagateError(res); + } + + return res; + } } \ No newline at end of file From 943de02b0965e66dade8842174864fcdc6aac7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Fri, 9 May 2025 15:37:06 +0200 Subject: [PATCH 2/8] Add url encoding instead of guid and add qrcode to ticketdetails --- .../Common/QR/Abstractions/IQRCodeService.cs | 6 ++++++ .../TickAPI/Common/QR/Services/QRCodeService.cs | 15 +++++---------- TickAPI/TickAPI/Program.cs | 3 +++ .../DTOs/Response/GetTicketDetailsResponseDto.cs | 3 ++- TickAPI/TickAPI/Tickets/Services/TicketService.cs | 12 +++++++++--- 5 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs diff --git a/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs b/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs new file mode 100644 index 0000000..5e04e95 --- /dev/null +++ b/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs @@ -0,0 +1,6 @@ +namespace TickAPI.Common.QR.Abstractions; + +public interface IQRCodeService +{ + public byte[] GenerateQrCode(Guid ticketId); +} \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs index 2ead564..f763cdf 100644 --- a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs +++ b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs @@ -1,20 +1,15 @@ -using Microsoft.AspNetCore.Components.RenderTree; -using QRCoder; -using System; -using System.Collections.Generic; -using System.Drawing.Imaging; -using System.IO; -using Microsoft.AspNetCore.Mvc; -using QRCoder; +using QRCoder; +using TickAPI.Common.QR.Abstractions; namespace TickAPI.Common.QR.Services; -public class QRCodeService +public class QRCodeService : IQRCodeService { public byte[] GenerateQrCode(Guid ticketId) { var qrGenerator = new QRCodeGenerator(); - var qrData = qrGenerator.CreateQrCode(ticketId.ToString(), QRCodeGenerator.ECCLevel.Q); + var url = "localhost:5124/scan/" + ticketId; + var qrData = qrGenerator.CreateQrCode(url, QRCodeGenerator.ECCLevel.Q); var qrCode = new PngByteQRCode(qrData); var qrCodeImage = qrCode.GetGraphic(20); return qrCodeImage; diff --git a/TickAPI/TickAPI/Program.cs b/TickAPI/TickAPI/Program.cs index 0a431a1..8a0ac0e 100644 --- a/TickAPI/TickAPI/Program.cs +++ b/TickAPI/TickAPI/Program.cs @@ -42,6 +42,8 @@ using TickAPI.Common.Payment.Abstractions; using TickAPI.Common.Payment.Health; using TickAPI.Common.Payment.Services; +using TickAPI.Common.QR.Abstractions; +using TickAPI.Common.QR.Services; // Builder constants const string allowClientPolicyName = "AllowClient"; @@ -126,6 +128,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/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs index 46032d5..17f4fc5 100644 --- a/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs +++ b/TickAPI/TickAPI/Tickets/DTOs/Response/GetTicketDetailsResponseDto.cs @@ -13,5 +13,6 @@ public record GetTicketDetailsResponseDto DateTime StartDate, DateTime EndDate, GetTicketDetailsAddressDto Address, - Guid eventId + Guid eventId, + string qrcode ); \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index abf3ec1..5a59451 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -1,5 +1,6 @@ using TickAPI.Common.Pagination.Abstractions; using TickAPI.Common.Pagination.Responses; +using TickAPI.Common.QR.Abstractions; using TickAPI.Common.Results.Generic; using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.DTOs.Response; @@ -11,11 +12,12 @@ public class TicketService : ITicketService { private readonly ITicketRepository _ticketRepository; private readonly IPaginationService _paginationService; - - public TicketService(ITicketRepository ticketRepository, IPaginationService paginationService) + private readonly IQRCodeService _qrCodeService; + public TicketService(ITicketRepository ticketRepository, IPaginationService paginationService, IQRCodeService qrCodeService) { _ticketRepository = ticketRepository; _paginationService = paginationService; + _qrCodeService = qrCodeService; } // TODO: Update this method to also count tickets cached in Redis as unavailable @@ -74,6 +76,9 @@ public async Task> GetTicketDetailsAsync(Gui 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 qrbytes = _qrCodeService.GenerateQrCode(ticketGuid); + var qrcode = Convert.ToBase64String(qrbytes); var ticketDetails = new GetTicketDetailsResponseDto ( ticket.NameOnTicket, @@ -85,7 +90,8 @@ public async Task> GetTicketDetailsAsync(Gui ticket.Type.Event.StartDate, ticket.Type.Event.EndDate, address, - ticket.Type.Event.Id + ticket.Type.Event.Id, + qrcode ); return Result.Success(ticketDetails); } From 12d579cd1e5875ff1d4fa1e6a301dd844cd22846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sat, 10 May 2025 17:13:20 +0200 Subject: [PATCH 3/8] Add retrieving urls --- .../TickAPI/Common/QR/Abstractions/IQRCodeService.cs | 2 +- TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs | 3 +-- .../TickAPI/Tickets/Abstractions/ITicketService.cs | 4 +++- .../TickAPI/Tickets/Controllers/TicketsController.cs | 10 ++++++---- TickAPI/TickAPI/Tickets/Services/TicketService.cs | 11 +++++++---- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs b/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs index 5e04e95..94b2690 100644 --- a/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs +++ b/TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs @@ -2,5 +2,5 @@ public interface IQRCodeService { - public byte[] GenerateQrCode(Guid ticketId); + public byte[] GenerateQrCode(string url); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs index f763cdf..1986f90 100644 --- a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs +++ b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs @@ -5,10 +5,9 @@ namespace TickAPI.Common.QR.Services; public class QRCodeService : IQRCodeService { - public byte[] GenerateQrCode(Guid ticketId) + public byte[] GenerateQrCode(string url) { var qrGenerator = new QRCodeGenerator(); - var url = "localhost:5124/scan/" + ticketId; var qrData = qrGenerator.CreateQrCode(url, QRCodeGenerator.ECCLevel.Q); var qrCode = new PngByteQRCode(qrData); var qrCodeImage = qrCode.GetGraphic(20); diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs index 76b1b06..abca918 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -9,10 +9,12 @@ namespace TickAPI.Tickets.Abstractions; public interface ITicketService { public Result GetNumberOfAvailableTicketsByType(TicketType ticketType); - public Task> GetTicketDetailsAsync(Guid ticketGuid, string email); public Task>> GetTicketsForResellAsync(Guid eventId, int page, int pageSize); public Task>> GetTicketsForCustomerAsync(string email, int page, int pageSize, TicketFiltersDto ? ticketFilters = null); public Task> ScanTicket(Guid ticketGuid); + + public Task> GetTicketDetailsAsync(Guid ticketGuid, string email, + string scanUrl); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs index 969a974..2473b96 100644 --- a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs +++ b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs @@ -31,11 +31,12 @@ public async Task> GetTicketDetails(Gu return emailResult.ToObjectResult(); } var email = emailResult.Value!; - var ticket = await _ticketService.GetTicketDetailsAsync(id, email); + string? scanTicketUrl = Url.Action("ScanTicket", "Tickets", new { id = id }, Request.Scheme); + var ticket = await _ticketService.GetTicketDetailsAsync(id, email, scanTicketUrl); return ticket.ToObjectResult(); } - [HttpGet("/for-resell")] + [HttpGet("for-resell")] public async Task>> GetTicketsForResell([FromQuery] Guid eventId, [FromQuery] int pageSize, [FromQuery] int page) { var result = await _ticketService.GetTicketsForResellAsync(eventId, page, pageSize); @@ -55,8 +56,8 @@ public async Task>> GetTicke return tickets.ToObjectResult(); } - [HttpPost("/scan/{id:guid}")] - public async Task> ScanTicket(Guid id) + [HttpGet("scan/{id:guid}")] + public async Task> ScanTicket([FromQuery] Guid id) { var res = await _ticketService.ScanTicket(id); if (res.IsError) @@ -65,4 +66,5 @@ public async Task> ScanTicket(Guid id) } return Ok(res.Value); } + } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index e6987c8..0da33d2 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -1,4 +1,5 @@ -using TickAPI.Common.Pagination.Abstractions; +using Azure.Core; +using TickAPI.Common.Pagination.Abstractions; using TickAPI.Common.Pagination.Responses; using TickAPI.Common.QR.Abstractions; using TickAPI.Common.Results.Generic; @@ -72,7 +73,7 @@ public async Task>> GetTicketsForC return Result>.Success(paginatedResult); } - public async Task> GetTicketDetailsAsync(Guid ticketGuid, string email) + public async Task> GetTicketDetailsAsync(Guid ticketGuid, string email, string scanUrl) { var ticketRes = await _ticketRepository.GetTicketWithDetailsByIdAndEmailAsync(ticketGuid, email); if (ticketRes.IsError) @@ -83,8 +84,8 @@ public async Task> GetTicketDetailsAsync(Gui 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 qrbytes = _qrCodeService.GenerateQrCode(ticketGuid); + + var qrbytes = _qrCodeService.GenerateQrCode(scanUrl); var qrcode = Convert.ToBase64String(qrbytes); var ticketDetails = new GetTicketDetailsResponseDto ( @@ -113,4 +114,6 @@ public async Task> ScanTicket(Guid ticketGuid) return res; } + + } \ No newline at end of file From 1cda709ccaa38fcfcc0de7d62f3e06d2a53a2b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sat, 10 May 2025 17:21:14 +0200 Subject: [PATCH 4/8] Handle scanning used tickets --- .../Tickets/Controllers/TicketsController.cs | 3 ++- .../Tickets/Repositories/TicketRepository.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs index 2473b96..f9f5512 100644 --- a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs +++ b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs @@ -5,6 +5,7 @@ using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.DTOs.Response; using TickAPI.Common.Pagination.Responses; +using TickAPI.Common.Results; using TickAPI.Tickets.DTOs.Request; namespace TickAPI.Tickets.Controllers; @@ -32,7 +33,7 @@ public async Task> GetTicketDetails(Gu } var email = emailResult.Value!; string? scanTicketUrl = Url.Action("ScanTicket", "Tickets", new { id = id }, Request.Scheme); - var ticket = await _ticketService.GetTicketDetailsAsync(id, email, scanTicketUrl); + var ticket = await _ticketService.GetTicketDetailsAsync(id, email, scanTicketUrl!); return ticket.ToObjectResult(); } diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index c2cb18c..1180158 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -53,15 +53,12 @@ public async Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, public async Task> MarkTicketAsUsed(Guid id) { var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id); - - if (ticket != null) + if (ticket == null || ticket.Used) { - ticket.Used = true; - await _tickApiDbContext.SaveChangesAsync(); // 🔄 This actually writes changes to the DB - return Result.Success(true); + return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); } - - return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); - + ticket.Used = true; + await _tickApiDbContext.SaveChangesAsync(); + return Result.Success(true); } } \ No newline at end of file From 102ff0689ec8685ae71de3661a65a3ff1a9065eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sat, 10 May 2025 17:39:23 +0200 Subject: [PATCH 5/8] Add test --- .../Tickets/Services/TicketServiceTests.cs | 64 +++++++++++++++---- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs index 389f7a4..8b46ba2 100644 --- a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs @@ -4,6 +4,8 @@ using TickAPI.Addresses.Models; using TickAPI.Common.Pagination.Abstractions; using TickAPI.Common.Pagination.Responses; +using TickAPI.Common.QR.Abstractions; +using TickAPI.Common.Results; using TickAPI.Common.Results.Generic; using TickAPI.Customers.Models; using TickAPI.Events.Models; @@ -27,12 +29,13 @@ public void GetNumberOfAvailableTicketsByType_AmountsAreCorrect_ShouldReturnCorr var ticketRepositoryMock = new Mock(); var paginationServiceMock = new Mock(); + var qrServiceMock = new Mock(); ticketRepositoryMock .Setup(m => m.GetAllTicketsByTicketType(type)) .Returns(ticketList.AsQueryable()); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = sut.GetNumberOfAvailableTicketsByType(type); @@ -51,12 +54,13 @@ public void GetNumberOfAvailableTicketsByType_WhenMoreTicketExistThanMaxCount_Sh var ticketRepositoryMock = new Mock(); var paginationServiceMock = new Mock(); + var qrServiceMock = new Mock(); ticketRepositoryMock .Setup(m => m.GetAllTicketsByTicketType(type)) .Returns(ticketList.AsQueryable()); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = sut.GetNumberOfAvailableTicketsByType(type); @@ -150,7 +154,9 @@ public async Task GetTicketsForResellAsync_WhenDataIsValid_ShouldReturnSuccess() It.IsAny>())) .Returns(mappedData); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var qrServiceMock = new Mock(); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize); @@ -226,8 +232,9 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForResell_ShouldReturnEm paginatedData, It.IsAny>())) .Returns(mappedData); + var qrServiceMock = new Mock(); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize); @@ -270,7 +277,9 @@ public async Task GetTicketsForResellAsync_WhenPaginationFails_ShouldPropagateEr paginationServiceMock.Setup(p => p.PaginateAsync(It.IsAny>(), pageSize, page)) .ReturnsAsync(Result>.Failure(statusCode, errorMsg)); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var qrServiceMock = new Mock(); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize); @@ -321,8 +330,9 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForEvent_ShouldReturnEmp paginatedData, It.IsAny>())) .Returns(mappedData); + var qrServiceMock = new Mock(); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize); @@ -371,7 +381,7 @@ public async Task GetTicketDetailsAsync_WhenTicketExistsForTheUser_ShouldReturnT }, }; string email = "123@123.com"; - + string scanurl = "http://localhost"; Mock ticketRepositoryMock = new Mock(); var paginationServiceMock = new Mock(); @@ -379,11 +389,14 @@ public async Task GetTicketDetailsAsync_WhenTicketExistsForTheUser_ShouldReturnT ticketRepositoryMock.Setup(m => m.GetTicketWithDetailsByIdAndEmailAsync(ticket.Id, email)) .ReturnsAsync(Result.Success(ticket)); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var qrServiceMock = new Mock(); + qrServiceMock.Setup(m => m.GenerateQrCode(scanurl)).Returns([]); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act - var res = await sut.GetTicketDetailsAsync(ticket.Id, email); + var res = await sut.GetTicketDetailsAsync(ticket.Id, email, scanurl); // Assert @@ -415,18 +428,20 @@ public async Task GetTicketDetailsAsync_WhenTicketDoesNotExistForTheUser_ShouldR Guid ticketId = Guid.NewGuid(); string email = "123@123.com"; + string scanUrl = "http://localhost"; 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 qrServiceMock = new Mock(); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act - var res = await sut.GetTicketDetailsAsync(ticketId, email); + var res = await sut.GetTicketDetailsAsync(ticketId, email, scanUrl); // Assert @@ -505,7 +520,10 @@ public async Task GetTicketsForCustomerAsync_WithValidInput_ReturnsSuccessResult paginationServiceMock.Setup(p => p.MapData(paginatedData, It.IsAny>())) .Returns(mappedPaginatedData); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + + var qrServiceMock = new Mock(); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = await sut.GetTicketsForCustomerAsync(email, page, pageSize); @@ -543,7 +561,9 @@ public async Task GetTicketsForCustomerAsync_WhenUserHasNoTickets_ReturnsEmptyPa paginationServiceMock.Setup(p => p.MapData(emptyPaginatedData, It.IsAny>())) .Returns(mappedEmptyPaginatedData); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object); + var qrServiceMock = new Mock(); + + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var result = await sut.GetTicketsForCustomerAsync(email, page, pageSize); @@ -552,4 +572,22 @@ public async Task GetTicketsForCustomerAsync_WhenUserHasNoTickets_ReturnsEmptyPa Assert.True(result.IsSuccess); Assert.Empty(result.Value!.Data); } + + [Fact] + public async Task ScanTicket_WhenScanningSuccesful_ShouldReturnSuccess() + { + // Arrange + var guid = Guid.NewGuid(); + var ticketRepositoryMock = new Mock(); + ticketRepositoryMock.Setup(m => m.MarkTicketAsUsed(guid)).ReturnsAsync(Result.Success(true)); + var paginationServiceMock = new Mock(); + var qrServiceMock = new Mock(); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); + + // Act + var res = await sut.ScanTicket(guid); + + // Assert + Assert.True(res.IsSuccess); + } } \ No newline at end of file From 5f6caeeb7ec11f512d2a4983d4c875b015d60127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sat, 10 May 2025 17:42:02 +0200 Subject: [PATCH 6/8] new spacing --- TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs | 2 +- TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs index 8b46ba2..5a56bc2 100644 --- a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs @@ -582,7 +582,7 @@ public async Task ScanTicket_WhenScanningSuccesful_ShouldReturnSuccess() ticketRepositoryMock.Setup(m => m.MarkTicketAsUsed(guid)).ReturnsAsync(Result.Success(true)); var paginationServiceMock = new Mock(); var qrServiceMock = new Mock(); - var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); + var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); // Act var res = await sut.ScanTicket(guid); diff --git a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs index 1986f90..9d5f5c1 100644 --- a/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs +++ b/TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs @@ -10,7 +10,7 @@ public byte[] GenerateQrCode(string url) var qrGenerator = new QRCodeGenerator(); var qrData = qrGenerator.CreateQrCode(url, QRCodeGenerator.ECCLevel.Q); var qrCode = new PngByteQRCode(qrData); - var qrCodeImage = qrCode.GetGraphic(20); + var qrCodeImage = qrCode.GetGraphic(20); return qrCodeImage; } } \ No newline at end of file From c6a9a3f69dd1f7c880d5b32f00cbbc7b909afa33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sun, 11 May 2025 12:30:01 +0200 Subject: [PATCH 7/8] Trzcinskik comments --- .../Tickets/Services/TicketServiceTests.cs | 2 +- .../TickAPI/Tickets/Abstractions/ITicketRepository.cs | 5 +++-- .../TickAPI/Tickets/Abstractions/ITicketService.cs | 4 ++-- .../TickAPI/Tickets/Controllers/TicketsController.cs | 8 ++------ .../TickAPI/Tickets/Repositories/TicketRepository.cs | 11 ++++++++--- TickAPI/TickAPI/Tickets/Services/TicketService.cs | 8 ++------ 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs index 5a56bc2..331853f 100644 --- a/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs @@ -579,7 +579,7 @@ public async Task ScanTicket_WhenScanningSuccesful_ShouldReturnSuccess() // Arrange var guid = Guid.NewGuid(); var ticketRepositoryMock = new Mock(); - ticketRepositoryMock.Setup(m => m.MarkTicketAsUsed(guid)).ReturnsAsync(Result.Success(true)); + ticketRepositoryMock.Setup(m => m.MarkTicketAsUsed(guid)).ReturnsAsync(Result.Success()); var paginationServiceMock = new Mock(); var qrServiceMock = new Mock(); var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object); diff --git a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs index 8b27c3c..6290950 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs @@ -1,4 +1,5 @@ -using TickAPI.Common.Results.Generic; +using TickAPI.Common.Results; +using TickAPI.Common.Results.Generic; using TickAPI.Tickets.Models; using TickAPI.TicketTypes.Models; @@ -10,5 +11,5 @@ public interface ITicketRepository public Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, string email); public IQueryable GetTicketsByEventId(Guid eventId); public IQueryable GetTicketsByCustomerEmail(string email); - public Task> MarkTicketAsUsed(Guid id); + public Task MarkTicketAsUsed(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 abca918..c722a8c 100644 --- a/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs +++ b/TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs @@ -1,4 +1,5 @@ using TickAPI.Common.Pagination.Responses; +using TickAPI.Common.Results; using TickAPI.Common.Results.Generic; using TickAPI.Tickets.DTOs.Request; using TickAPI.Tickets.DTOs.Response; @@ -13,8 +14,7 @@ public Task>> GetTicketsForR int pageSize); public Task>> GetTicketsForCustomerAsync(string email, int page, int pageSize, TicketFiltersDto ? ticketFilters = null); - public Task> ScanTicket(Guid ticketGuid); - + public Task ScanTicket(Guid ticketGuid); public Task> GetTicketDetailsAsync(Guid ticketGuid, string email, string scanUrl); } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs index f9f5512..77b45e2 100644 --- a/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs +++ b/TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs @@ -58,14 +58,10 @@ public async Task>> GetTicke } [HttpGet("scan/{id:guid}")] - public async Task> ScanTicket([FromQuery] Guid id) + public async Task> ScanTicket(Guid id) { var res = await _ticketService.ScanTicket(id); - if (res.IsError) - { - return StatusCode(res.StatusCode, res.ErrorMsg); - } - return Ok(res.Value); + return res.ToObjectResult(); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index 1180158..fe4c5f1 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; using TickAPI.Common.Results.Generic; using TickAPI.Common.TickApiDbContext; using TickAPI.Tickets.Abstractions; @@ -50,15 +51,19 @@ public async Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, return Result.Success(ticket); } - public async Task> MarkTicketAsUsed(Guid id) + public async Task MarkTicketAsUsed(Guid id) { var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id); if (ticket == null || ticket.Used) { - return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); + return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); + } + if (ticket.Used) + { + return Result.Failure(StatusCodes.Status400BadRequest, "Ticket already used"); } ticket.Used = true; await _tickApiDbContext.SaveChangesAsync(); - return Result.Success(true); + return Result.Success(); } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Tickets/Services/TicketService.cs b/TickAPI/TickAPI/Tickets/Services/TicketService.cs index 0da33d2..27b60fd 100644 --- a/TickAPI/TickAPI/Tickets/Services/TicketService.cs +++ b/TickAPI/TickAPI/Tickets/Services/TicketService.cs @@ -2,6 +2,7 @@ using TickAPI.Common.Pagination.Abstractions; using TickAPI.Common.Pagination.Responses; using TickAPI.Common.QR.Abstractions; +using TickAPI.Common.Results; using TickAPI.Common.Results.Generic; using TickAPI.Tickets.Abstractions; using TickAPI.Tickets.DTOs.Request; @@ -104,14 +105,9 @@ public async Task> GetTicketDetailsAsync(Gui return Result.Success(ticketDetails); } - public async Task> ScanTicket(Guid ticketGuid) + public async Task ScanTicket(Guid ticketGuid) { var res = await _ticketRepository.MarkTicketAsUsed(ticketGuid); - if (res.IsError) - { - return Result.PropagateError(res); - } - return res; } From af70c9e9e8d18afdeb39950534b7014a48e7d613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw?= <62651497+staszkiet@users.noreply.github.com> Date: Sun, 11 May 2025 13:45:41 +0200 Subject: [PATCH 8/8] if chang --- TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs index fe4c5f1..e5abb5e 100644 --- a/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs +++ b/TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs @@ -54,7 +54,7 @@ public async Task> GetTicketWithDetailsByIdAndEmailAsync(Guid id, public async Task MarkTicketAsUsed(Guid id) { var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id); - if (ticket == null || ticket.Used) + if (ticket == null) { return Result.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist"); }