Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 51 additions & 13 deletions TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,12 +29,13 @@ public void GetNumberOfAvailableTicketsByType_AmountsAreCorrect_ShouldReturnCorr

var ticketRepositoryMock = new Mock<ITicketRepository>();
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();

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);
Expand All @@ -51,12 +54,13 @@ public void GetNumberOfAvailableTicketsByType_WhenMoreTicketExistThanMaxCount_Sh

var ticketRepositoryMock = new Mock<ITicketRepository>();
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();

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);
Expand Down Expand Up @@ -150,7 +154,9 @@ public async Task GetTicketsForResellAsync_WhenDataIsValid_ShouldReturnSuccess()
It.IsAny<Func<Ticket, GetTicketForResellResponseDto>>()))
.Returns(mappedData);

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object);
var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize);
Expand Down Expand Up @@ -226,8 +232,9 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForResell_ShouldReturnEm
paginatedData,
It.IsAny<Func<Ticket, GetTicketForResellResponseDto>>()))
.Returns(mappedData);
var qrServiceMock = new Mock<IQRCodeService>();

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);
Expand Down Expand Up @@ -270,7 +277,9 @@ public async Task GetTicketsForResellAsync_WhenPaginationFails_ShouldPropagateEr
paginationServiceMock.Setup(p => p.PaginateAsync(It.IsAny<IQueryable<Ticket>>(), pageSize, page))
.ReturnsAsync(Result<PaginatedData<Ticket>>.Failure(statusCode, errorMsg));

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object);
var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize);
Expand Down Expand Up @@ -321,8 +330,9 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForEvent_ShouldReturnEmp
paginatedData,
It.IsAny<Func<Ticket, GetTicketForResellResponseDto>>()))
.Returns(mappedData);
var qrServiceMock = new Mock<IQRCodeService>();

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);
Expand Down Expand Up @@ -371,19 +381,22 @@ public async Task GetTicketDetailsAsync_WhenTicketExistsForTheUser_ShouldReturnT
},
};
string email = "123@123.com";

string scanurl = "http://localhost";
Mock<ITicketRepository> ticketRepositoryMock = new Mock<ITicketRepository>();

var paginationServiceMock = new Mock<IPaginationService>();

ticketRepositoryMock.Setup(m => m.GetTicketWithDetailsByIdAndEmailAsync(ticket.Id, email))
.ReturnsAsync(Result<Ticket>.Success(ticket));

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object);
var qrServiceMock = new Mock<IQRCodeService>();
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

Expand Down Expand Up @@ -415,18 +428,20 @@ public async Task GetTicketDetailsAsync_WhenTicketDoesNotExistForTheUser_ShouldR

Guid ticketId = Guid.NewGuid();
string email = "123@123.com";
string scanUrl = "http://localhost";

Mock<ITicketRepository> ticketRepositoryMock = new Mock<ITicketRepository>();
ticketRepositoryMock.Setup(m => m.GetTicketWithDetailsByIdAndEmailAsync(ticketId, email)).
ReturnsAsync(Result<Ticket>.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist " +
"for this user"));
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();

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

Expand Down Expand Up @@ -505,7 +520,10 @@ public async Task GetTicketsForCustomerAsync_WithValidInput_ReturnsSuccessResult

paginationServiceMock.Setup(p => p.MapData(paginatedData, It.IsAny<Func<Ticket, GetTicketForCustomerDto>>()))
.Returns(mappedPaginatedData);
var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object);

var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForCustomerAsync(email, page, pageSize);
Expand Down Expand Up @@ -543,7 +561,9 @@ public async Task GetTicketsForCustomerAsync_WhenUserHasNoTickets_ReturnsEmptyPa
paginationServiceMock.Setup(p => p.MapData(emptyPaginatedData, It.IsAny<Func<Ticket, GetTicketForCustomerDto>>()))
.Returns(mappedEmptyPaginatedData);

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object);
var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForCustomerAsync(email, page, pageSize);
Expand All @@ -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<ITicketRepository>();
ticketRepositoryMock.Setup(m => m.MarkTicketAsUsed(guid)).ReturnsAsync(Result.Success());
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();
var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var res = await sut.ScanTicket(guid);

// Assert
Assert.True(res.IsSuccess);
}
}
6 changes: 6 additions & 0 deletions TickAPI/TickAPI/Common/QR/Abstractions/IQRCodeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TickAPI.Common.QR.Abstractions;

public interface IQRCodeService
{
public byte[] GenerateQrCode(string url);
}
16 changes: 16 additions & 0 deletions TickAPI/TickAPI/Common/QR/Services/QRCodeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using QRCoder;
using TickAPI.Common.QR.Abstractions;

namespace TickAPI.Common.QR.Services;

public class QRCodeService : IQRCodeService
{
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);
return qrCodeImage;
}
}
3 changes: 3 additions & 0 deletions TickAPI/TickAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -126,6 +128,7 @@
builder.Services.AddScoped<IRedisService, RedisService>();
builder.Services.AddScoped<IMailService, MailService>();
builder.Services.AddScoped<IPaymentGatewayService, PaymentGatewayService>();
builder.Services.AddScoped<IQRCodeService, QRCodeService>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
Expand Down
2 changes: 2 additions & 0 deletions TickAPI/TickAPI/TickAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
<PackageReference Include="QRcoder" Version="1.6.0" />
<PackageReference Include="QRCoder-ImageSharp" Version="0.10.0" />
<PackageReference Include="SendGrid" Version="9.29.3" />
<PackageReference Include="SendGrid.Extensions.DependencyInjection" Version="1.0.1" />
<PackageReference Include="StackExchange.Redis" Version="2.8.31" />
Expand Down
4 changes: 3 additions & 1 deletion TickAPI/TickAPI/Tickets/Abstractions/ITicketRepository.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -10,4 +11,5 @@ public interface ITicketRepository
public Task<Result<Ticket>> GetTicketWithDetailsByIdAndEmailAsync(Guid id, string email);
public IQueryable<Ticket> GetTicketsByEventId(Guid eventId);
public IQueryable<Ticket> GetTicketsByCustomerEmail(string email);
public Task<Result> MarkTicketAsUsed(Guid id);
}
5 changes: 4 additions & 1 deletion TickAPI/TickAPI/Tickets/Abstractions/ITicketService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,9 +10,11 @@ namespace TickAPI.Tickets.Abstractions;
public interface ITicketService
{
public Result<uint> GetNumberOfAvailableTicketsByType(TicketType ticketType);
public Task<Result<GetTicketDetailsResponseDto>> GetTicketDetailsAsync(Guid ticketGuid, string email);
public Task<Result<PaginatedData<GetTicketForResellResponseDto>>> GetTicketsForResellAsync(Guid eventId, int page,
int pageSize);
public Task<Result<PaginatedData<GetTicketForCustomerDto>>> GetTicketsForCustomerAsync(string email, int page,
int pageSize, TicketFiltersDto ? ticketFilters = null);
public Task<Result> ScanTicket(Guid ticketGuid);
public Task<Result<GetTicketDetailsResponseDto>> GetTicketDetailsAsync(Guid ticketGuid, string email,
string scanUrl);
}
14 changes: 12 additions & 2 deletions TickAPI/TickAPI/Tickets/Controllers/TicketsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,11 +32,12 @@ public async Task<ActionResult<GetTicketDetailsResponseDto>> 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<ActionResult<PaginatedData<GetTicketForResellResponseDto>>> GetTicketsForResell([FromQuery] Guid eventId, [FromQuery] int pageSize, [FromQuery] int page)
{
var result = await _ticketService.GetTicketsForResellAsync(eventId, page, pageSize);
Expand All @@ -54,4 +56,12 @@ public async Task<ActionResult<PaginatedData<GetTicketForCustomerDto>>> GetTicke
var tickets = await _ticketService.GetTicketsForCustomerAsync(emailResult.Value!, page, pageSize, filters);
return tickets.ToObjectResult();
}

[HttpGet("scan/{id:guid}")]
public async Task<ActionResult<bool>> ScanTicket(Guid id)
{
var res = await _ticketService.ScanTicket(id);
return res.ToObjectResult();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public record GetTicketDetailsResponseDto
DateTime StartDate,
DateTime EndDate,
GetTicketDetailsAddressDto Address,
Guid eventId
Guid eventId,
string qrcode
);
17 changes: 17 additions & 0 deletions TickAPI/TickAPI/Tickets/Repositories/TicketRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using TickAPI.Common.Results;
using TickAPI.Common.Results.Generic;
using TickAPI.Common.TickApiDbContext;
using TickAPI.Tickets.Abstractions;
Expand Down Expand Up @@ -49,4 +50,20 @@ public async Task<Result<Ticket>> GetTicketWithDetailsByIdAndEmailAsync(Guid id,
}
return Result<Ticket>.Success(ticket);
}

public async Task<Result> MarkTicketAsUsed(Guid id)
{
var ticket = await _tickApiDbContext.Tickets.FirstOrDefaultAsync(t => t.Id == id);
if (ticket == null)
{
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();
}
}
Loading
Loading