feat(admin): project (template-item) manager + After Effects file upload
Build backend images / build content-svc (push) Failing after 50s
Build backend images / build file-svc (push) Failing after 1m5s
Build backend images / build gateway (push) Failing after 3m13s
Build backend images / build identity-svc (push) Failing after 1m32s
Build backend images / build notification-svc (push) Failing after 5m7s
Build backend images / build render-svc (push) Failing after 1m2s
Build backend images / build studio-svc (push) Failing after 54s

The admin could edit a container but not manage its renderable projects or attach
AE files. Now, inside the template editor:
- add a new project/variant under the container (name, WxH, aspect, resolution,
  duration, fps, choose-mode) → POST /v1/projects (maps via container_id)
- upload the After Effects file (.aep/.zip) per project → new PATCH
  /v1/projects/{id}/aep (sets AepFileUrl/Minio/Md5/Size + RenderAepComp), with an
  "AE ✓ / بدون فایل" status badge
- set the render composition name; delete a variant
- ProjectResponse now surfaces aep_file_url / aep_file_size_bytes / render_aep_comp

Additive only — the existing aspect/resolution variant editing is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-03 00:23:50 +03:30
parent 675b60d858
commit c4839bd35f
5 changed files with 157 additions and 27 deletions
@@ -310,9 +310,28 @@ public class TemplateService(ContentDbContext db)
p.OriginalWidth, p.OriginalHeight, p.Aspect,
p.ProjectDurationSec, p.MinDurationSec, p.MaxDurationSec,
p.FreeFps, p.ChooseMode.ToString(), p.Resolution.ToString(),
p.IsPublished, p.Sort
p.IsPublished, p.Sort,
p.AepFileUrl, p.AepFileSizeBytes, p.RenderAepComp
);
/// <summary>Attach an uploaded After Effects file (and render composition) to a project.</summary>
public async Task<ProjectDetailResponse> SetProjectAepAsync(Guid id, SetAepRequest req)
{
var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == id && p.DeletedAt == null)
?? throw new KeyNotFoundException($"Project {id} not found");
if (req.AepFileUrl != null) project.AepFileUrl = req.AepFileUrl;
if (req.AepMinioBucket != null) project.AepMinioBucket = req.AepMinioBucket;
if (req.AepMinioKey != null) project.AepMinioKey = req.AepMinioKey;
if (req.AepFileMd5 != null) project.AepFileMd5 = req.AepFileMd5;
if (req.AepFileSizeBytes.HasValue) project.AepFileSizeBytes = req.AepFileSizeBytes;
if (!string.IsNullOrWhiteSpace(req.RenderAepComp)) project.RenderAepComp = req.RenderAepComp;
if (req.Folder != null) project.Folder = req.Folder;
project.AepUploadedAt = DateTime.UtcNow;
project.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync();
return await GetProjectDetailAsync(id);
}
private static ProjectDetailResponse MapProjectDetail(Project p) => new(
p.Id, p.ContainerId, p.Name, p.Description, p.Image, p.FullDemo, p.DemoScriptTag, p.DownloadLink,
p.OriginalWidth, p.OriginalHeight, p.Aspect,
@@ -77,6 +77,12 @@ public class ProjectsController(TemplateService svc) : ControllerBase
public async Task<IActionResult> PatchProject(Guid id, [FromBody] PatchProjectRequest req) =>
Ok(await svc.PatchProjectAsync(id, req));
// Attach an uploaded After Effects file (.aep) + render composition to a project.
[Authorize(Roles = "Admin")]
[HttpPatch("{id:guid}/aep")]
public async Task<IActionResult> SetAep(Guid id, [FromBody] SetAepRequest req) =>
Ok(await svc.SetProjectAepAsync(id, req));
[Authorize(Roles = "Admin")]
[HttpDelete("{id:guid}")]
public async Task<IActionResult> DeleteProject(Guid id)
@@ -244,6 +244,16 @@ public record UpdateProjectRequest(
// 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 SetAepRequest(
string? AepFileUrl,
string? AepMinioBucket,
string? AepMinioKey,
string? AepFileMd5,
long? AepFileSizeBytes,
string? RenderAepComp,
string? Folder
);
public record PatchProjectRequest(
string? Name,
string? Description,
@@ -128,7 +128,10 @@ public record ProjectResponse(
string ChooseMode,
string Resolution,
bool IsPublished,
int Sort
int Sort,
string? AepFileUrl,
long? AepFileSizeBytes,
string RenderAepComp
);
public record ProjectDetailResponse(