From 5753c2db8f3e87e04315422137d62b326fa85ff5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 08:55:01 +0000 Subject: [PATCH 1/5] Initial plan From be749e5888977c7461940a94b9695cd92d053dc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:01:46 +0000 Subject: [PATCH 2/5] Implement pagination support for Gods and Mythologies endpoints Co-authored-by: moller2866 <83021349+moller2866@users.noreply.github.com> --- src/Common/Models/PagedResult.cs | 43 +++++++++++++++++++ src/Common/Models/PaginationParameters.cs | 36 ++++++++++++++++ src/Endpoints/v1/Gods.cs | 24 ++++++++++- src/Endpoints/v1/Mythologies.cs | 25 ++++++++++- src/Gods/DBRepositories/GodRepository.cs | 26 +++++++++++ src/Gods/Interfaces/IGodRepository.cs | 3 ++ src/Gods/Mocks/GodRepository.cs | 18 ++++++++ .../DBRepository/MythologyRepository.cs | 20 +++++++++ .../Interfaces/IMythologyRepository.cs | 3 ++ tests/UnitTests/GodEndpointsTests.cs | 38 ++++++++++++++-- 10 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 src/Common/Models/PagedResult.cs create mode 100644 src/Common/Models/PaginationParameters.cs diff --git a/src/Common/Models/PagedResult.cs b/src/Common/Models/PagedResult.cs new file mode 100644 index 0000000..4846525 --- /dev/null +++ b/src/Common/Models/PagedResult.cs @@ -0,0 +1,43 @@ +namespace MythApi.Common.Models; + +/// +/// Represents a paginated result set +/// +/// The type of items in the result +public class PagedResult +{ + /// + /// The items in the current page + /// + public IList Items { get; set; } = new List(); + + /// + /// Current page number (1-based) + /// + public int Page { get; set; } + + /// + /// Number of items per page + /// + public int PageSize { get; set; } + + /// + /// Total number of items across all pages + /// + public int TotalCount { get; set; } + + /// + /// Total number of pages + /// + public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); + + /// + /// Whether there is a previous page + /// + public bool HasPrevious => Page > 1; + + /// + /// Whether there is a next page + /// + public bool HasNext => Page < TotalPages; +} diff --git a/src/Common/Models/PaginationParameters.cs b/src/Common/Models/PaginationParameters.cs new file mode 100644 index 0000000..eaa6e72 --- /dev/null +++ b/src/Common/Models/PaginationParameters.cs @@ -0,0 +1,36 @@ +namespace MythApi.Common.Models; + +/// +/// Parameters for pagination requests +/// +public class PaginationParameters +{ + private const int MaxPageSize = 100; + private const int DefaultPageSize = 10; + + private int _pageSize = DefaultPageSize; + private int _page = 1; + + /// + /// Page number (1-based) + /// + public int Page + { + get => _page; + set => _page = value < 1 ? 1 : value; + } + + /// + /// Number of items per page (default: 10, max: 100) + /// + public int PageSize + { + get => _pageSize; + set => _pageSize = value > MaxPageSize ? MaxPageSize : (value < 1 ? DefaultPageSize : value); + } + + /// + /// Calculate the number of items to skip + /// + public int Skip => (Page - 1) * PageSize; +} diff --git a/src/Endpoints/v1/Gods.cs b/src/Endpoints/v1/Gods.cs index a0352e2..015f62c 100644 --- a/src/Endpoints/v1/Gods.cs +++ b/src/Endpoints/v1/Gods.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using MythApi.Gods.Interfaces; using MythApi.Common.Database.Models; +using MythApi.Common.Models; using MythApi.Gods.Models; namespace MythApi.Endpoints.v1; @@ -18,5 +19,26 @@ public static void RegisterGodEndpoints(this IEndpointRouteBuilder endpoints) { public static Task> AddOrUpdateGods(List gods, IGodRepository repository) => repository.AddOrUpdateGods(gods); - public static Task> GetAlllGods(IGodRepository repository) => repository.GetAllGodsAsync(); + public static async Task GetAlllGods( + IGodRepository repository, + [FromQuery] int? page = null, + [FromQuery] int? pageSize = null) + { + // If pagination parameters are not provided, return all gods (backward compatibility) + if (page == null && pageSize == null) + { + var allGods = await repository.GetAllGodsAsync(); + return Results.Ok(allGods); + } + + // Use pagination + var pagination = new PaginationParameters + { + Page = page ?? 1, + PageSize = pageSize ?? 10 + }; + + var pagedResult = await repository.GetAllGodsAsync(pagination); + return Results.Ok(pagedResult); + } } \ No newline at end of file diff --git a/src/Endpoints/v1/Mythologies.cs b/src/Endpoints/v1/Mythologies.cs index 6add897..8488939 100644 --- a/src/Endpoints/v1/Mythologies.cs +++ b/src/Endpoints/v1/Mythologies.cs @@ -1,4 +1,6 @@ +using Microsoft.AspNetCore.Mvc; using MythApi.Common.Database.Models; +using MythApi.Common.Models; using MythApi.Mythologies.Interfaces; public static class Mythologies @@ -10,5 +12,26 @@ public static void RegisterMythologiesEndpoints(this IEndpointRouteBuilder endpo mythologies.MapGet("", GetAllMythologies); } - public static Task> GetAllMythologies(IMythologyRepository repository) => repository.GetAllMythologiesAsync(); + public static async Task GetAllMythologies( + IMythologyRepository repository, + [FromQuery] int? page = null, + [FromQuery] int? pageSize = null) + { + // If pagination parameters are not provided, return all mythologies (backward compatibility) + if (page == null && pageSize == null) + { + var allMythologies = await repository.GetAllMythologiesAsync(); + return Results.Ok(allMythologies); + } + + // Use pagination + var pagination = new PaginationParameters + { + Page = page ?? 1, + PageSize = pageSize ?? 10 + }; + + var pagedResult = await repository.GetAllMythologiesAsync(pagination); + return Results.Ok(pagedResult); + } } diff --git a/src/Gods/DBRepositories/GodRepository.cs b/src/Gods/DBRepositories/GodRepository.cs index d2bef4e..2d2904b 100644 --- a/src/Gods/DBRepositories/GodRepository.cs +++ b/src/Gods/DBRepositories/GodRepository.cs @@ -2,6 +2,7 @@ using MythApi.Gods.Interfaces; using MythApi.Common.Database.Models; using MythApi.Common.Database; +using MythApi.Common.Models; using MythApi.Gods.Models; namespace MythApi.Gods.DBRepositories; @@ -52,6 +53,31 @@ public async Task> GetAllGodsAsync() return gods; } + public async Task> GetAllGodsAsync(PaginationParameters pagination) + { + var totalCount = await _context.Gods.CountAsync(); + + var gods = await _context.Gods + .OrderBy(g => g.Id) + .Skip(pagination.Skip) + .Take(pagination.PageSize) + .ToListAsync(); + + // Load aliases for each god + foreach (var god in gods) + { + _context.Entry(god).Collection(x => x.Aliases).Load(); + } + + return new PagedResult + { + Items = gods, + Page = pagination.Page, + PageSize = pagination.PageSize, + TotalCount = totalCount + }; + } + public async Task GetGodAsync(GodParameter parameter) { return await _context.Gods.FirstAsync(x => x.Id == parameter.Id); diff --git a/src/Gods/Interfaces/IGodRepository.cs b/src/Gods/Interfaces/IGodRepository.cs index 4e25ac7..4f266b1 100644 --- a/src/Gods/Interfaces/IGodRepository.cs +++ b/src/Gods/Interfaces/IGodRepository.cs @@ -1,4 +1,5 @@ using MythApi.Common.Database.Models; +using MythApi.Common.Models; using MythApi.Gods.Models; namespace MythApi.Gods.Interfaces; @@ -6,6 +7,8 @@ namespace MythApi.Gods.Interfaces; public interface IGodRepository{ public Task> GetAllGodsAsync(); + public Task> GetAllGodsAsync(PaginationParameters pagination); + public Task GetGodAsync(GodParameter parameter); public Task> GetGodByNameAsync(GodByNameParameter parameter); diff --git a/src/Gods/Mocks/GodRepository.cs b/src/Gods/Mocks/GodRepository.cs index 1ebaf63..6c7d576 100644 --- a/src/Gods/Mocks/GodRepository.cs +++ b/src/Gods/Mocks/GodRepository.cs @@ -1,5 +1,6 @@ using MythApi.Gods.Interfaces; using MythApi.Common.Database.Models; +using MythApi.Common.Models; using MythApi.Gods.Models; namespace MythApi.Gods.Mocks; @@ -40,6 +41,23 @@ public Task> GetAllGodsAsync() return Task.FromResult(gods as IList); } + public Task> GetAllGodsAsync(PaginationParameters pagination) + { + var totalCount = gods.Count; + var items = gods + .Skip(pagination.Skip) + .Take(pagination.PageSize) + .ToList(); + + return Task.FromResult(new PagedResult + { + Items = items, + Page = pagination.Page, + PageSize = pagination.PageSize, + TotalCount = totalCount + }); + } + public Task GetGodAsync(GodParameter parameter) { return Task.FromResult(gods[parameter.Id]); diff --git a/src/Mythologies/DBRepository/MythologyRepository.cs b/src/Mythologies/DBRepository/MythologyRepository.cs index fcad2ef..b582d2e 100644 --- a/src/Mythologies/DBRepository/MythologyRepository.cs +++ b/src/Mythologies/DBRepository/MythologyRepository.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using MythApi.Common.Database; using MythApi.Common.Database.Models; +using MythApi.Common.Models; using MythApi.Mythologies.Interfaces; namespace MythApi.Mythologies.DBRepositories; @@ -19,4 +20,23 @@ public async Task> GetAllMythologiesAsync() { return await _context.Mythologies.ToListAsync(); } + + public async Task> GetAllMythologiesAsync(PaginationParameters pagination) + { + var totalCount = await _context.Mythologies.CountAsync(); + + var mythologies = await _context.Mythologies + .OrderBy(m => m.Id) + .Skip(pagination.Skip) + .Take(pagination.PageSize) + .ToListAsync(); + + return new PagedResult + { + Items = mythologies, + Page = pagination.Page, + PageSize = pagination.PageSize, + TotalCount = totalCount + }; + } } diff --git a/src/Mythologies/Interfaces/IMythologyRepository.cs b/src/Mythologies/Interfaces/IMythologyRepository.cs index 1ab1858..b77656f 100644 --- a/src/Mythologies/Interfaces/IMythologyRepository.cs +++ b/src/Mythologies/Interfaces/IMythologyRepository.cs @@ -1,9 +1,12 @@ using MythApi.Common.Database.Models; +using MythApi.Common.Models; namespace MythApi.Mythologies.Interfaces; public interface IMythologyRepository { public Task> GetAllMythologiesAsync(); + + public Task> GetAllMythologiesAsync(PaginationParameters pagination); } diff --git a/tests/UnitTests/GodEndpointsTests.cs b/tests/UnitTests/GodEndpointsTests.cs index 595ff47..717aeed 100644 --- a/tests/UnitTests/GodEndpointsTests.cs +++ b/tests/UnitTests/GodEndpointsTests.cs @@ -1,6 +1,9 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Moq; using MythApi.Common.Database.Models; +using MythApi.Common.Models; using MythApi.Endpoints.v1; using MythApi.Gods.Interfaces; using MythApi.Gods.Models; @@ -22,7 +25,7 @@ public void Setup() } [Test] - public async Task GetAllGods_ShouldReturnAllGods() + public async Task GetAllGods_WithoutPagination_ShouldReturnAllGods() { var gods = new List { @@ -31,9 +34,38 @@ public async Task GetAllGods_ShouldReturnAllGods() }; _mockRepository.Setup(repo => repo.GetAllGodsAsync()).ReturnsAsync(gods); - var result = await MythApi.Endpoints.v1.Gods.GetAlllGods(_mockRepository.Object); + var result = await MythApi.Endpoints.v1.Gods.GetAlllGods(_mockRepository.Object, null, null); - Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result, Is.InstanceOf>>()); + var okResult = result as Ok>; + Assert.That(okResult, Is.Not.Null); + Assert.That(okResult!.Value.Count, Is.EqualTo(2)); + } + + [Test] + public async Task GetAllGods_WithPagination_ShouldReturnPagedResult() + { + var pagedResult = new PagedResult + { + Items = new List + { + new God { Name = "Zeus", MythologyId = 1, Description = "God of the sky" } + }, + Page = 1, + PageSize = 1, + TotalCount = 2 + }; + _mockRepository.Setup(repo => repo.GetAllGodsAsync(It.IsAny())) + .ReturnsAsync(pagedResult); + + var result = await MythApi.Endpoints.v1.Gods.GetAlllGods(_mockRepository.Object, 1, 1); + + Assert.That(result, Is.InstanceOf>>()); + var okResult = result as Ok>; + Assert.That(okResult, Is.Not.Null); + Assert.That(okResult!.Value.Items.Count, Is.EqualTo(1)); + Assert.That(okResult.Value.TotalCount, Is.EqualTo(2)); + Assert.That(okResult.Value.TotalPages, Is.EqualTo(2)); } [Test] From 48eed44be3898b5b6be442064f847719c193673c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:04:03 +0000 Subject: [PATCH 3/5] Add comprehensive integration tests for pagination functionality Co-authored-by: moller2866 <83021349+moller2866@users.noreply.github.com> --- tests/IntegrationTests/PaginationTests.cs | 156 ++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 tests/IntegrationTests/PaginationTests.cs diff --git a/tests/IntegrationTests/PaginationTests.cs b/tests/IntegrationTests/PaginationTests.cs new file mode 100644 index 0000000..359bfe6 --- /dev/null +++ b/tests/IntegrationTests/PaginationTests.cs @@ -0,0 +1,156 @@ +using System.Net.Http.Json; +using MythApi.Common.Database.Models; +using MythApi.Common.Models; + +namespace IntegrationTests; + +[TestFixture] +public class PaginationTests +{ + private CustomWebApplicationFactory _factory; + private HttpClient _httpClient; + + [SetUp] + public void SetUp() + { + _factory = new CustomWebApplicationFactory(); + _httpClient = _factory.CreateClient(); + } + + [TearDown] + public void TearDown() + { + _httpClient.Dispose(); + _factory.Dispose(); + } + + [Test] + public async Task GetAllGods_WithoutPagination_ShouldReturnList() + { + // Act + var response = await _httpClient.GetAsync("/api/v1/gods"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var gods = await response.Content.ReadFromJsonAsync>(); + Assert.That(gods, Is.Not.Null); + } + + [Test] + public async Task GetAllGods_WithPagination_ShouldReturnPagedResult() + { + // Act + var response = await _httpClient.GetAsync("/api/v1/gods?page=1&pageSize=2"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var pagedResult = await response.Content.ReadFromJsonAsync>(); + + Assert.That(pagedResult, Is.Not.Null); + Assert.That(pagedResult.Page, Is.EqualTo(1)); + Assert.That(pagedResult.PageSize, Is.EqualTo(2)); + Assert.That(pagedResult.Items.Count, Is.LessThanOrEqualTo(2)); + Assert.That(pagedResult.TotalCount, Is.GreaterThan(0)); + Assert.That(pagedResult.TotalPages, Is.GreaterThan(0)); + } + + [Test] + public async Task GetAllGods_WithLargePageSize_ShouldBeConstrainedToMaxSize() + { + // Act + var response = await _httpClient.GetAsync("/api/v1/gods?page=1&pageSize=200"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var pagedResult = await response.Content.ReadFromJsonAsync>(); + + Assert.That(pagedResult, Is.Not.Null); + Assert.That(pagedResult.PageSize, Is.EqualTo(100), "PageSize should be constrained to max of 100"); + } + + [Test] + public async Task GetAllGods_WithInvalidPage_ShouldDefaultToPage1() + { + // Act + var response = await _httpClient.GetAsync("/api/v1/gods?page=0&pageSize=5"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var pagedResult = await response.Content.ReadFromJsonAsync>(); + + Assert.That(pagedResult, Is.Not.Null); + Assert.That(pagedResult.Page, Is.EqualTo(1), "Page should default to 1 when invalid"); + } + + [Test] + public async Task GetAllMythologies_WithoutPagination_ShouldReturnList() + { + // Act + var response = await _httpClient.GetAsync("/api/v1/mythologies"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var mythologies = await response.Content.ReadFromJsonAsync>(); + Assert.That(mythologies, Is.Not.Null); + } + + [Test] + public async Task GetAllMythologies_WithPagination_ShouldReturnPagedResult() + { + // Act + var response = await _httpClient.GetAsync("/api/v1/mythologies?page=1&pageSize=1"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var pagedResult = await response.Content.ReadFromJsonAsync>(); + + Assert.That(pagedResult, Is.Not.Null); + Assert.That(pagedResult.Page, Is.EqualTo(1)); + Assert.That(pagedResult.PageSize, Is.EqualTo(1)); + Assert.That(pagedResult.Items.Count, Is.LessThanOrEqualTo(1)); + Assert.That(pagedResult.TotalCount, Is.GreaterThan(0)); + } + + [Test] + public async Task GetAllGods_PaginationMetadata_ShouldBeCorrect() + { + // Get all gods first to know total count + var allResponse = await _httpClient.GetAsync("/api/v1/gods"); + var allGods = await allResponse.Content.ReadFromJsonAsync>(); + var totalCount = allGods?.Count ?? 0; + + if (totalCount == 0) + { + Assert.Inconclusive("No gods in database to test pagination"); + return; + } + + // Act - Get first page with page size of 1 + var response = await _httpClient.GetAsync("/api/v1/gods?page=1&pageSize=1"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var pagedResult = await response.Content.ReadFromJsonAsync>(); + + Assert.That(pagedResult, Is.Not.Null); + Assert.That(pagedResult.TotalCount, Is.EqualTo(totalCount)); + Assert.That(pagedResult.TotalPages, Is.EqualTo(totalCount)); + Assert.That(pagedResult.HasNext, Is.EqualTo(totalCount > 1)); + Assert.That(pagedResult.HasPrevious, Is.False); + } + + [Test] + public async Task GetAllGods_PageBeyondTotalPages_ShouldReturnEmptyItems() + { + // Act - Request a page that's way beyond what exists + var response = await _httpClient.GetAsync("/api/v1/gods?page=999&pageSize=10"); + + // Assert + Assert.That(response.IsSuccessStatusCode, Is.True); + var pagedResult = await response.Content.ReadFromJsonAsync>(); + + Assert.That(pagedResult, Is.Not.Null); + Assert.That(pagedResult.Items.Count, Is.EqualTo(0)); + Assert.That(pagedResult.HasNext, Is.False); + } +} From 68bfe3291ef714f3e0fcc9760185a792337e422a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:05:31 +0000 Subject: [PATCH 4/5] Address code review feedback: fix N+1 query and method name typo Co-authored-by: moller2866 <83021349+moller2866@users.noreply.github.com> --- src/Endpoints/v1/Gods.cs | 4 ++-- src/Gods/DBRepositories/GodRepository.cs | 15 ++++----------- tests/UnitTests/GodEndpointsTests.cs | 4 ++-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Endpoints/v1/Gods.cs b/src/Endpoints/v1/Gods.cs index 015f62c..a872883 100644 --- a/src/Endpoints/v1/Gods.cs +++ b/src/Endpoints/v1/Gods.cs @@ -11,7 +11,7 @@ public static void RegisterGodEndpoints(this IEndpointRouteBuilder endpoints) { var gods = endpoints.MapGroup("/api/v1/gods"); - gods.MapGet("", GetAlllGods); + gods.MapGet("", GetAllGods); gods.MapGet("{id}", (int id, IGodRepository repository) => repository.GetGodAsync(new GodParameter(id))); gods.MapGet("search/{name}", (string name, IGodRepository repository, [FromQuery] bool includeAliases = false) => repository.GetGodByNameAsync(new GodByNameParameter(name, includeAliases))); gods.MapPost("", AddOrUpdateGods); @@ -19,7 +19,7 @@ public static void RegisterGodEndpoints(this IEndpointRouteBuilder endpoints) { public static Task> AddOrUpdateGods(List gods, IGodRepository repository) => repository.AddOrUpdateGods(gods); - public static async Task GetAlllGods( + public static async Task GetAllGods( IGodRepository repository, [FromQuery] int? page = null, [FromQuery] int? pageSize = null) diff --git a/src/Gods/DBRepositories/GodRepository.cs b/src/Gods/DBRepositories/GodRepository.cs index 2d2904b..76e5d72 100644 --- a/src/Gods/DBRepositories/GodRepository.cs +++ b/src/Gods/DBRepositories/GodRepository.cs @@ -45,11 +45,9 @@ public async Task> AddOrUpdateGods(List gods) public async Task> GetAllGodsAsync() { - var gods = await _context.Gods.ToListAsync(); - foreach (var god in gods) - { - _context.Entry(god).Collection(x => x.Aliases).Load(); - } + var gods = await _context.Gods + .Include(g => g.Aliases) + .ToListAsync(); return gods; } @@ -58,17 +56,12 @@ public async Task> GetAllGodsAsync(PaginationParameters paginat var totalCount = await _context.Gods.CountAsync(); var gods = await _context.Gods + .Include(g => g.Aliases) .OrderBy(g => g.Id) .Skip(pagination.Skip) .Take(pagination.PageSize) .ToListAsync(); - // Load aliases for each god - foreach (var god in gods) - { - _context.Entry(god).Collection(x => x.Aliases).Load(); - } - return new PagedResult { Items = gods, diff --git a/tests/UnitTests/GodEndpointsTests.cs b/tests/UnitTests/GodEndpointsTests.cs index 717aeed..fa1e908 100644 --- a/tests/UnitTests/GodEndpointsTests.cs +++ b/tests/UnitTests/GodEndpointsTests.cs @@ -34,7 +34,7 @@ public async Task GetAllGods_WithoutPagination_ShouldReturnAllGods() }; _mockRepository.Setup(repo => repo.GetAllGodsAsync()).ReturnsAsync(gods); - var result = await MythApi.Endpoints.v1.Gods.GetAlllGods(_mockRepository.Object, null, null); + var result = await MythApi.Endpoints.v1.Gods.GetAllGods(_mockRepository.Object, null, null); Assert.That(result, Is.InstanceOf>>()); var okResult = result as Ok>; @@ -58,7 +58,7 @@ public async Task GetAllGods_WithPagination_ShouldReturnPagedResult() _mockRepository.Setup(repo => repo.GetAllGodsAsync(It.IsAny())) .ReturnsAsync(pagedResult); - var result = await MythApi.Endpoints.v1.Gods.GetAlllGods(_mockRepository.Object, 1, 1); + var result = await MythApi.Endpoints.v1.Gods.GetAllGods(_mockRepository.Object, 1, 1); Assert.That(result, Is.InstanceOf>>()); var okResult = result as Ok>; From 454764863ff70761d513983f01acd76faf7ef76e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:07:53 +0000 Subject: [PATCH 5/5] Add comprehensive pagination documentation Co-authored-by: moller2866 <83021349+moller2866@users.noreply.github.com> --- PAGINATION.md | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 15 +++++++ 2 files changed, 135 insertions(+) create mode 100644 PAGINATION.md diff --git a/PAGINATION.md b/PAGINATION.md new file mode 100644 index 0000000..79739b2 --- /dev/null +++ b/PAGINATION.md @@ -0,0 +1,120 @@ +# Pagination Support + +## Overview + +The MythAPI now supports pagination for all list-returning endpoints. This improves performance and usability when dealing with large datasets. + +## Usage + +### Query Parameters + +All list endpoints now accept optional pagination parameters: + +- `page` - The page number (1-based). Default: returns all results if not specified +- `pageSize` - The number of items per page. Default: 10, Maximum: 100 + +### Examples + +#### Without Pagination (Backward Compatible) +```bash +# Returns all gods +GET /api/v1/gods + +# Returns all mythologies +GET /api/v1/mythologies +``` + +#### With Pagination +```bash +# Returns first page with 10 items (default page size) +GET /api/v1/gods?page=1 + +# Returns second page with 5 items per page +GET /api/v1/gods?page=2&pageSize=5 + +# Returns first page of mythologies with 20 items +GET /api/v1/mythologies?page=1&pageSize=20 +``` + +## Response Format + +### Non-Paginated Response +When pagination parameters are not provided, the response is a simple list: +```json +[ + { + "id": 1, + "name": "Zeus", + "description": "God of the sky", + "mythologyId": 1, + "aliases": [] + }, + ... +] +``` + +### Paginated Response +When pagination parameters are provided, the response includes metadata: +```json +{ + "items": [ + { + "id": 1, + "name": "Zeus", + "description": "God of the sky", + "mythologyId": 1, + "aliases": [] + }, + ... + ], + "page": 1, + "pageSize": 10, + "totalCount": 42, + "totalPages": 5, + "hasNext": true, + "hasPrevious": false +} +``` + +### Response Fields + +- `items` - Array of items for the current page +- `page` - Current page number (1-based) +- `pageSize` - Number of items per page +- `totalCount` - Total number of items across all pages +- `totalPages` - Total number of pages available +- `hasNext` - Boolean indicating if there's a next page +- `hasPrevious` - Boolean indicating if there's a previous page + +## Validation + +The API automatically validates and corrects invalid pagination parameters: + +- `page` values less than 1 are automatically set to 1 +- `pageSize` values greater than 100 are automatically capped at 100 +- `pageSize` values less than 1 are automatically set to the default (10) + +## Endpoints Supporting Pagination + +The following endpoints support pagination: + +1. **GET /api/v1/gods** - List all gods +2. **GET /api/v1/mythologies** - List all mythologies + +## Implementation Notes + +- Pagination is implemented at the database level for optimal performance +- Uses Entity Framework's `Skip()` and `Take()` methods +- Eager loading is used to avoid N+1 query problems +- Results are ordered by ID to ensure consistent pagination + +## Backward Compatibility + +Existing clients that don't provide pagination parameters will continue to work as before, receiving the complete list of items. This ensures no breaking changes for existing integrations. + +## Best Practices + +1. **Use appropriate page sizes**: Balance between reducing the number of requests and keeping response sizes manageable +2. **Handle edge cases**: Always check `hasNext` and `hasPrevious` before navigating +3. **Cache when possible**: If data doesn't change frequently, consider caching paginated results +4. **Consistent parameters**: Use the same page size across related requests for better caching diff --git a/README.md b/README.md index c666398..d086025 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,21 @@ app.UseSwaggerUI(); app.Run(); ``` +# Features + +## Pagination Support + +All list-returning endpoints now support optional pagination to improve performance and usability. See [PAGINATION.md](PAGINATION.md) for detailed documentation. + +### Quick Example +```bash +# Get first page with 10 items +GET /api/v1/gods?page=1&pageSize=10 + +# Get all items (backward compatible) +GET /api/v1/gods +``` + # Future work - Improve Bicep scripts