feat: Implement Query endpoint for Connections puzzles
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using ConnectionsAPI.Database.Entities;
|
using ConnectionsAPI.Database.Entities;
|
||||||
|
using ConnectionsAPI.Models.Response;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace ConnectionsAPI.Database.Repository;
|
namespace ConnectionsAPI.Database.Repository;
|
||||||
@@ -32,12 +33,26 @@ public class PuzzleRepository(ConnectionsContext _db)
|
|||||||
return puzzle;
|
return puzzle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<ConnectionsPuzzle>> GetAllConnectionsAsync(bool includeSolutions = true)
|
public async Task<PagedDataResponse<ConnectionsPuzzle>> QueryConnectionsPuzzles(int page,
|
||||||
|
int pageCount,
|
||||||
|
int? year,
|
||||||
|
int? month,
|
||||||
|
bool includeSolutions = true)
|
||||||
{
|
{
|
||||||
// query all, ordered by print date
|
|
||||||
var query = _db.ConnectionsPuzzles
|
var query = _db.ConnectionsPuzzles
|
||||||
.AsNoTracking();
|
.AsNoTracking();
|
||||||
|
|
||||||
|
if (year != null)
|
||||||
|
{
|
||||||
|
string filterStr = $"{year}-%";
|
||||||
|
if (month != null)
|
||||||
|
{
|
||||||
|
filterStr = $"{year}-{month.ToString()!.PadLeft(2, '0')}-%";
|
||||||
|
}
|
||||||
|
|
||||||
|
query = query.Where(x => EF.Functions.Like(x.PrintDate, filterStr));
|
||||||
|
}
|
||||||
|
|
||||||
if (includeSolutions)
|
if (includeSolutions)
|
||||||
{
|
{
|
||||||
query = query
|
query = query
|
||||||
@@ -45,14 +60,24 @@ public class PuzzleRepository(ConnectionsContext _db)
|
|||||||
.ThenInclude(x => x.Cards);
|
.ThenInclude(x => x.Cards);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = (await query.OrderBy(x => x.PrintDate).ToListAsync()) ?? [];
|
query = query
|
||||||
|
.OrderBy(x => x.PrintDate)
|
||||||
|
.Skip((page - 1) * pageCount)
|
||||||
|
.Take(pageCount);
|
||||||
|
|
||||||
foreach (var puzzle in result)
|
var puzzles = await query.ToListAsync();
|
||||||
|
|
||||||
|
if (puzzles.Count > 0)
|
||||||
{
|
{
|
||||||
await EnhanceConnectionsWithDatesAsync(puzzle);
|
foreach (var puzzle in puzzles)
|
||||||
|
{
|
||||||
|
await EnhanceConnectionsWithDatesAsync(puzzle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
int totalCount = await _db.ConnectionsPuzzles.AsNoTracking().CountAsync();
|
||||||
|
|
||||||
|
return new PagedDataResponse<ConnectionsPuzzle>(page, puzzles.Count, totalCount, puzzles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnhanceConnectionsWithDatesAsync(ConnectionsPuzzle puzzle)
|
private async Task EnhanceConnectionsWithDatesAsync(ConnectionsPuzzle puzzle)
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
|
using ConnectionsAPI.Database.Repository;
|
||||||
using ConnectionsAPI.Models.Request;
|
using ConnectionsAPI.Models.Request;
|
||||||
using ConnectionsAPI.Models.Response;
|
using ConnectionsAPI.Models.Response;
|
||||||
|
|
||||||
namespace ConnectionsAPI.Features.Connections.Query;
|
namespace ConnectionsAPI.Features.Connections.Query;
|
||||||
|
|
||||||
public class Endpoint : Endpoint<QueryPuzzlesRequest, PagedDataResponse<ConnectionsPuzzleDTO>>
|
public class Endpoint(PuzzleRepository _puzzleRepository) : Endpoint<QueryPuzzlesRequest, PagedDataResponse<ConnectionsPuzzleDTO>>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Get("query");
|
Get("query");
|
||||||
|
Group<ConnectionsGroup>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task HandleAsync(QueryPuzzlesRequest req, CancellationToken ct)
|
public override async Task HandleAsync(QueryPuzzlesRequest req, CancellationToken ct)
|
||||||
{
|
{
|
||||||
return base.HandleAsync(req, ct);
|
bool hideSolutions = Query<bool>("hideSolutions", isRequired: false);
|
||||||
|
|
||||||
|
var puzzles = await _puzzleRepository.QueryConnectionsPuzzles(req.Page,
|
||||||
|
req.Count,
|
||||||
|
req.Year,
|
||||||
|
req.Month,
|
||||||
|
!hideSolutions);
|
||||||
|
|
||||||
|
PagedDataResponse<ConnectionsPuzzleDTO> response = new(puzzles.Page, puzzles.Count, puzzles.MaxCount, puzzles.Data.Select(ConnectionsPuzzleDTO.FromEntity).ToList());
|
||||||
|
|
||||||
|
await SendAsync(response, cancellation: ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// using ConnectionsAPI.Database.Repository;
|
|
||||||
// using ConnectionsAPI.Models;
|
|
||||||
// using FluentValidation;
|
|
||||||
// using LazyCache;
|
|
||||||
// using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
// namespace ConnectionsAPI.Features.Puzzle.Get
|
|
||||||
// {
|
|
||||||
// public record GetPuzzleEndpointRequest(string PuzzleDate);
|
|
||||||
|
|
||||||
// public partial class GetPuzzleEndpointRequestValidator : Validator<GetPuzzleEndpointRequest>
|
|
||||||
// {
|
|
||||||
// [GeneratedRegex("^\\d{4}-\\d{2}-\\d{2}$", RegexOptions.IgnoreCase)]
|
|
||||||
// private static partial Regex PrintDateGeneratedRegex();
|
|
||||||
|
|
||||||
// public GetPuzzleEndpointRequestValidator()
|
|
||||||
// {
|
|
||||||
// RuleFor(x => x.PuzzleDate)
|
|
||||||
// .NotEmpty().WithMessage("Puzzle date is required")
|
|
||||||
// .Must(x => PrintDateGeneratedRegex().IsMatch(x)).WithMessage("Puzzle date must be in the format yyyy-MM-dd");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public class GetPuzzleEndpoint(PuzzleRepository puzzleRepo, ILogger<GetPuzzleEndpoint> logger, IAppCache cache) : Endpoint<GetPuzzleEndpointRequest, ConnectionsPuzzleDTO>
|
|
||||||
// {
|
|
||||||
// private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
|
|
||||||
// private readonly ILogger<GetPuzzleEndpoint> _logger = logger;
|
|
||||||
// private readonly IAppCache _cache = cache;
|
|
||||||
|
|
||||||
// public override void Configure()
|
|
||||||
// {
|
|
||||||
// Get("/{PuzzleDate}.json",
|
|
||||||
// "/puzzle/{PuzzleDate}");
|
|
||||||
// AllowAnonymous();
|
|
||||||
// DontThrowIfValidationFails();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public override async Task HandleAsync(GetPuzzleEndpointRequest req, CancellationToken ct)
|
|
||||||
// {
|
|
||||||
// // default to 404 if validation fails
|
|
||||||
// if (ValidationFailed)
|
|
||||||
// {
|
|
||||||
// _logger.LogError("Validation error. {path} {pathBase}", HttpContext.Request.Path, HttpContext.Request.PathBase);
|
|
||||||
// await SendNotFoundAsync(ct);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// bool hideSolutions = Query<bool>("hideSolutions", isRequired: false);
|
|
||||||
|
|
||||||
// // query for the puzzle
|
|
||||||
// var puzzle = await _puzzleRepo.GetPuzzleByDateAsync(req.PuzzleDate, includeSolutions: !hideSolutions);
|
|
||||||
|
|
||||||
// // if not found, done here
|
|
||||||
// if (puzzle == null)
|
|
||||||
// {
|
|
||||||
// await SendNotFoundAsync(ct);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // get response from cache
|
|
||||||
// var response = ConnectionsPuzzleDTO.FromEntity(puzzle);
|
|
||||||
|
|
||||||
// // done
|
|
||||||
// await SendAsync(response, cancellation: ct);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// using ConnectionsAPI.Database.Repository;
|
|
||||||
// using ConnectionsAPI.Models;
|
|
||||||
// using LazyCache;
|
|
||||||
// using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
// namespace ConnectionsAPI.Features.Puzzle.List
|
|
||||||
// {
|
|
||||||
// public class ListPuzzlesEndpoint(PuzzleRepository puzzleRepo, IAppCache cache) : EndpointWithoutRequest<ICollection<ConnectionsPuzzleDTO>>
|
|
||||||
// {
|
|
||||||
// private readonly PuzzleRepository _puzzleRepo = puzzleRepo;
|
|
||||||
// private readonly IAppCache _cache = cache;
|
|
||||||
|
|
||||||
// public override void Configure()
|
|
||||||
// {
|
|
||||||
// Get("/all.json",
|
|
||||||
// "/puzzle/all");
|
|
||||||
// AllowAnonymous();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public override async Task HandleAsync(CancellationToken ct)
|
|
||||||
// {
|
|
||||||
// bool hideSolutions = Query<bool>("hideSolutions", isRequired: false);
|
|
||||||
|
|
||||||
// // query all, ordered by print date
|
|
||||||
// var puzzles = await _puzzleRepo.GetAllPuzzlesAsync(includeSolutions: !hideSolutions);
|
|
||||||
|
|
||||||
// // map to response object
|
|
||||||
// var response = puzzles.Select(ConnectionsPuzzleDTO.FromEntity).ToList();
|
|
||||||
|
|
||||||
// // done
|
|
||||||
// await SendAsync(response, cancellation: ct);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -4,4 +4,7 @@ public class QueryPuzzlesRequest
|
|||||||
{
|
{
|
||||||
[QueryParam] public int Page { get; set; }
|
[QueryParam] public int Page { get; set; }
|
||||||
[QueryParam] public int Count { get; set; }
|
[QueryParam] public int Count { get; set; }
|
||||||
|
|
||||||
|
[QueryParam] public int? Year { get; set; }
|
||||||
|
[QueryParam] public int? Month { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class ConnectionsPuzzleDTO
|
|||||||
NextPuzzle = dbPuzzle.NextPrintDate ?? string.Empty,
|
NextPuzzle = dbPuzzle.NextPrintDate ?? string.Empty,
|
||||||
PreviousPuzzle = dbPuzzle.PrevPrintDate ?? string.Empty,
|
PreviousPuzzle = dbPuzzle.PrevPrintDate ?? string.Empty,
|
||||||
|
|
||||||
Categories = dbPuzzle.Categories.OrderBy(x => (int)x.Color).Select(PuzzleCategoryDTO.FromEntity).ToList(),
|
Categories = dbPuzzle.Categories.OrderBy(x => (int)x.Color).Select(ConnectionsCategoryDTO.FromEntity).ToList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
public int PuzzleNumber { get; set; }
|
public int PuzzleNumber { get; set; }
|
||||||
@@ -19,16 +19,16 @@ public class ConnectionsPuzzleDTO
|
|||||||
public string PreviousPuzzle { get; set; } = string.Empty;
|
public string PreviousPuzzle { get; set; } = string.Empty;
|
||||||
public string NextPuzzle { get; set; } = string.Empty;
|
public string NextPuzzle { get; set; } = string.Empty;
|
||||||
public string Editor { get; set; } = string.Empty;
|
public string Editor { get; set; } = string.Empty;
|
||||||
public ICollection<PuzzleCategoryDTO> Categories { get; set; } = [];
|
public ICollection<ConnectionsCategoryDTO> Categories { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PuzzleCategoryDTO
|
public class ConnectionsCategoryDTO
|
||||||
{
|
{
|
||||||
public static PuzzleCategoryDTO FromEntity(Database.Entities.ConnectionsCategory dbCategory) =>
|
public static ConnectionsCategoryDTO FromEntity(Database.Entities.ConnectionsCategory dbCategory) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Title = dbCategory.Name,
|
Title = dbCategory.Name,
|
||||||
Cards = dbCategory.Cards.OrderBy(x => x.Content).Select(PuzzleCardDTO.FromEntity).ToList(),
|
Cards = dbCategory.Cards.OrderBy(x => x.Content).Select(ConnectionsCardDTO.FromEntity).ToList(),
|
||||||
Color = dbCategory.Color.ToString().ToLower(),
|
Color = dbCategory.Color.ToString().ToLower(),
|
||||||
OrderingKey = (int)dbCategory.Color
|
OrderingKey = (int)dbCategory.Color
|
||||||
};
|
};
|
||||||
@@ -36,12 +36,12 @@ public class PuzzleCategoryDTO
|
|||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
public string Color { get; set; } = string.Empty;
|
public string Color { get; set; } = string.Empty;
|
||||||
public int OrderingKey { get; set; }
|
public int OrderingKey { get; set; }
|
||||||
public ICollection<PuzzleCardDTO> Cards { get; set; } = [];
|
public ICollection<ConnectionsCardDTO> Cards { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PuzzleCardDTO
|
public class ConnectionsCardDTO
|
||||||
{
|
{
|
||||||
public static PuzzleCardDTO FromEntity(Database.Entities.ConnectionsCard dbCard) =>
|
public static ConnectionsCardDTO FromEntity(Database.Entities.ConnectionsCard dbCard) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Content = dbCard.Content,
|
Content = dbCard.Content,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO.Compression;
|
||||||
using ConnectionsAPI.Models.Request;
|
using ConnectionsAPI.Models.Request;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
|
||||||
@@ -15,5 +16,13 @@ public class QueryPuzzlesRequestValidator : Validator<QueryPuzzlesRequest>
|
|||||||
RuleFor(x => x.Count)
|
RuleFor(x => x.Count)
|
||||||
.Must(x => x > 0)
|
.Must(x => x > 0)
|
||||||
.WithMessage(x => "Item count must be a positive integer");
|
.WithMessage(x => "Item count must be a positive integer");
|
||||||
|
|
||||||
|
RuleFor(x => x.Year)
|
||||||
|
.Must(x => x == null || (x != null && x >= 2021 && x <= DateTime.UtcNow.Year + 1))
|
||||||
|
.WithMessage($"Year must be a valid year between 2021 and {DateTime.UtcNow.Year + 1}");
|
||||||
|
|
||||||
|
RuleFor(x => x.Month)
|
||||||
|
.Must(x => x == null || (x != null && x >= 1 && x <= 12))
|
||||||
|
.WithMessage("Month must be a valid month");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user