Skip to content

feat(api): emit event.status.changed business events#1468

Merged
losolio merged 1 commit into
mainfrom
feat/api-event-status-business-events-stacked
May 19, 2026
Merged

feat(api): emit event.status.changed business events#1468
losolio merged 1 commit into
mainfrom
feat/api-event-status-business-events-stacked

Conversation

@losolio
Copy link
Copy Markdown
Contributor

@losolio losolio commented May 19, 2026

Stacked on #1462 — base retargets to main automatically when that lands.

Summary

  • New BusinessEventSubjects.ForEvent(Guid) helper.
  • EventManagementService.UpdateEventAsync now snapshots pre-update status (AsNoTracking) and emits event.status.changed on any delta. Covers PUT/PATCH /v3/events/{id}.
  • RegistrationManagementService.CreateRegistrationAsync emits the same event type when its filling-the-last-spot auto-flip changes the status. Message tags the auto-close path as (auto: reached MaxParticipants N) so consumers can distinguish from operator actions.
  • Actor user UUID comes from the current HttpContext (GetUserId()), same pattern as the existing registration.status.changed emission. Null for background paths.

Why

The audit trail already covers registration.status.changed / registration.type.changed / order events, but had no signal for event-level state. Auto-closing an event when it fills up is a meaningful state change that downstream consumers (webhooks, analytics, oncall) should be able to see. Adding emission for manual changes in the same PR keeps the audit trail symmetric — same shape regardless of trigger.

Tests

Unit tests covering both sites:

  • EventManagementServiceTests.UpdateEventAsync_EmitsStatusChangedEvent_WhenStatusChanges
  • EventManagementServiceTests.UpdateEventAsync_DoesNotEmit_WhenStatusUnchanged
  • RegistrationManagementServiceTests.CreateRegistrationAsync_EmitsBusinessEvent_WhenEventAutoCloses
  • RegistrationManagementServiceTests.CreateRegistrationAsync_DoesNotEmitBusinessEvent_WhenStatusDoesNotChange

Full integration suite (744 tests, 742 passing, 2 skipped pre-existing) runs green locally.

Test plan

  • Update an event via PUT/PATCH /v3/events/{id} with a different status — Sentry/audit log shows event.status.changed keyed by event UUID with the acting admin's UUID.
  • Fill an event up via self-service so the auto-flip fires — audit log shows the same event type with the registering user as actor and the auto-close annotation.
  • Update an event without changing status — no business event emitted.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 19, 2026 20:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds emission of a new event.status.changed business event whenever an event's status transitions, both for operator-initiated PUT/PATCH updates via EventManagementService.UpdateEventAsync and for the auto-close path in RegistrationManagementService.CreateRegistrationAsync (when filling the last spot flips the event to RegistrationsClosed). The auto-close emission is tagged so consumers can distinguish it from manual operator actions, and the actor UUID is taken from the current HttpContext.

Changes:

  • New BusinessEventSubjects.ForEvent(Guid) helper plus event.status.changed emission from EventManagementService.UpdateEventAsync (with an AsNoTracking snapshot of pre-update status) and from the auto-close branch of RegistrationManagementService.CreateRegistrationAsync.
  • UpdateEventAsync interface and implementation now accept an optional CancellationToken (callers in EventsController still pass none).
  • New EventManagementServiceTests and added cases in RegistrationManagementServiceTests covering emit/no-emit paths for both sites; test helper extended to set up an authenticated HttpContext and seed an Organization so the org UUID lookup resolves.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.changeset/api-event-status-business-events.md Patch changeset describing the new business event emission.
apps/api/src/Eventuras.Domain/BusinessEventSubject.cs Adds ForEvent(Guid) subject factory.
apps/api/src/Eventuras.Services/Events/IEventManagementService.cs Adds optional CancellationToken to UpdateEventAsync.
apps/api/src/Eventuras.Services/Events/EventManagementService.cs Injects IBusinessEventService/IHttpContextAccessor, snapshots pre-update status, threads CT through saves, emits event.status.changed on delta.
apps/api/src/Eventuras.Services/Registrations/RegistrationManagementService.cs Emits event.status.changed when the auto-close flip changes the status, tagging the message with the MaxParticipants auto-close annotation.
apps/api/tests/Eventuras.Services.Tests/Events/EventManagementServiceTests.cs New tests covering emit on status change and no-emit on unchanged status.
apps/api/tests/Eventuras.Services.Tests/Registrations/RegistrationManagementServiceTests.cs Adds emit/no-emit tests for the auto-close path; extends BuildService to authenticate the caller and seed an org.

Task CreateNewEventAsync(EventInfo info);

Task UpdateEventAsync(EventInfo info);
Task UpdateEventAsync(EventInfo info, CancellationToken cancellationToken = default);
Comment on lines +197 to +208
// Pre-populate the event organisation so the auto-close emission can
// look up the org UUID via DbContext.
if (!_context.Organizations.Any(o => o.OrganizationId == eventInfo.OrganizationId))
{
_context.Organizations.Add(new Organization
{
OrganizationId = eventInfo.OrganizationId == 0 ? 1 : eventInfo.OrganizationId,
Name = "Test Org",
});
_context.SaveChanges();
eventInfo.OrganizationId = _context.Organizations.First().OrganizationId;
}
Base automatically changed from fix/api-block-registration-when-full to main May 19, 2026 20:30
@losolio losolio force-pushed the feat/api-event-status-business-events-stacked branch from 45c476c to 4636b17 Compare May 19, 2026 20:32
@losolio
Copy link
Copy Markdown
Contributor Author

losolio commented May 19, 2026

Addressed both Copilot review comments in 4636b17 (amended + force-pushed):

  1. CancellationToken plumbing (EventsController.cs) — both PUT and PATCH now pass their cancellationToken through to UpdateEventAsync. PUT also gained the token parameter (it didn't have one before, but it should — GetEventInfoByIdAsync and the update each do DB work). PATCH already had the token, just wasn't forwarding it past the read.

  2. BuildService ordering (RegistrationManagementServiceTests.cs) — extracted the org seeding into a SeedOrganizationFor helper that runs before the retrieval mock is built, so eventInfo.OrganizationId is stable by the time any consumer observes it. Removes the subtle post-mock mutation Copilot flagged.

All 132 EventsController integration tests + 4 unit tests still pass locally.

Every status transition on `EventInfo` now produces an `event.status.changed`
business event keyed by the event's UUID, so the audit trail records who
changed an event's status, when, and the from→to transition. Two emission
sites:

- `EventManagementService.UpdateEventAsync` snapshots the pre-update
  status (AsNoTracking) and emits after `_context.UpdateAsync` whenever
  the new value differs. Covers operator-initiated transitions made via
  PUT/PATCH `/v3/events/{id}`.
- `RegistrationManagementService.CreateRegistrationAsync` emits when
  the filling registration triggers the auto-flip to
  `RegistrationsClosed`. The message includes `(auto: reached
  MaxParticipants N)` so consumers can distinguish from manual changes.

The actor user UUID comes from `IHttpContextAccessor.HttpContext?.User?
.GetUserId()` — same pattern as the existing
`registration.status.changed` emission. Null for background-job paths.

New `BusinessEventSubjects.ForEvent(Guid)` helper next to `ForOrder` /
`ForRegistration` / `ForUser`.

Tests:
- `EventManagementServiceTests.UpdateEventAsync_EmitsStatusChangedEvent_WhenStatusChanges`
- `EventManagementServiceTests.UpdateEventAsync_DoesNotEmit_WhenStatusUnchanged`
- `RegistrationManagementServiceTests.CreateRegistrationAsync_EmitsBusinessEvent_WhenEventAutoCloses`
- `RegistrationManagementServiceTests.CreateRegistrationAsync_DoesNotEmitBusinessEvent_WhenStatusDoesNotChange`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@losolio losolio force-pushed the feat/api-event-status-business-events-stacked branch from 4636b17 to 13f387d Compare May 19, 2026 21:27
@sonarqubecloud
Copy link
Copy Markdown

@losolio losolio merged commit 6dc0bf4 into main May 19, 2026
21 checks passed
@losolio losolio deleted the feat/api-event-status-business-events-stacked branch May 19, 2026 21:55
@github-project-automation github-project-automation Bot moved this from 🆕 New to ✅ Done in Eventuras backlog May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

2 participants