feat(studio B1): persist input edits to content elements (render-binding foundation)
Build backend images / build content-svc (push) Failing after 1m3s
Build backend images / build file-svc (push) Failing after 1m5s
Build backend images / build gateway (push) Failing after 1m0s
Build backend images / build identity-svc (push) Failing after 1m8s
Build backend images / build notification-svc (push) Failing after 57s
Build backend images / build render-svc (push) Failing after 1m6s
Build backend images / build studio-svc (push) Failing after 1m3s

Studio edits previously went only to edit_state; the render binds saved_scene_contents,
so edits never reached the MP4. Add studio-svc PATCH /v1/saved-projects/{id}/contents
(update saved_scene_contents.value/value_file_id by content key, ExecuteSqlInterpolated
for null-safe params) + Next /api/projects/[id]/contents route + persistence hook pushes
edited values (from bridged c-<key> layers) alongside the scene_data save. Verified
text persists incl. UTF-8 Persian (9 chars/17 bytes).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-07 00:53:17 +03:30
parent d4b1fbd9e6
commit a69bc62724
6 changed files with 140 additions and 0 deletions
@@ -230,6 +230,31 @@ public class StudioService(StudioDbContext db)
return await GetProjectAsync(id, userId);
}
/// <summary>
/// Update individual scene-input values by content key (the studio editor's live
/// edits). Writes to saved_scene_contents.value so the render binder uses them.
/// </summary>
public async Task<int> UpdateSceneContentsAsync(Guid projectId, Guid userId, List<UpdateContentItem> items)
{
var owns = await db.SavedProjects.AnyAsync(x => x.Id == projectId && x.UserId == userId);
if (!owns) throw new KeyNotFoundException($"Project {projectId} not found");
var updated = 0;
foreach (var item in items)
{
if (string.IsNullOrWhiteSpace(item.Key)) continue;
// ExecuteSqlInterpolated maps C# null → typed SQL NULL (raw DBNull params throw).
updated += await db.Database.ExecuteSqlInterpolatedAsync($@"
UPDATE studio.saved_scene_contents c
SET value = {item.Value}, value_file_id = COALESCE({item.ValueFileId}, c.value_file_id), updated_at = now()
FROM studio.saved_scenes s
WHERE c.saved_scene_id = s.id AND s.saved_project_id = {projectId} AND c.key = {item.Key}");
}
await db.Database.ExecuteSqlInterpolatedAsync(
$"UPDATE studio.saved_projects SET last_edit_date = now(), updated_at = now() WHERE id = {projectId}");
return updated;
}
public async Task DeleteProjectAsync(Guid id, Guid userId)
{
var project = await db.SavedProjects