refactor: migrate Exceptionless.Web to Minimal APIs with Foundatio.Mediator#2257
Open
niemyjski wants to merge 62 commits into
Open
refactor: migrate Exceptionless.Web to Minimal APIs with Foundatio.Mediator#2257niemyjski wants to merge 62 commits into
niemyjski wants to merge 62 commits into
Conversation
Planning artifacts for migrating Exceptionless.Web controllers to Minimal APIs with Foundatio.Mediator dispatch, preserving all existing API behavior. Change deliverables: - proposal.md: justification, classification, rollback plan - design.md: architecture, endpoint/mediator/handler patterns - tasks.md: 19 ordered migration tasks with verification steps - acceptance.md: SHALL/SHALL NOT acceptance criteria - risks.md: 9 risks with mitigation strategies New specs (testable SHALL statements): - api-architecture: endpoint registration, mediator dispatch, DI - api-contract: route/response/header preservation - api-validation: DataAnnotation + MiniValidation - api-problem-details: error response shape - api-middleware: throttling, overage, filters, pipeline ordering - api-openapi: runtime/build-time generation, snapshot tests - api-patching: Delta<T> preservation, no JSON Patch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merge all service registrations and middleware pipeline into single Program.cs - Use WebApplication.CreateBuilder() minimal hosting pattern - Add Foundatio.Mediator 1.2.1 package reference - Add Microsoft.Extensions.ApiDescription.Server for build-time OpenAPI - Add stub MapApiEndpoints() extension for future endpoint registrations - Update AppWebHostFactory to use WebApplicationFactory<Program> - Remove Startup.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ApiEndpointGroups: shared route group builder with auth policy - ApiResults: OkWithLinks, OkWithResourceLinks, Permission, WorkInProgress helpers - Pagination: limit/page/skip helpers extracted from base controller - TimeRangeParser: time range parsing extracted from base controller - CurrentUserAccessor: HttpContext user helpers - ConfigurationResponseEndpointFilter: config version header filter - ApiResponseHeadersEndpointFilter: common response headers - ApiValidation: MiniValidation wrapper for endpoint validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create Messages/StatusMessages.cs with command/query records - Create Handlers/StatusHandler.cs and UtilityHandler.cs with mediator handlers - Create Endpoints/StatusEndpoints.cs and UtilityEndpoints.cs - Remove StatusController.cs and UtilityController.cs - Wire up MapApiEndpoints() in ApiEndpoints.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- TokenEndpoints: full CRUD with org/project scoped routes - WebHookEndpoints: CRUD plus Zapier subscribe/unsubscribe/test - StripeEndpoints: webhook receiver with signature validation - All use Foundatio.Mediator handler pattern - Remove TokenController, WebHookController, StripeController Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ts with Foundatio.Mediator Replace MVC controllers with the same Minimal API + Mediator pattern used by Token, WebHook, and Status endpoints. Each controller is split into Messages (records), Handler (business logic), and Endpoints (HTTP routing via IMediator). Preserves all routes, route constraints (:objectid, :token, :minlength), auth policies (User, GlobalAdmin), named routes (GetSavedViewById, GetUserById), and behavior including predefined saved view management, email verification, admin role management, and rate-limited email updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ProjectEndpoints: full CRUD, config, notifications, integrations, Slack - OrganizationEndpoints: full CRUD, invoices, plans, suspend - Preserve all routes, auth policies, route names - Remove ProjectController.cs and OrganizationController.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- AuthEndpoints: login, signup, OAuth, forgot-password, change-password - Preserve AllowAnonymous on public auth routes - Port complete OAuth flow (Google, Facebook, GitHub, Microsoft) - Remove AuthController.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace MVC controllers with Foundatio.Mediator-based Minimal API endpoints following the established pattern (Messages/Handlers/Endpoints). All routes, authorization policies, and route names are preserved. - AdminController → AdminMessages + AdminHandler + AdminEndpoints - StackController → StackMessages + StackHandler + StackEndpoints - EventController → EventMessages + EventHandler + EventEndpoints - Update ApiEndpoints.cs to register new endpoint groups - Fix ControllerManifestTests assembly reference (no controllers remain)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove AddControllers() and MapControllers() from Program.cs - Remove AddAutoValidation() (MVC-specific filter) - Remove ExceptionlessApiController, ReadOnlyRepositoryApiController, RepositoryApiController base classes - Keep shared types (PermissionResult, TimeInfo, WorkInProgressResult, ModelActionResults) - Update ControllerManifestTests to verify no MVC controllers remain - Full solution builds with 0 errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- EndpointManifestTests: verifies all endpoint classes are registered - OpenApiSnapshotTests: lightweight test app for OpenAPI document verification - MinimalApiTestApp: shared test host without Elasticsearch dependency - SnapshotTestHelper: shared snapshot comparison utility - Remove old OpenApiControllerTests (replaced by snapshot approach) - Generate initial endpoint-manifest.json and openapi.json baselines Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ebhook subscribe route, user-agent - Restore :token/:tokens route constraints on token endpoints - Add canonical api/v2/webhooks/subscribe route (was only versioned) - Create AutoValidationEndpointFilter for Minimal API auto-validation - Register auto-validation filter on all endpoint groups - Remove dead ApiEndpointGroups.cs - Fix UserAgent header regression: prefer X-Exceptionless-Client over User-Agent - Fix Stripe trailing slash: map POST directly without empty-string sub-route - Delete obsolete controller-manifest.json test fixture Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Routes now match pre-migration manifest (184 endpoints, all constraints preserved). Only remaining diff: versioned subscribe route template lacks =2 default (Minimal API limitation; covered by canonical api/v2/webhooks/subscribe route). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace Host.CreateDefaultBuilder()/ConfigureWebHostDefaults() with WebApplication.CreateBuilder() for consistency with the web project. Preserves all behavior: health checks, Serilog, APM, job registration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port all XML doc summaries, parameter descriptions, and response descriptions from the old MVC controllers to Minimal API endpoints using .WithSummary() and .WithMetadata(EndpointDocumentation) with a custom IOpenApiOperationTransformer. Results vs old spec (128/348/244 target): - Summaries: 128/128 (100%) - Parameter descriptions: 298/348 (86% - gap is from params not in lambda signatures like headers and manual query params) - Response descriptions: 266 total responses documented (exceeds 244) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend EndpointDocumentationOperationTransformer to support injecting additional parameters (e.g. User-Agent header, query string arrays). Add Produces<T>() and ProducesProblem() declarations across all endpoint files to document response types and error codes. This brings coverage to: - Summaries: 128 (unchanged) - Parameters: 409 (was 287, added 122) - Response codes: 353 (was 231, added 122) - Schemas: 49 (was 43, added 6) Update snapshot test assertion from 200 to 202 for user-description endpoint to match its actual Accepted semantics.
Change endpoint group tags from plural to singular to match the old MVC controller-derived tags (Event, Organization, Project, Stack, User, etc.). Add explicit WithTags to Token, WebHook groups and all v1 endpoints that previously inherited tags from their controller class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Disable ValidateOnBuild in WebApplication.CreateBuilder since the service graph uses lambda factories (queues, caching, Elasticsearch) that resolve dependencies at runtime via IServiceProvider. The old Generic Host path did not enable this validation. - Add using/dispose to StreamReader in StripeEndpoints - Add using/dispose to MemoryStream in EventHandler - Add using/dispose to ScopedCacheClient in EventHandler and StackHandler - Refactor AutoValidationEndpointFilter to use Where() filtering - Refactor DeleteEvents/DeleteStacks to use LINQ Where() instead of mutating a list inside a foreach loop Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ValidateOnBuild=false in Program.cs via builder.Host.UseDefaultServiceProvider() does not take effect in the minimal hosting model when used with WebApplicationFactory. The ConfigureHostBuilder stores but may not replay service provider options. Fix: Add builder.UseDefaultServiceProvider() in AppWebHostFactory.ConfigureWebHost where the IWebHostBuilder properly replaces the service provider factory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ecc217a to
30aed31
Compare
…nd add validation unit tests - Svelte: export jsonPatchRequestOptions from json-patch.ts with correct Content-Type header; pass to all patchJSON() calls - Angular: already fixed in prior commit (customPATCH headers) - Harden ValidateOperations: reject root/empty/malformed/nested paths - Add 24 focused unit tests for JsonPatchValidation covering: - Valid replace/test operations - Disallowed operations (add/remove/move/copy) - Root/empty/whitespace path rejection - Nested/double-slash path rejection - Immutable path enforcement (case-insensitive) - Max operations limit - ApplyPatch, AffectsPath, IsEmpty, FromPartialObject Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…TCH calls Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… compatibility The JsonPatchValidationTests.ApplyPatch_ValidReplace_MutatesTarget test failed in CI Release builds because JsonSerializerOptions without a TypeInfoResolver cannot resolve metadata for TestDto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-mediator-openapi-migration
Reject JSON Patch operation paths that do not start with '/' before normalization so RFC-invalid paths cannot silently mutate resources. Restore raw JSON/text request-body OpenAPI metadata for event ingestion endpoints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Recreate test IFileStorage from the final AppOptions so parallel WebApplicationFactory instances use their own scoped folder storage. This prevents ResetDataAsync in one factory from deleting queued event payloads for another factory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-mediator-openapi-migration # Conflicts: # .agents/skills/backend-architecture/SKILL.md # src/Exceptionless.Web/Api/Handlers/SavedViewHandler.cs # src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts # src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts # src/Exceptionless.Web/Controllers/EventController.cs # src/Exceptionless.Web/Controllers/OrganizationController.cs # src/Exceptionless.Web/Controllers/ProjectController.cs # src/Exceptionless.Web/Controllers/StatusController.cs # src/Exceptionless.Web/Controllers/UserController.cs # tests/Exceptionless.Tests/Controllers/Data/controller-manifest.json # tests/Exceptionless.Tests/Controllers/Data/openapi.json
…al-api-mediator-openapi-migration # Conflicts: # src/Exceptionless.Web/Api/Handlers/SavedViewHandler.cs # src/Exceptionless.Web/Controllers/AdminController.cs # src/Exceptionless.Web/Controllers/EventController.cs # src/Exceptionless.Web/Controllers/OrganizationController.cs # src/Exceptionless.Web/Controllers/ProjectController.cs # src/Exceptionless.Web/Controllers/StackController.cs # src/Exceptionless.Web/Controllers/StatusController.cs # src/Exceptionless.Web/Startup.cs # src/Exceptionless.Web/Utility/Delta/Delta.cs # src/Exceptionless.Web/Utility/Delta/DeltaJsonConverter.cs # tests/Exceptionless.Tests/AppWebHostFactory.cs # tests/Exceptionless.Tests/Controllers/Data/openapi.json # tests/Exceptionless.Tests/Controllers/EventControllerTests.cs # tests/Exceptionless.Tests/Controllers/OpenApiSnapshotTests.cs # tests/Exceptionless.Tests/Serializer/SnakeCaseLowerNamingPolicyTests.cs
| c.Enrich.WithMachineName(); | ||
|
|
||
| if (!String.IsNullOrEmpty(options.ExceptionlessApiKey)) | ||
| c.WriteTo.Sink(new ExceptionlessSink(), LogEventLevel.Information); |
Comment on lines
+17
to
+27
| foreach (var argument in validatableArguments) | ||
| { | ||
| if (!MiniValidator.TryValidate(argument!, out var errors)) | ||
| { | ||
| var normalizedErrors = errors.ToDictionary( | ||
| e => e.Key.ToLowerUnderscoredWords(), | ||
| e => e.Value); | ||
|
|
||
| return Microsoft.AspNetCore.Http.Results.ValidationProblem(normalizedErrors, statusCode: StatusCodes.Status422UnprocessableEntity); | ||
| } | ||
| } |
Comment on lines
+231
to
+236
| foreach (var webHook in webHooks) | ||
| { | ||
| if ((!String.IsNullOrEmpty(webHook.OrganizationId) && HttpContext.Request.IsInOrganization(webHook.OrganizationId)) | ||
| || (!String.IsNullOrEmpty(webHook.ProjectId) && await IsInProjectAsync(webHook.ProjectId))) | ||
| results.Add(webHook); | ||
| } |
Comment on lines
+72
to
+76
| catch (Exception ex) | ||
| { | ||
| logger.LogCritical(ex, "Login failed for {EmailAddress}: {Message}", email, ex.Message); | ||
| return Result.Unauthorized("Login failed."); | ||
| } |
Comment on lines
+713
to
+716
| catch (Exception ex) | ||
| { | ||
| logger.LogCritical(ex, "Error removing user tokens for {EmailAddress}: {Message}", user.EmailAddress, ex.Message); | ||
| } |
Comment on lines
+670
to
+673
| catch (Exception ex) | ||
| { | ||
| _logger.LogWarning(ex, "Failed to increment deleted usage metrics for org {OrganizationId} project {ProjectId}: {Message}", projectGroup.Key.OrganizationId, projectGroup.Key.ProjectId, ex.Message); | ||
| } |
Comment on lines
+23
to
+30
| catch (Exception) | ||
| { | ||
| return new AppQueryValidator.QueryProcessResult | ||
| { | ||
| IsValid = false, | ||
| Message = $"Error parsing query: \"{message.Query}\"" | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrate all Exceptionless.Web controllers to Minimal API endpoints using Foundatio.Mediator for command/query dispatch.
What Changed
Startup.csinto single minimal hostingProgram.csWebApplication.CreateBuilder()minimal hostingEndpointDocumentationOperationTransformerKey Invariants Preserved
Structure
Migration Order (per OpenSpec)
Breaking Changes
None. All public routes, auth behavior, and response shapes are preserved.
Known Acceptable Differences
StringStringValuesKeyValuePairschema removed (MVC model binding artifact) →ProblemDetailsschema added (from.ProducesProblem())/api/v{apiVersion:int}/webhooks/subscribelacks=2default (Minimal API limitation, canonical/api/v2/webhooks/subscriberoute exists)