feat(content+admin): content ranking + statistics dashboard
Build backend images / build content-svc (push) Failing after 16s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 17s
Build backend images / build identity-svc (push) Failing after 2m12s
Build backend images / build notification-svc (push) Failing after 3m15s
Build backend images / build render-svc (push) Failing after 51s
Build backend images / build studio-svc (push) Failing after 56s
Build backend images / build content-svc (push) Failing after 16s
Build backend images / build file-svc (push) Failing after 48s
Build backend images / build gateway (push) Failing after 17s
Build backend images / build identity-svc (push) Failing after 2m12s
Build backend images / build notification-svc (push) Failing after 3m15s
Build backend images / build render-svc (push) Failing after 51s
Build backend images / build studio-svc (push) Failing after 56s
- content-svc: template list gains popularity/rating sort modes (use_count_desc,
popular, rating_desc); new PATCH /v1/templates/{id}/sort to set manual sort
weight (feature/pin) without a full edit
- admin /admin/ranking: templates ordered by popularity with views/uses/rating
and inline manual-sort editor
- admin /admin/stats: overview dashboard (users, revenue, paying customers,
conversion, templates/categories/campaigns/blogs counts) aggregated from
existing identity + content endpoints
- nav: Dashboard + Ranking links
Completes the epic: SMS/Email/Templates → Marketing → CRM → Ranking + Stats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,8 @@ public class TemplateService(ContentDbContext db)
|
||||
"sort_asc" => q.OrderBy(x => x.Sort),
|
||||
"name_asc" => q.OrderBy(x => x.Name),
|
||||
"view_count_desc" => q.OrderByDescending(x => x.ViewCount),
|
||||
"use_count_desc" or "popular" => q.OrderByDescending(x => x.UseCount).ThenByDescending(x => x.ViewCount),
|
||||
"rating_desc" => q.OrderByDescending(x => x.RateAvg).ThenByDescending(x => x.RateCount),
|
||||
_ => q.OrderByDescending(x => x.SortDate)
|
||||
};
|
||||
|
||||
@@ -132,6 +134,16 @@ public class TemplateService(ContentDbContext db)
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>Lightweight ranking update — set a template's manual sort weight without a full edit.</summary>
|
||||
public async Task SetContainerSortAsync(Guid id, int sort)
|
||||
{
|
||||
var container = await db.ProjectContainers.FindAsync(id)
|
||||
?? throw new KeyNotFoundException($"Container {id} not found");
|
||||
container.Sort = sort;
|
||||
container.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<ProjectDetailResponse> GetProjectDetailAsync(Guid id)
|
||||
{
|
||||
var project = await db.Projects
|
||||
|
||||
@@ -40,8 +40,19 @@ public class TemplatesController(TemplateService svc) : ControllerBase
|
||||
await svc.DeleteContainerAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// ── Ranking: set manual sort weight (feature / pin) ─────────────────────────
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPatch("{id:guid}/sort")]
|
||||
public async Task<IActionResult> SetSort(Guid id, [FromBody] SetSortRequest req)
|
||||
{
|
||||
await svc.SetContainerSortAsync(id, req.Sort);
|
||||
return Ok(new { ok = true });
|
||||
}
|
||||
}
|
||||
|
||||
public record SetSortRequest(int Sort);
|
||||
|
||||
[ApiController]
|
||||
[Route("v1/projects")]
|
||||
public class ProjectsController(TemplateService svc) : ControllerBase
|
||||
|
||||
Reference in New Issue
Block a user