feat(admin): category SEO fields, Templates admin, safe project PATCH
Build backend images / build content-svc (push) Failing after 21s
Build backend images / build file-svc (push) Failing after 3m49s
Build backend images / build gateway (push) Failing after 1m2s
Build backend images / build identity-svc (push) Failing after 1m1s
Build backend images / build notification-svc (push) Failing after 1m2s
Build backend images / build render-svc (push) Failing after 1m0s
Build backend images / build studio-svc (push) Failing after 58s

- categories/tags admin forms: add meta title/description/keywords, bot-follow,
  sort, is_active (backend already supported these)
- new Templates admin (/admin/templates): container CRUD with description,
  keywords, publishing, premium, primary mode, category/tag assignment, plus
  editable per-variant aspect & resolution
- content-svc: PATCH /v1/projects/{id} partial update so aspect/resolution edits
  never wipe render/colour data (SharedColorsSvg, RenderAepComp, Folder)
- admin resource proxy: add PATCH passthrough

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-02 14:26:44 +03:30
parent cd95ca2c6f
commit cf5dd4f195
10 changed files with 362 additions and 5 deletions
@@ -199,6 +199,42 @@ public class TemplateService(ContentDbContext db)
return await GetProjectDetailAsync(project.Id);
}
/// <summary>Partial update — only applies supplied (non-null) fields, so editing an
/// aspect/resolution never clears render/colour data the full update would require.</summary>
public async Task<ProjectDetailResponse> PatchProjectAsync(Guid id, PatchProjectRequest req)
{
var project = await db.Projects.FindAsync(id)
?? throw new KeyNotFoundException($"Project {id} not found");
if (req.Name != null) project.Name = req.Name;
if (req.Description != null) project.Description = req.Description;
if (req.Aspect != null) project.Aspect = req.Aspect;
if (req.Resolution != null)
{
if (!Enum.TryParse<ResolutionKind>(req.Resolution, true, out var resolution))
throw new ArgumentException($"Invalid Resolution: {req.Resolution}");
project.Resolution = resolution;
}
if (req.ChooseMode != null)
{
if (!Enum.TryParse<ChooseMode>(req.ChooseMode, true, out var chooseMode))
throw new ArgumentException($"Invalid ChooseMode: {req.ChooseMode}");
project.ChooseMode = chooseMode;
}
if (req.OriginalWidth.HasValue) project.OriginalWidth = req.OriginalWidth.Value;
if (req.OriginalHeight.HasValue) project.OriginalHeight = req.OriginalHeight.Value;
if (req.ProjectDurationSec.HasValue) project.ProjectDurationSec = req.ProjectDurationSec.Value;
if (req.MinDurationSec.HasValue) project.MinDurationSec = req.MinDurationSec;
if (req.MaxDurationSec.HasValue) project.MaxDurationSec = req.MaxDurationSec;
if (req.FreeFps.HasValue) project.FreeFps = req.FreeFps.Value;
if (req.IsPublished.HasValue) project.IsPublished = req.IsPublished.Value;
if (req.Sort.HasValue) project.Sort = req.Sort.Value;
project.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync();
return await GetProjectDetailAsync(project.Id);
}
public async Task DeleteProjectAsync(Guid id)
{
var project = await db.Projects.FindAsync(id)
@@ -60,6 +60,12 @@ public class ProjectsController(TemplateService svc) : ControllerBase
public async Task<IActionResult> UpdateProject(Guid id, [FromBody] UpdateProjectRequest req) =>
Ok(await svc.UpdateProjectAsync(id, req));
// Partial update (aspect / resolution / dimensions / duration) without wiping other fields.
[Authorize(Roles = "Admin")]
[HttpPatch("{id:guid}")]
public async Task<IActionResult> PatchProject(Guid id, [FromBody] PatchProjectRequest req) =>
Ok(await svc.PatchProjectAsync(id, req));
[Authorize(Roles = "Admin")]
[HttpDelete("{id:guid}")]
public async Task<IActionResult> DeleteProject(Guid id)
@@ -215,6 +215,24 @@ public record UpdateProjectRequest(
int Sort
);
// Partial update — only non-null fields are applied, so editing an aspect/resolution
// never wipes render/colour data that the full UpdateProjectRequest would require.
public record PatchProjectRequest(
string? Name,
string? Description,
string? Aspect,
string? Resolution,
string? ChooseMode,
int? OriginalWidth,
int? OriginalHeight,
decimal? ProjectDurationSec,
decimal? MinDurationSec,
decimal? MaxDurationSec,
int? FreeFps,
bool? IsPublished,
int? Sort
);
// ── CMS ──────────────────────────────────────────────────────────────────────
public record CreateBlogRequest(