diff --git a/services/content/FlatRender.ContentSvc/Application/Services/TemplateService.cs b/services/content/FlatRender.ContentSvc/Application/Services/TemplateService.cs index 6345704..aa35dd9 100644 --- a/services/content/FlatRender.ContentSvc/Application/Services/TemplateService.cs +++ b/services/content/FlatRender.ContentSvc/Application/Services/TemplateService.cs @@ -51,8 +51,18 @@ public class TemplateService(ContentDbContext db) var total = await q.LongCountAsync(); var items = await q.Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToListAsync(); + // Scene count per template = scenes in a single aspect project (max across + // projects). One grouped query, no scene entities loaded. + var ids = items.Select(i => i.Id).ToList(); + var sceneCounts = (await db.Projects + .Where(p => p.DeletedAt == null && ids.Contains(p.ContainerId)) + .Select(p => new { p.ContainerId, Cnt = p.Scenes.Count(s => s.DeletedAt == null) }) + .ToListAsync()) + .GroupBy(x => x.ContainerId) + .ToDictionary(g => g.Key, g => g.Max(x => x.Cnt)); + return new PagedResponse( - items.Select(MapContainerSummary), + items.Select(c => MapContainerSummary(c, sceneCounts.GetValueOrDefault(c.Id, 0))), new PaginationMeta(req.Page, req.PageSize, total, (int)Math.Ceiling((double)total / req.PageSize)) ); } @@ -62,7 +72,7 @@ public class TemplateService(ContentDbContext db) var container = await db.ProjectContainers .Include(x => x.ContainerCategories).ThenInclude(x => x.Category) .Include(x => x.ContainerTags).ThenInclude(x => x.Tag) - .Include(x => x.Projects.Where(p => p.DeletedAt == null)) + .Include(x => x.Projects.Where(p => p.DeletedAt == null)).ThenInclude(p => p.Scenes.Where(s => s.DeletedAt == null)) .FirstOrDefaultAsync(x => x.Slug == slug) ?? throw new KeyNotFoundException($"Container '{slug}' not found"); @@ -74,7 +84,7 @@ public class TemplateService(ContentDbContext db) var container = await db.ProjectContainers .Include(x => x.ContainerCategories).ThenInclude(x => x.Category) .Include(x => x.ContainerTags).ThenInclude(x => x.Tag) - .Include(x => x.Projects.Where(p => p.DeletedAt == null)) + .Include(x => x.Projects.Where(p => p.DeletedAt == null)).ThenInclude(p => p.Scenes.Where(s => s.DeletedAt == null)) .FirstOrDefaultAsync(x => x.Id == id) ?? throw new KeyNotFoundException($"Container {id} not found"); @@ -450,12 +460,13 @@ public class TemplateService(ContentDbContext db) await db.SaveChangesAsync(); } - private static ContainerSummaryResponse MapContainerSummary(ProjectContainer c) => new( + private static ContainerSummaryResponse MapContainerSummary(ProjectContainer c, int sceneCount = 0) => new( c.Id, c.Slug, c.Name, c.Description, c.Image, c.Demo, c.MiniDemo, c.IsPublished, c.IsPremium, c.IsMockup, c.PrimaryMode.ToString(), c.RateAvg, c.RateCount, c.ViewCount, c.UseCount, c.Sort, c.SortDate, c.ContainerCategories.Select(cc => cc.Category.Slug).ToList(), - c.ContainerTags.Select(ct => ct.Tag.Name).ToList() + c.ContainerTags.Select(ct => ct.Tag.Name).ToList(), + sceneCount ); private static ContainerDetailResponse MapContainerDetail(ProjectContainer c) => new( @@ -465,7 +476,8 @@ public class TemplateService(ContentDbContext db) c.RateAvg, c.RateCount, c.ViewCount, c.UseCount, c.Sort, c.SortDate, c.Projects.Select(MapProject).ToList(), c.ContainerCategories.Select(cc => MapCategoryFlat(cc.Category)).ToList(), - c.ContainerTags.Select(ct => new TagResponse(ct.Tag.Id, ct.Tag.Name, ct.Tag.LatinName, ct.Tag.Slug, ct.Tag.AppliesToMode, ct.Tag.IsActive)).ToList() + c.ContainerTags.Select(ct => new TagResponse(ct.Tag.Id, ct.Tag.Name, ct.Tag.LatinName, ct.Tag.Slug, ct.Tag.AppliesToMode, ct.Tag.IsActive)).ToList(), + c.Projects.Count == 0 ? 0 : c.Projects.Max(p => p.Scenes.Count) ); private static CategoryResponse MapCategoryFlat(Category c) => new( diff --git a/services/content/FlatRender.ContentSvc/Models/Responses/Responses.cs b/services/content/FlatRender.ContentSvc/Models/Responses/Responses.cs index bf961c9..f1ae7c0 100644 --- a/services/content/FlatRender.ContentSvc/Models/Responses/Responses.cs +++ b/services/content/FlatRender.ContentSvc/Models/Responses/Responses.cs @@ -82,7 +82,8 @@ public record ContainerSummaryResponse( int Sort, DateTime SortDate, List CategorySlugs, - List Tags + List Tags, + int SceneCount ); public record ContainerDetailResponse( @@ -109,7 +110,8 @@ public record ContainerDetailResponse( DateTime SortDate, List Projects, List Categories, - List Tags + List Tags, + int SceneCount ); public record ProjectResponse( diff --git a/src/lib/admin-api.ts b/src/lib/admin-api.ts index cee3daf..e1a46e9 100644 --- a/src/lib/admin-api.ts +++ b/src/lib/admin-api.ts @@ -39,6 +39,7 @@ export interface AdminProject { metaJson?: string; sortOrder: number; mediaCount: number; + sceneCount: number; createdAt: string; updatedAt: string; } @@ -85,6 +86,7 @@ interface V2ContainerSummary { sort_date: string; category_slugs: string[]; tags: string[]; + scene_count?: number; } interface V2PagedContainers { @@ -130,6 +132,7 @@ function containerToAdminProject(c: V2ContainerSummary): AdminProject { metaJson: undefined, sortOrder: c.sort, mediaCount: 0, + sceneCount: c.scene_count ?? 0, createdAt: c.sort_date ?? "", updatedAt: c.sort_date ?? "", }; diff --git a/src/lib/video-templates-catalog.ts b/src/lib/video-templates-catalog.ts index 748fd8e..ee95bbc 100644 --- a/src/lib/video-templates-catalog.ts +++ b/src/lib/video-templates-catalog.ts @@ -511,6 +511,7 @@ export interface AdminProjectLike { categoryName?: string; coverImageUrl?: string; previewVideoUrl?: string; + sceneCount?: number; } export function adminProjectToCatalogTemplate( @@ -523,7 +524,7 @@ export function adminProjectToCatalogTemplate( aspectRatio: "widescreen", durationType: "flexible", premium: false, - sceneCount: 0, + sceneCount: p.sceneCount ?? 0, supports4k: false, colorChange: true, scriptToVideo: false,