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); } } }