Skip to content

Move the user contacts domain to Immediate.Handlers - #831

Open
Jack-Edwards wants to merge 6 commits into
stablefrom
chore/immediate-user-contacts
Open

Move the user contacts domain to Immediate.Handlers#831
Jack-Edwards wants to merge 6 commits into
stablefrom
chore/immediate-user-contacts

Conversation

@Jack-Edwards

@Jack-Edwards Jack-Edwards commented Jul 31, 2026

Copy link
Copy Markdown
Member

Adds Immediate.Handlers and Immediate.Apis alongside MediatR, and converts the user contacts domain to them. UserContactController is replaced by three minimal API endpoint classes under Crypter.API/Endpoints/UserContacts. MediatR remains wired up for every domain that has not moved yet, so this merges on its own.

The plan is to continue one domain per pull request until nothing is left on MediatR, then remove it and the MVC scaffolding in a final pass.

Notes for review:

  • Routes are unchanged (api/user/contact, GET/POST/DELETE).
  • Crypter.API takes its own Immediate.Handlers reference despite Crypter.Core already having one. Source generators do not flow across a project reference, and Immediate.Apis excludes the analyzer from its dependency.
  • Minimal API handlers have no ControllerBase.User, so authenticated endpoints now resolve the caller via IHttpContextAccessor. AddHttpContextAccessor() is registered in Program.cs.
  • Core handlers become [Handler] container classes with a nested Command/Query record, keeping the existing Command/Query file and type names.

Breaking changes to Crypter.Common.Client:

  • IUserContactRequests.RemoveUserContactAsync returns Either<RemoveUserContactError, Unit> instead of Maybe<Unit>. RemoveUserContactError is new.
  • ICrypterHttpClient gains DeleteEitherUnitResponseAsync and loses DeleteUnitResponseAsync, which had no remaining callers once user contacts moved over.

Behavior changes:

  • Both handlers validate the username with Username.TryFrom before querying. DELETE api/user/contact with a malformed username now returns 400 InvalidUser where it previously returned 200, and POST returns 400 InvalidUser where it previously returned 404 NotFound.
  • Absent-parameter and invalid-username cases are covered by new integration tests. RemoveUserContact_Tests is updated for the signature change above.

🤖 Generated with Claude Code

Jack-Edwards and others added 5 commits July 30, 2026 23:16
Introduces Immediate.Handlers alongside MediatR and converts the first
domain to it, replacing UserContactController with minimal API endpoints
generated by Immediate.Apis. MediatR stays wired up for every domain that
has not moved yet.

Immediate has no publish/notification concept and no pipeline behaviors are
in use here, so the conversion is mechanical: each request/handler pair
becomes a [Handler] container class with a nested Command or Query record.

Crypter.API needs its own Immediate.Handlers reference even though
Crypter.Core already has one. Source generators do not flow across a project
reference, and Immediate.Apis excludes the analyzer from its own dependency,
so without it the API assembly gets no generated handlers.

Minimal API handlers have no equivalent of ControllerBase.User, so
authenticated endpoints resolve the caller through IHttpContextAccessor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Minimal API binding rejects a missing query parameter before the handler
runs, returning a bare 400 with no body, where the MVC controller used to
produce an ErrorResponse via InvalidModelStateResponseFactory. Rather than
reinstate that at the endpoint, the handlers now validate the username
themselves with Username.TryFrom, so a non-HTTP caller gets the same
checking.

RemoveUserContactCommand had no error channel and returned Unit, so it
gains Either<RemoveUserContactError, Unit>. That changes the client
signature of RemoveUserContactAsync from Maybe<Unit>, and adds
DeleteEitherUnitResponseAsync to the HTTP clients, which had no Either
returning delete.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Minimal API handlers have no ControllerBase.User, so every authenticated
endpoint repeats the same walk from IHttpContextAccessor to the claims
principal. Roughly forty endpoints are still to be migrated, so this puts
the walk in one place before the pattern spreads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The controller carried these descriptions as XML doc comments, which never
reached Swagger because the project does not generate a documentation file.
WithSummary puts them in the generated document instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The existing tests only checked that a failure was a Left, so a scrambled
error-to-status mapping would still have passed. They now assert the error
value, and cover the usernames the handlers reject.

The absent-parameter case needs a raw HttpClient, since the typed client
interpolates the username into the query string and so can only ever send
an empty value, never omit it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Moving user contacts to DeleteEitherUnitResponseAsync left this method
with no callers anywhere in the solution. The unauthenticated
implementation also built its request with HttpMethod.Post, so it would
not have deleted anything had it been called.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant