feat(content): scenes + shared-colors + colour-presets endpoints
Build backend images / build content-svc (push) Failing after 2m53s
Build backend images / build file-svc (push) Failing after 3m55s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 3m26s
Build backend images / build notification-svc (push) Failing after 3m5s
Build backend images / build render-svc (push) Failing after 46s
Build backend images / build studio-svc (push) Failing after 2m22s
Build backend images / build content-svc (push) Failing after 2m53s
Build backend images / build file-svc (push) Failing after 3m55s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 3m26s
Build backend images / build notification-svc (push) Failing after 3m5s
Build backend images / build render-svc (push) Failing after 46s
Build backend images / build studio-svc (push) Failing after 2m22s
Completes the content backend for the studio building blocks (all project-scoped):
- GET /v1/scenes?project_id= + POST/PUT/DELETE (scene metadata CRUD)
- GET /v1/shared-colors?project_id= + POST/PUT/DELETE
- GET /v1/color-presets?project_id= + POST/PUT/DELETE (palette + items)
SceneColorService + DTOs; reads open, writes [Authorize(Roles=Admin)].
Gateway routes /v1/{scenes,shared-colors,color-presets}/* → content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
using FlatRender.ContentSvc.Domain.Entities;
|
||||
using FlatRender.ContentSvc.Domain.Enums;
|
||||
using FlatRender.ContentSvc.Infrastructure.Data;
|
||||
using FlatRender.ContentSvc.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FlatRender.ContentSvc.Application.Services;
|
||||
|
||||
/// <summary>CRUD for project-scoped scenes, shared colors and colour presets.</summary>
|
||||
public class SceneColorService(ContentDbContext db)
|
||||
{
|
||||
// ── Scenes ──────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<List<SceneResponse>> GetScenesAsync(Guid projectId) =>
|
||||
await db.Scenes.Where(s => s.ProjectId == projectId && s.DeletedAt == null)
|
||||
.OrderBy(s => s.Sort)
|
||||
.Select(s => ToSceneResponse(s)).ToListAsync();
|
||||
|
||||
public async Task<SceneResponse> CreateSceneAsync(CreateSceneRequest req)
|
||||
{
|
||||
var scene = new Scene
|
||||
{
|
||||
ProjectId = req.ProjectId,
|
||||
Key = req.Key,
|
||||
Title = req.Title,
|
||||
LocalizedTitle = req.LocalizedTitle,
|
||||
SceneType = ParseSceneKind(req.SceneType),
|
||||
Image = req.Image,
|
||||
Demo = req.Demo,
|
||||
SceneColorSvg = req.SceneColorSvg,
|
||||
SnapshotUrl = req.SnapshotUrl,
|
||||
GenerateKf = req.GenerateKf,
|
||||
DefaultDurationSec = req.DefaultDurationSec,
|
||||
MinDurationSec = req.MinDurationSec,
|
||||
MaxDurationSec = req.MaxDurationSec,
|
||||
OverlapAtEndSec = req.OverlapAtEndSec,
|
||||
CanHandleDuration = req.CanHandleDuration,
|
||||
ManualColorSelection = req.ManualColorSelection,
|
||||
Sort = req.Sort,
|
||||
IsActive = req.IsActive,
|
||||
};
|
||||
db.Scenes.Add(scene);
|
||||
await db.SaveChangesAsync();
|
||||
return ToSceneResponse(scene);
|
||||
}
|
||||
|
||||
public async Task<SceneResponse> UpdateSceneAsync(Guid id, UpdateSceneRequest req)
|
||||
{
|
||||
var scene = await db.Scenes.FindAsync(id) ?? throw new KeyNotFoundException($"Scene {id} not found");
|
||||
scene.Key = req.Key;
|
||||
scene.Title = req.Title;
|
||||
scene.LocalizedTitle = req.LocalizedTitle;
|
||||
scene.SceneType = ParseSceneKind(req.SceneType);
|
||||
scene.Image = req.Image;
|
||||
scene.Demo = req.Demo;
|
||||
scene.SceneColorSvg = req.SceneColorSvg;
|
||||
scene.SnapshotUrl = req.SnapshotUrl;
|
||||
scene.GenerateKf = req.GenerateKf;
|
||||
scene.DefaultDurationSec = req.DefaultDurationSec;
|
||||
scene.MinDurationSec = req.MinDurationSec;
|
||||
scene.MaxDurationSec = req.MaxDurationSec;
|
||||
scene.OverlapAtEndSec = req.OverlapAtEndSec;
|
||||
scene.CanHandleDuration = req.CanHandleDuration;
|
||||
scene.ManualColorSelection = req.ManualColorSelection;
|
||||
scene.Sort = req.Sort;
|
||||
scene.IsActive = req.IsActive;
|
||||
scene.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return ToSceneResponse(scene);
|
||||
}
|
||||
|
||||
public async Task DeleteSceneAsync(Guid id)
|
||||
{
|
||||
var scene = await db.Scenes.FindAsync(id) ?? throw new KeyNotFoundException($"Scene {id} not found");
|
||||
scene.DeletedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Shared colours ──────────────────────────────────────────────────────
|
||||
|
||||
public async Task<List<SharedColorResponse>> GetSharedColorsAsync(Guid projectId) =>
|
||||
await db.SharedColors.Where(c => c.ProjectId == projectId).OrderBy(c => c.Sort)
|
||||
.Select(c => new SharedColorResponse(c.Id, c.ProjectId, c.ElementKey, c.Title, c.Icon,
|
||||
c.AttrValue.ToString(), c.DefaultColor, c.Sort)).ToListAsync();
|
||||
|
||||
public async Task<SharedColorResponse> CreateSharedColorAsync(SaveSharedColorRequest req)
|
||||
{
|
||||
var c = new SharedColor
|
||||
{
|
||||
ProjectId = req.ProjectId,
|
||||
ElementKey = req.ElementKey,
|
||||
Title = req.Title,
|
||||
Icon = req.Icon,
|
||||
AttrValue = ParseAttrValue(req.AttrValue),
|
||||
DefaultColor = req.DefaultColor,
|
||||
Sort = req.Sort,
|
||||
};
|
||||
db.SharedColors.Add(c);
|
||||
await db.SaveChangesAsync();
|
||||
return new SharedColorResponse(c.Id, c.ProjectId, c.ElementKey, c.Title, c.Icon, c.AttrValue.ToString(), c.DefaultColor, c.Sort);
|
||||
}
|
||||
|
||||
public async Task<SharedColorResponse> UpdateSharedColorAsync(Guid id, SaveSharedColorRequest req)
|
||||
{
|
||||
var c = await db.SharedColors.FindAsync(id) ?? throw new KeyNotFoundException($"Color {id} not found");
|
||||
c.ElementKey = req.ElementKey;
|
||||
c.Title = req.Title;
|
||||
c.Icon = req.Icon;
|
||||
c.AttrValue = ParseAttrValue(req.AttrValue);
|
||||
c.DefaultColor = req.DefaultColor;
|
||||
c.Sort = req.Sort;
|
||||
c.UpdatedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
return new SharedColorResponse(c.Id, c.ProjectId, c.ElementKey, c.Title, c.Icon, c.AttrValue.ToString(), c.DefaultColor, c.Sort);
|
||||
}
|
||||
|
||||
public async Task DeleteSharedColorAsync(Guid id)
|
||||
{
|
||||
var c = await db.SharedColors.FindAsync(id) ?? throw new KeyNotFoundException($"Color {id} not found");
|
||||
db.SharedColors.Remove(c);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Colour presets (palettes) ───────────────────────────────────────────
|
||||
|
||||
public async Task<List<ColorPresetResponse>> GetColorPresetsAsync(Guid projectId) =>
|
||||
await db.SharedColorPresets.Where(p => p.ProjectId == projectId).OrderBy(p => p.Sort)
|
||||
.Select(p => new ColorPresetResponse(p.Id, p.ProjectId, p.Name, p.Sort,
|
||||
p.Items.OrderBy(i => i.Sort).Select(i => new ColorPresetItemResponse(i.Id, i.ElementKey, i.Value, i.Sort)).ToList()))
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<ColorPresetResponse> CreateColorPresetAsync(SaveColorPresetRequest req)
|
||||
{
|
||||
var preset = new SharedColorPreset
|
||||
{
|
||||
ProjectId = req.ProjectId,
|
||||
Name = req.Name,
|
||||
Sort = req.Sort,
|
||||
Items = (req.Items ?? []).Select(i => new SharedColorPresetItem
|
||||
{
|
||||
ElementKey = i.ElementKey, Value = i.Value, Sort = i.Sort,
|
||||
}).ToList(),
|
||||
};
|
||||
db.SharedColorPresets.Add(preset);
|
||||
await db.SaveChangesAsync();
|
||||
return await GetPresetAsync(preset.Id);
|
||||
}
|
||||
|
||||
public async Task<ColorPresetResponse> UpdateColorPresetAsync(Guid id, SaveColorPresetRequest req)
|
||||
{
|
||||
var preset = await db.SharedColorPresets.Include(p => p.Items).FirstOrDefaultAsync(p => p.Id == id)
|
||||
?? throw new KeyNotFoundException($"Preset {id} not found");
|
||||
preset.Name = req.Name;
|
||||
preset.Sort = req.Sort;
|
||||
db.SharedColorPresetItems.RemoveRange(preset.Items);
|
||||
preset.Items = (req.Items ?? []).Select(i => new SharedColorPresetItem
|
||||
{
|
||||
PresetId = preset.Id, ElementKey = i.ElementKey, Value = i.Value, Sort = i.Sort,
|
||||
}).ToList();
|
||||
await db.SaveChangesAsync();
|
||||
return await GetPresetAsync(preset.Id);
|
||||
}
|
||||
|
||||
public async Task DeleteColorPresetAsync(Guid id)
|
||||
{
|
||||
var preset = await db.SharedColorPresets.FindAsync(id) ?? throw new KeyNotFoundException($"Preset {id} not found");
|
||||
db.SharedColorPresets.Remove(preset);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private async Task<ColorPresetResponse> GetPresetAsync(Guid id) =>
|
||||
await db.SharedColorPresets.Where(p => p.Id == id)
|
||||
.Select(p => new ColorPresetResponse(p.Id, p.ProjectId, p.Name, p.Sort,
|
||||
p.Items.OrderBy(i => i.Sort).Select(i => new ColorPresetItemResponse(i.Id, i.ElementKey, i.Value, i.Sort)).ToList()))
|
||||
.FirstAsync();
|
||||
|
||||
// ── helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
private static SceneResponse ToSceneResponse(Scene s) => new(
|
||||
s.Id, s.ProjectId, s.Key, s.Title, s.LocalizedTitle, s.SceneType.ToString(),
|
||||
s.Image, s.Demo, s.SceneColorSvg, s.SnapshotUrl, s.GenerateKf,
|
||||
s.DefaultDurationSec, s.MinDurationSec, s.MaxDurationSec, s.OverlapAtEndSec,
|
||||
s.CanHandleDuration, s.ManualColorSelection, s.Sort, s.IsActive);
|
||||
|
||||
private static SceneKind ParseSceneKind(string? v) =>
|
||||
Enum.TryParse<SceneKind>(v, true, out var k) ? k : SceneKind.Normal;
|
||||
|
||||
private static AttrValueKind ParseAttrValue(string? v) =>
|
||||
Enum.TryParse<AttrValueKind>(v, true, out var a) ? a : AttrValueKind.fill;
|
||||
}
|
||||
Reference in New Issue
Block a user