feat(api): REST endpoints for deco templates and deco template groups (#144 S4 (#162))

Adds DecoTemplateController mirroring TemplateController: CRUD for deco
template groups, deco templates (flat list + by-id), item listing, and a
full-replace PUT for a deco template's item list.

Hardening (deviation from a literal port of the existing handlers, matching
the #144 S2 fix for ReplaceTemplateItemsHandler):
- CreateDecoTemplateHandler now validates DecoTemplateGroupId exists before
  insert (previously a bad id hit the FK constraint at SaveChanges and
  surfaced as a 500; now a 422).
- ReplaceDecoTemplateItemsHandler now rejects invalid items (unknown DecoId,
  StartTime >= EndTime unless EndTime is the end-of-day sentinel 00:00:00,
  or overlapping ranges) with a 422 instead of silently dropping/persisting
  them - the same silent-drop/silent-overlap bug class already fixed for
  templates.

Response DTOs serialize the raw item TimeSpans (via .TimeOfDay), so an
end-of-day item still round-trips as StartTime=22:00:00/EndTime=00:00:00
regardless of the ViewModel's day-wrapping DateTime representation.
This commit is contained in:
2026-07-07 19:51:00 +02:00
parent d71a609b4f
commit cda5c12e34
15 changed files with 1501 additions and 8 deletions
@@ -0,0 +1,4 @@
#nullable enable
namespace ErsatzTV.Core.Api.Scheduling;
public record DecoTemplateGroupResponseModel(int Id, string Name, int DecoTemplateCount);
@@ -0,0 +1,4 @@
#nullable enable
namespace ErsatzTV.Core.Api.Scheduling;
public record DecoTemplateItemResponseModel(int DecoId, string DecoName, TimeSpan StartTime, TimeSpan EndTime);
@@ -0,0 +1,4 @@
#nullable enable
namespace ErsatzTV.Core.Api.Scheduling;
public record DecoTemplateResponseModel(int Id, int DecoTemplateGroupId, string GroupName, string Name);
@@ -0,0 +1,9 @@
#nullable enable
namespace ErsatzTV.Core.Api.Scheduling;
public record DecoTemplateWithItemsResponseModel(
int Id,
int DecoTemplateGroupId,
string GroupName,
string Name,
List<DecoTemplateItemResponseModel> Items);