feat(admin): manually edit scene inputs (content elements)

Scene content elements (the editable Text/Media/Color/… inputs inside a scene)
had no CRUD — only AEP-import created them, so admins couldn't define or edit
them. Added full management:

content-svc:
- SceneElementsController: GET/POST/PUT/DELETE /v1/scene-elements?scene_id=
- SceneColorService: Get/Create/Update/DeleteContentElementAsync
- ContentElementResponse + SaveContentElementRequest (key, title, type,
  default_value, hint, position, text-box/font/media flags)
gateway: route /v1/scene-elements/*path → content
frontend: SceneColorEditor scenes tab → per-scene "ورودی‌ها" expander with full
  add/edit/delete of inputs (15 element types: Text/Media/Color/Number/…)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-06 06:54:22 +03:30
parent 9d499a89de
commit ddc0a2d0d9
5 changed files with 336 additions and 36 deletions
@@ -174,6 +174,63 @@ public class SceneColorService(ContentDbContext db)
p.Items.OrderBy(i => i.Sort).Select(i => new ColorPresetItemResponse(i.Id, i.ElementKey, i.Value, i.Sort)).ToList()))
.FirstAsync();
// ── Scene content elements (editable inputs) ────────────────────────────
public async Task<List<ContentElementResponse>> GetContentElementsAsync(Guid sceneId) =>
await db.SceneContentElements.Where(e => e.SceneId == sceneId)
.OrderBy(e => e.PositionInContainer)
.Select(e => ToElementResponse(e)).ToListAsync();
public async Task<ContentElementResponse> CreateContentElementAsync(SaveContentElementRequest req)
{
var e = new SceneContentElement { SceneId = req.SceneId };
ApplyElement(e, req);
db.SceneContentElements.Add(e);
await db.SaveChangesAsync();
return ToElementResponse(e);
}
public async Task<ContentElementResponse> UpdateContentElementAsync(Guid id, SaveContentElementRequest req)
{
var e = await db.SceneContentElements.FindAsync(id)
?? throw new KeyNotFoundException($"Element {id} not found");
ApplyElement(e, req);
await db.SaveChangesAsync();
return ToElementResponse(e);
}
public async Task DeleteContentElementAsync(Guid id)
{
var e = await db.SceneContentElements.FindAsync(id)
?? throw new KeyNotFoundException($"Element {id} not found");
db.SceneContentElements.Remove(e);
await db.SaveChangesAsync();
}
private static void ApplyElement(SceneContentElement e, SaveContentElementRequest req)
{
e.Key = req.Key;
e.Title = req.Title;
e.Hint = req.Hint;
e.Type = Enum.TryParse<ContentElementType>(req.Type, true, out var t) ? t : ContentElementType.Text;
e.DefaultValue = req.DefaultValue;
e.PositionInContainer = req.PositionInContainer;
e.IsTextBox = req.IsTextBox;
e.MaxSize = req.MaxSize;
e.FontSize = req.FontSize;
e.IsFontChangeable = req.IsFontChangeable;
e.IsFontSizeChangeable = req.IsFontSizeChangeable;
e.VideoSupport = req.VideoSupport;
e.Width = req.Width;
e.Height = req.Height;
e.Thumbnail = req.Thumbnail;
}
private static ContentElementResponse ToElementResponse(SceneContentElement e) => new(
e.Id, e.SceneId, e.Key, e.Title, e.Hint, e.Type.ToString(), e.DefaultValue,
e.PositionInContainer, e.IsTextBox, e.MaxSize, e.FontSize, e.IsFontChangeable,
e.IsFontSizeChangeable, e.VideoSupport, e.Width, e.Height, e.Thumbnail);
// ── helpers ─────────────────────────────────────────────────────────────
private static SceneResponse ToSceneResponse(Scene s) => new(