feat(management): additive Skip/Take paging on template/instance lists (arch-review P2)

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 05:57:14 -04:00
parent 8221ee797e
commit 655834f0b8
10 changed files with 152 additions and 13 deletions
@@ -295,7 +295,7 @@ public class ManagementActor : ReceiveActor
return command switch
{
// Templates
ListTemplatesCommand => await HandleListTemplates(sp),
ListTemplatesCommand cmd => await HandleListTemplates(sp, cmd),
GetTemplateCommand cmd => await HandleGetTemplate(sp, cmd),
CreateTemplateCommand cmd => await HandleCreateTemplate(sp, cmd, user.Username),
UpdateTemplateCommand cmd => await HandleUpdateTemplate(sp, cmd, user.Username),
@@ -559,12 +559,22 @@ public class ManagementActor : ReceiveActor
// Template handlers
// ========================================================================
private static async Task<object?> HandleListTemplates(IServiceProvider sp)
private static async Task<object?> HandleListTemplates(IServiceProvider sp, ListTemplatesCommand cmd)
{
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
return await repo.GetAllTemplatesAsync();
var templates = await repo.GetAllTemplatesAsync();
return Page(templates, cmd.Skip, cmd.Take);
}
// Additive offset paging (arch-review P2). Take = null (or non-positive) keeps
// today's unlimited behaviour; Take is clamped to 1..1000; Skip floors at 0.
// Applied AFTER any in-memory scope filtering by the caller.
private static List<T> Page<T>(IEnumerable<T> source, int skip, int? take) =>
source
.Skip(Math.Max(0, skip))
.Take(take is > 0 and <= 1000 ? take.Value : int.MaxValue)
.ToList();
private static async Task<object?> HandleGetTemplate(IServiceProvider sp, GetTemplateCommand cmd)
{
var repo = sp.GetRequiredService<ITemplateEngineRepository>();
@@ -780,7 +790,9 @@ public class ManagementActor : ReceiveActor
var permittedIds = new HashSet<string>(user.PermittedSiteIds);
instances = instances.Where(i => permittedIds.Contains(i.SiteId.ToString())).ToList();
}
return instances;
// Paging is applied AFTER the site-scope filter so a scoped user never sees
// an out-of-scope instance surface into their page window.
return Page(instances, cmd.Skip, cmd.Take);
}
private static async Task<object?> HandleGetInstance(IServiceProvider sp, GetInstanceCommand cmd, AuthenticatedUser user)