Wire the frozen ETag/If-Match/412 recipe (Block reference implementation) onto the Template and DecoTemplate aggregates: - ReplaceTemplateItems / ReplaceDecoTemplateItems commands gain Option<int> ExpectedVersion; ToCommand() on the request DTOs threads it through from If-Match. - Handlers introduce the version check as a standalone Either after validation (never via Apply), bump Version unconditionally before saving, and persist through SaveChangesWithConcurrencyGuard so a losing writer maps to 412 instead of 500. DecoTemplate's post-commit playout Reset enqueue now only runs after a successful save. - TemplateViewModel / DecoTemplateViewModel carry Version (header-only, not echoed in the response body), populated in Mapper. - TemplateController / DecoTemplateController: GET items emits a strong ETag of the root's version; PUT parses If-Match (400 on malformed), threads the expected version into the command, and returns the new ETag from the refreshed root on success. Both PUT actions now use the handler's returned item list directly instead of re-querying items. - SPA: templates.ts / decoTemplates.ts gain getXItemsWithMeta and an If-Match-aware replaceX; TemplateEditor / DecoTemplateEditor hold the ETag in a ref, read items-with-meta first on load, and open a "changed elsewhere" ConfirmDialog on a 412 instead of navigating away. Tests: new ReplaceTemplateItemsHandlerConcurrencyTests / ReplaceDecoTemplateItemsHandlerConcurrencyTests mirror the Block concurrency contract tests (stale/matching/absent If-Match, no-op bump, racing-save 412, non-vacuous backstop). TemplateControllerTests / DecoTemplateControllerTests gain ETag/If-Match/412 coverage. TemplatesScreen.test.tsx / DecoTemplatesScreen.test.tsx gain a 412 conflict-dialog test mirroring BlocksScreen's. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 lines
308 B
C#
12 lines
308 B
C#
using ErsatzTV.Core;
|
|
|
|
namespace ErsatzTV.Application.Scheduling;
|
|
|
|
public record ReplaceTemplateItems(
|
|
int TemplateGroupId,
|
|
int TemplateId,
|
|
string Name,
|
|
List<ReplaceTemplateItem> Items,
|
|
Option<int> ExpectedVersion = default)
|
|
: IRequest<Either<BaseError, List<TemplateItemViewModel>>>;
|