using Gridify;
using Gridify.EntityFramework;
namespace GerbilManagerWebAPI.Common
{
/// Standard paged list envelope returned by every list endpoint.
public record PagedResult(IReadOnlyList Items, int TotalCount, int Page, int PageSize);
///
/// Query-string parameters for list endpoints. All optional (nullable) so the
/// frontend can call a list with no params; Gridify's own GridifyQuery has
/// non-nullable int Page/PageSize which [AsParameters] would make REQUIRED.
/// Bound via [AsParameters]: ?filter=…&orderBy=…&page=1&pageSize=20.
///
public class GridifyParams
{
public string? Filter { get; set; }
public string? OrderBy { get; set; }
public int? Page { get; set; }
public int? PageSize { get; set; }
public GridifyQuery ToQuery() => new()
{
Filter = NormalizeFilter(Filter),
OrderBy = OrderBy,
Page = Page is > 0 ? Page.Value : 1,
PageSize = PageSize is > 0 ? PageSize.Value : 20,
};
// The frontend's gridify.ts emits "==" for equals (its documented convention),
// but Gridify's equals operator is a single "=". Translate "==" -> "=". This is
// safe because the frontend backslash-escapes any "=" inside values, and the
// other operators it uses ( != >= <= =* ) contain no literal "==".
private static string? NormalizeFilter(string? filter) =>
string.IsNullOrEmpty(filter) ? filter : filter.Replace("==", "=");
}
public static class QueryableExtensions
{
///
/// Apply a Gridify query (filter/order/page) to an EF query and project each
/// row to a DTO, returning the standard paged envelope. Filter/orderBy names
/// are the ENTITY property names (case-insensitive), e.g. "status==Active",
/// "orderBy=dateOfBirth", "litterId==…".
///
public static async Task> ToPagedResultAsync(
this IQueryable source,
GridifyParams parameters,
Func map)
{
var query = parameters.ToQuery();
Paging paging = await source.GridifyAsync(query);
var items = paging.Data.Select(map).ToList();
return new PagedResult(items, paging.Count, query.Page, query.PageSize);
}
}
}