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
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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user