Conversation
…ring - Add HealthKeyAuthorizationHandler for private network and key-based auth - Add HealthEndpointAuthorizationMiddleware returning 404 for unauthorized access - Support CONDUIT_HEALTH_MONITORING_KEY env var with X-Conduit-Health-Key header - Allow full health access from private networks (10.x, 172.16-31.x, 192.168.x) - Secure SignalRHealthController endpoints via middleware - Simplify WebAdmin health response to minimal status - Add 34 unit tests for handler and middleware - Update docs with BetterStack configuration guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract shared useMediaInterface hook for model discovery and parameters - Unify RetryHistoryEntry type usage in ImageTask (import shared type) - Standardize error handling in video generation callbacks - Refactor ImageInterface and VideoInterface to use shared hook Closes #832 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tion, caching, failover, and request pipeline.
- SignalR: 8.0.17 → 10.0.0 - @microsoft/signalr-protocol-msgpack: 8.0.17 → 10.0.0 - @types/node: 24.3.0 → 25.0.3 - TypeScript: 5.9.2 → 5.9.3 - tsup: 8.5.0 → 8.5.1
- SignalR: 8.0.17 → 10.0.0 - All dev dependencies updated to latest: - @types/node: 24.3.0 → 25.0.3 - TypeScript: 5.9.2 → 5.9.3 - ESLint/TypeScript ESLint tooling to latest - Jest: 30.0.4 → 30.2.0 - Prettier: 3.6.2 → 3.7.4
- SignalR: 8.0.17 → 10.0.0 - All dev dependencies updated to latest - Fixed ESLint issues: consolidated imports and optional chain preference
Major updates: - SignalR: 9.0.6 → 10.0.0 (aligned with SDKs) - Next.js: 16.0.10 → 16.1.1 - Mantine suite: 8.1.2 → 8.3.10 (all packages) - uuid: 11.1.0 → 13.0.0 (ESM-only) - react-syntax-highlighter: 15.6.1 → 16.1.0 - stylelint-config-standard: 36.0.0 → 39.0.1 - stylelint-order: 6.0.4 → 7.0.1 - TypeScript: 5.8.3 → 5.9.3 - zod: 4.0.5 → 4.3.4 - @clerk/nextjs: 6.36.3 → 6.36.5 - @tanstack/react-query: 5.0.0 → 5.90.16 - zustand: 5.0.6 → 5.0.9 - jest: 30.0.4 → 30.2.0 - @playwright/test: 1.54.1 → 1.57.0 - All other dependencies updated to latest versions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add AdminControllerBase with ExecuteAsync/ExecuteWithNotFoundAsync for consistent error handling across Admin API controllers - Add async versions of ILLMClientFactory methods (GetClientAsync, etc.) to eliminate blocking Task.Run().Result anti-pattern - Add GetTopEnabledAsync to VirtualKeyRepository for optimized queries - Configure FileRetrievalService with retry policy and proper HttpClient - Fix async void in SignalRMessageQueueService to use proper async Task - Add comprehensive unit tests for AdminControllerBase (22 tests)
…infrastructure Consolidate duplicated code across Admin and Gateway APIs to reduce maintenance burden and improve consistency. Phase 1 - SecurityOptions consolidation: - Create SecurityOptionsBase with shared IP filtering, rate limiting, failed auth, and security headers options - Create AdminSecurityOptions and GatewaySecurityOptions with API-specific extensions (ApiAuth for Admin, VirtualKey for Gateway) - Shared configuration loader with environment variable parsing Phase 2 - SecurityHeadersMiddleware consolidation: - Create generic SecurityHeadersMiddleware<TOptions> in shared library - Admin/Gateway middleware files now delegate to shared implementation Phase 3 - SecurityMiddleware base class: - Create SecurityMiddlewareBase with template method pattern - Admin/Gateway inherit base and add API-specific behavior - Gateway adds event monitoring via OnSecurityViolationAsync override Phase 4 - SignalRNotificationServiceBase: - Create generic base class with optional resilience policies - Provides SendToGroupAsync, SendToAllAsync helper methods - Refactor SystemNotificationService and VideoGenerationNotificationService Benefits: - ~750 lines of duplicated code eliminated - Single source of truth for security configuration - Consistent error handling patterns across services - Optional resilience policies for SignalR notifications
…actory Remove synchronous wrapper methods that used .GetAwaiter().GetResult() to eliminate thread blocking and potential deadlock risks. Changes: - Remove GetClient, GetClientByProviderId, GetClientByProviderType sync methods from ILLMClientFactory interface and implementations - Change IConduit.GetClient to async GetClientAsync - Change ISignalRMessageBatcher.GetStatistics to async GetStatisticsAsync - Update all callers to use async methods with await - Refactor SignalROpenTelemetryService timer callback to fire-and-forget async pattern - Update test mocks to use async method signatures
…cross repositories
…e files Delete facade middleware files in Gateway and Admin that simply delegated to the shared ConduitLLM.Security.Middleware implementation. Both services now use the shared extension methods directly (UseGatewaySecurityHeaders, UseAdminSecurityHeaders).
Replace individual Redis StringIncrementAsync calls with local buffering using thread-safe Interlocked operations. Statistics are flushed to Redis every 5 seconds via a batched pipeline, eliminating 2-3 Redis round-trips per cache operation. - Add StatisticsBuffer class with atomic GetAndReset - Implement IDisposable with final flush on shutdown - Update GetStatsAsync to include pending buffered values - Use Interlocked.Increment/Add for all stat updates
…entFactory Prevents socket exhaustion under high load by using proper connection pooling. - Add IImageDownloadService with ImageDownloadService implementation - Add HttpClient overloads to static methods in ContentParts and ImageUtility - Mark original static methods as [Obsolete] with migration guidance - Add warning logs when clients fall back to direct HttpClient creation - Register named HttpClients for ExternalImageFetch, Exa, and Tavily providers
…extension method Add AddProviderResiliencePolicies<TClient>() to consolidate timeout and retry policy configuration that was duplicated across OpenAI, Groq, and MiniMax client registrations, reducing ~102 lines to ~10 lines.
…sed processing - AlertBatchingService: Convert to Channel-based work queue processed in ExecuteAsync loop with proper error handling and PeriodicTimer - SignalRAcknowledgmentService: Replace per-message Task.Run with periodic timer scan to prevent memory leaks under high load - SignalRMessageBatcher: Add Channel-based signaling for batch operations with dedicated processing task and graceful shutdown These changes prevent silent exception swallowing, memory leaks from unbounded Task creation, and ensure proper graceful shutdown handling.
Implement hybrid local + distributed locking to prevent cache stampede when cache entries expire. When multiple requests hit a cache miss simultaneously, only one performs the database query while others wait. - Add IDistributedCachePopulator interface and DistributedCachePopulator implementation with triple-check pattern (cache -> local lock -> distributed lock) - Update RedisModelCostCache and RedisProviderCache to use stampede prevention for database fallback operations - Fix CacheManager.AcquireLockAsync bug that created new SemaphoreSlim per call (defeating locking purpose) - now uses ConcurrentDictionary for per-key semaphores with cleanup to prevent memory leaks Performance impact: 100 concurrent cache misses for same key now result in 1 database query instead of 100.
…tionToResponseMapper Extract duplicated exception handling logic from ControllerErrorExtensions and AdminControllerBase into a shared ExceptionToResponseMapper class. This provides a single source of truth for mapping exceptions to HTTP responses, adding support for custom Conduit exceptions (AuthorizationException, ModelNotFoundException, InvalidRequestException, RateLimitExceededException, ServiceUnavailableException, ConfigurationException).
…ase using template method pattern Extract common batch processing logic from BillingAuditService, PricingAuditService, FunctionCallAuditService, and RequestLogService into a shared abstract base class. This reduces code duplication (~400 lines) and ensures consistent behavior across all audit services including batched writes, timer-based flushing, and data retention. Key changes: - Add IAuditEvent interface for timestamp-based retention cleanup - Create BatchAuditServiceBase<TEvent> with template method pattern - Convert RequestLogService from direct DB writes to batch processing - Register RequestLogService as leader-elected hosted service
… consolidating common functionality
…on for stats reset time; mark synchronous methods in SignalRConnectionMonitor as obsolete; refactor S3MediaStorageService to defer bucket initialization; enhance TiktokenCounter with async encoding retrieval; add async method to IFunctionClientFactory; improve FunctionClientFactory to use async client retrieval; implement async signing in AwsSignatureV4
Replace inefficient LINQ patterns where Count() is used for existence checks with Any() which short-circuits on first element found. Changes: - Replace .Count() == 0 with !.Any() - Replace .Count() > 0 with .Any() - Replace .Count() != 0 with .Any() - Replace .Count() >= 1 with .Any() Also fixes logic bug in SecurityEventMonitoringService.Analysis.cs where condition Count() == 0 && Count() > threshold was impossible to satisfy. Affected areas: repositories, services, controllers, providers, caching, security monitoring, batch operations, and integration tests.
- Added GetPaginatedAsync methods to ModelProviderMappingRepository, NotificationRepository, ProviderKeyCredentialRepository, ProviderRepository, RequestLogRepository, VirtualKeyGroupRepository, and VirtualKeyRepository for efficient data retrieval. - Deprecated GetAllAsync methods in the above repositories with warnings to use the new paginated methods. - Introduced GetByProviderPaginatedAsync and GetByModelPaginatedAsync methods for specific filtering and pagination. - Updated tests to reflect changes in repository methods and ensure correct functionality. - Modified frontend components to handle paginated data responses. - Added VirtualKeyCountsDto for tracking virtual key counts by status.
No references anywhere in WebAdmin source or config; all HTTP goes through the SDK clients or native fetch.
The streaming chat.create overload claimed StreamingResponse<ChatCompletionChunk>, but the SSE stream interleaves metrics, final-metrics, reasoning, tool, and error events on the same wire - createWebStream yields every data: payload. The lie forced consumers to defeat type narrowing with 'as unknown' casts (after exhausting the declared type, TypeScript inferred never). - Add ChatStreamEvent union (chunk | metrics | final | error | reasoning | tool events) and return StreamingResponse<ChatStreamEvent> from both chat services; narrow with the existing is* type guards. - Drop the BaseStreamChunk constraint from the generic stream plumbing; non-chunk events do not carry id/object/created. - Add missing StreamingErrorEvent to the EnhancedStreamEvent data union. - Common: type the lazy MessagePack protocol holder instead of any.
…ruthful - sdkChatStreamingAdapter: remove all seven 'as unknown as' payload casts; the SDK type guards now narrow ChatStreamEvent directly. Also drop the tokens_per_second fallback read - the backend only ever sends current_tokens_per_second in streaming metrics events. - Delete media/utils/metadata.ts, a byte-for-byte duplicate of MediaMetadata/MetadataExtractor.tsx (256 lines); both copies were live via different barrels. Imports now point at the single source. - cost-dashboard: re-wrap export bytes in a fresh Uint8Array instead of casting to BlobPart.
- CleanupModal POSTed to /api/media/cleanup, a route that does not exist (WebAdmin has only the 3 sanctioned API routes), so every cleanup action 404ed. Now calls client.media.cleanupMedia via the Admin SDK. - Delete usage-analytics/AnalyticsUtils.ts and AnalyticsCharts.tsx: orphaned remnants of the pre-Grafana analytics page whose hooks also fetched nonexistent /api/usage-analytics routes. Nothing imports them. - Add console.warn context to the few catch blocks whose silence could hide corrupt stored JSON (pricing rules, cost multipliers) or make a failed provider key lookup read as 'no keys'. The remaining empty catches are intentional (errors surfaced by mutation hooks, or parse fallbacks in form validators).
Bump ASP.NET Core / EF Core / Microsoft.Extensions 10.0.x to 10.0.9, Npgsql to 10.0.2, MSBuild CVE pins to 18.6.3, StackExchange.Redis to 2.13.17, OpenTelemetry to latest 1.15.x (prerelease packages to their latest betas), AWS SDKs, Scalar, ApplicationInsights, and test tooling (coverlet 10, Test.Sdk 18.6, bunit 2.7.2, FluentAssertions 8.10). MassTransit stays at 8.5.7 (9.x not adopted).
The legacy ASP.NET Core Startup-pattern class (ConfigureServices/Configure) was never wired up: the Gateway boots from top-level Program.cs plus the Program.*.cs partials, and nothing references ProductionStartup or calls UseStartup. It also held a divergent second copy of pipeline config (CORS, JWT bearer, rate limiting, OpenTelemetry) that no longer matched the live wiring, making it actively misleading. Verified unreferenced across the repo (*.cs/*.csproj, including tests); Gateway project builds clean with 0 warnings/0 errors. Closes #903
…s in ModelAuthorController Introduces OperationLoggingFilter (IAsyncActionFilter) which reproduces the success logging previously embedded in AdminControllerBase.ExecuteAsync (Information for mutations, Debug for reads, categorized to the controller type). Exceptions are left to propagate to the already-wired global AdminExceptionMiddleware, which maps them via the same ExceptionToResponseMapper the wrappers used — so error responses are byte-identical. ModelAuthorController converted off ExecuteAsync/ExecuteWithNotFoundAsync: null results return NotFoundEntity directly (same 404 body), and the existing InvalidOperationException/KeyNotFoundException throws now flow to the global handler. LogAdminAudit calls are unchanged. The filter is applied per-controller via [ServiceFilter] during the incremental rollout; it moves to a single global AddControllers filter once all controllers are converted. Verified: Admin builds clean (0/0); 18 ModelAuthor tests pass. Part of #902.
… batch 1) Converts ModelSeries, IpFilter, VirtualKeyGroups, Notifications, FunctionConfigurations, and FunctionCosts controllers off the per-action ExecuteAsync/ExecuteWithNotFoundAsync wrappers (45 calls removed, net -175 lines). Each gets [ServiceFilter(typeof(OperationLoggingFilter))]; thrown exceptions propagate to the global AdminExceptionMiddleware (same ExceptionToResponseMapper => identical responses), and null lookups call NotFoundEntity directly (byte-identical 404s). LogAdminAudit, PublishEventFireAndForget, metrics, [ProducesResponseType], and XML docs preserved; only wrapper-only casts and logging-context args removed. Updated VirtualKeyGroupsControllerTests: the InvalidOperationException path now propagates (asserted via ThrowsAsync) instead of returning a wrapper-caught 400 - HTTP behavior unchanged. Verified: Admin builds 0/0; full ConduitLLM.Tests.Admin namespace passes (552/552). Part of #902.
… batch 2) Converts BatchSpending, PromptCaching, FunctionCredentials, FunctionExecutions, ProviderTools, GlobalSettings, and VirtualKeys off the per-action ExecuteAsync/ExecuteWithNotFoundAsync wrappers (47 calls removed, net -200 lines). Same pattern as batch 1: class-level [ServiceFilter(typeof(OperationLoggingFilter))]; thrown exceptions propagate to the global AdminExceptionMiddleware (same ExceptionToResponseMapper => identical responses); null lookups call NotFoundEntity directly. Preserved LogAdminAudit/LogAdminAuditWithChanges, metrics, event publishing, and response-header side effects; the sync-body GetStatus action uses 'await Task.CompletedTask' to stay async without CS1998. Updated 10 throw-path unit tests (GlobalSettings split files + VirtualKeys) to assert the propagated exception via ThrowsAsync instead of a wrapper-caught error result, renamed to ...ShouldPropagateException. HTTP behavior unchanged; exception-to-response mapping is covered by AdminExceptionMiddlewareTests. Verified: Admin builds 0/0; full ConduitLLM.Tests.Admin namespace passes (552/552). Part of #902.
… batch 3) Converts Configuration, BillingAudit, SystemInfo, ProviderErrors, Analytics, ModelCosts, and Tasks off the per-action ExecuteAsync/ExecuteWithNotFoundAsync wrappers (46 calls removed; ModelCosts alone was 15, including CSV/JSON IFormFile imports). Same pattern as prior batches: class-level [ServiceFilter(typeof(OperationLoggingFilter))]; thrown exceptions propagate to AdminExceptionMiddleware (same ExceptionToResponseMapper => identical responses); null lookups call NotFoundEntity directly. Preserved Prometheus metrics, event publishing, LogAdminAudit/LogAdminAuditBulk, CSV/JSON export (File results), and date-range/timeframe validation guards. Updated 12 throw-path tests (Configuration/ModelCosts/SystemInfo/Tasks unit tests + 1 ModelCost integration test) to assert the propagated exception via ThrowsAsync. Removed now-obsolete controller-logger verifications from the *_LogsError_* tests: error logging is now owned by AdminExceptionMiddleware (covered by AdminExceptionMiddlewareTests). Renamed to ...ShouldPropagateException. Verified: Admin builds 0/0; full ConduitLLM.Tests.Admin namespace passes (552/552). Part of #902.
…rtials (Tier 1a batch 4)
Converts ModelController (3-file partial), PricingController (partial), ProviderCredentialsController (3-file partial), and the single-file ModelProviderMappingController off the per-action wrappers (43 calls removed). For partial classes, [ServiceFilter(typeof(OperationLoggingFilter))] is added exactly once on the primary declaration (the file with [ApiController]/[Route]) while wrappers are converted across all sibling files, with per-file using blocks managed. Preserved the ModelUpdated event publish, LogAdminAuditWithChanges change tracking, AdminOperationsMetricsService metrics, the connection-test and bulk-op inner try/catch blocks, and all attributes/docs.
Updated 15 throw-path tests (ModelController/ModelProviderMapping/Pricing/ProviderCredentials) to assert the propagated exception via ThrowsAsync. The TestProviderConnectionWithCredentials_With{Empty,Null}ApiKey tests mock the client factory to throw ArgumentException outside the action try/catch, so it now propagates and is asserted as such (the 400 mapping is covered by AdminExceptionMiddlewareTests).
Verified: Admin builds 0/0; full ConduitLLM.Tests.Admin namespace passes (552/552). 25 controller classes now converted; Admin wrapper calls 222 -> 32. Part of #902.
…rollers (Tier 1a batch 5) Converts AuthController, MetricsController, HealthMonitoringController, and SecurityMonitoringController off the per-action ExecuteAsync wrappers (9 calls removed). Standard pattern: class-level [ServiceFilter(typeof(OperationLoggingFilter))]; dropped now-unnecessary (object) casts. Preserved LogAdminAudit, private-helper try/catch blocks, memory-cache usage, HealthMonitoringController's intentional lack of a class-level [Authorize], and MetricsController.GetAllMetrics' internal GetDatabasePoolMetrics call + OkObjectResult cast. No controller-level unit tests exercise these, so no test changes were needed. Verified: Admin builds 0/0; full ConduitLLM.Tests.Admin namespace passes (552/552). Part of #902.
…min side complete (Tier 1a batch 6) Converts MediaController (15 actions) and MediaRetentionController (8 actions) off the per-action wrappers. MediaController was missed in the initial survey; MediaRetentionController inherits AdminControllerBase (an earlier assumption that it was on ControllerBase was wrong). Same pattern: class-level [ServiceFilter(typeof(OperationLoggingFilter))]; thrown exceptions propagate (DeleteMedia's KeyNotFoundException still maps to 404); existing NotFoundEntity/BadRequestError direct returns and LogAdminAudit calls preserved; (object) casts dropped. This completes the Admin side of Tier 1a: all 31 wrapper-using controller classes converted, zero 'return ExecuteAsync' call sites remain. Verified: Admin builds 0/0; full ConduitLLM.Tests.Admin namespace passes (552/552). Part of #902.
…ers (Tier 1a) Gateway counterpart to the Admin Tier 1a rollout (#908/#935). Introduces a Gateway-side OperationLoggingFilter (ConduitLLM.Gateway.Filters) + DI registration, and converts all 11 wrapper-using Gateway controllers (~28 real base wrappers) off the per-action ExecuteAsync wrappers from GatewayControllerBase. Thrown exceptions now propagate to the global OpenAIErrorMiddleware, which maps them via the same ExceptionToResponseMapper in OpenAI error format => identical responses. GatewayControllerBase has no ExecuteWithNotFoundAsync, so not-found stays inline (NotFound(OpenAIErrorResponse)/OpenAIError); file/range-streaming returns, inner try/catch, metrics, and CurrentVirtualKey usage preserved. The survey's 32 included a FunctionsController false positive + 3 injected-service ExecuteAsync calls in BatchOperations. Verified: Gateway builds 0/0 (zero remaining base-wrapper call sites); ConduitLLM.Tests.Gateway passes 111/111. No test changes needed (Gateway used inline error returns, not in-wrapper throws). Part of #902.
chore(gateway): remove dead ProductionStartup (Startup.Production.cs)
…filter refactor(admin): pilot Tier 1a — OperationLoggingFilter + drop ExecuteAsync wrappers in ModelAuthorController
refactor(gateway): Tier 1a — drop ExecuteAsync wrappers across Gateway controllers
refactor(admin): re-land Tier 1a rollout (30 controllers) into dev — corrective for #935
…se classes (Tier 1a cleanup) All 42 Admin + Gateway controllers are converted off the per-action wrappers (merged to dev), so this deletes the now-unused machinery: AdminControllerBase loses its 3 ExecuteAsync + 2 ExecuteWithNotFoundAsync overloads and the LogOperationSuccess/HandleOperationException/CreateErrorResult helpers; GatewayControllerBase loses its 3 ExecuteAsync overloads and LogOperationSuccess/HandleOpenAIException. Kept: constructors, AdminControllerBase.LogAdminAudit*, GatewayControllerBase.CurrentVirtualKeyId/CurrentVirtualKey/OpenAIError. AdminControllerBaseTests reduced to constructor tests (the wrapper-behavior tests are now covered by AdminExceptionMiddlewareTests). ~800 lines removed. Deliberately kept per-controller [ServiceFilter(typeof(OperationLoggingFilter))] rather than promoting to a global filter: a global filter would add success-logging to the Gateway streaming controllers (Chat/Completions/Images/SignalR) that never had it. EventPublishingControllerBase.IsMutationRequest/LogExceptionWithBodyAsync are now unused by the bases but retained as general protected utilities. Verified: builds 0 errors / 0 new warnings (2 pre-existing NU1903 SQLite warnings); Admin+Gateway test slices pass 643/643. Completes #902.
refactor: remove dead controller-base ExecuteAsync wrappers (Tier 1a cleanup)
…Dto (Tier 2a) Adds a custom InvalidModelStateResponseFactory so automatic [ApiController] DataAnnotation validation failures return the Admin API's standard ErrorResponseDto (Code 'validation_error') instead of the framework-default ValidationProblemDetails, unifying the validation error shape with the rest of the Admin API (AdminExceptionMiddleware and the controller error helpers also emit ErrorResponseDto). The factory is extracted to a static, testable ConduitLLM.Admin.Validation.InvalidModelStateResponse and wired via AddControllers().ConfigureApiBehaviorOptions(...). Re-scoped from the original #904 plan after investigation: there were no ModelState checks and the manual BadRequest/OpenAIError calls are genuine business rules (not annotation re-checks), so nothing redundant to remove. Gateway left as-is (unannotated request DTOs; manual OpenAIError is correct for OpenAI compatibility). NOTE: this changes the Admin 400 body for annotation failures from ValidationProblemDetails to ErrorResponseDto. Verified: Admin builds 0/0; ConduitLLM.Tests.Admin passes 534/534 (incl. 2 new factory tests). Resolves #904.
feat(admin): unify [ApiController] validation errors to ErrorResponseDto (Tier 2a)
…esResponseType(500)] attributes (Tier 2b) Adds DefaultErrorResponsesOperationTransformer (an OpenAPI operation transformer) that documents a 500 response on every Admin operation, then removes the 151 now-redundant per-action [ProducesResponseType(StatusCodes.Status500InternalServerError)] attributes across 22 controllers. Every endpoint can return 500 (the global AdminExceptionMiddleware emits ErrorResponseDto), so it's documented once instead of ~150 times. Scope: Admin only; all 151 were the untyped standalone form (0 typed 500s in Admin). Gateway's 7 typed (OpenAIErrorResponse) 500s left as-is; 400/404/409 attributes kept (meaningful per-endpoint); the code-level StatusCode(500) in ModelController is untouched. Behavior-neutral: [ProducesResponseType] is OpenAPI metadata only. Verified: Admin builds 0/0; ConduitLLM.Tests.Admin 534/534. Transformer compiles against Microsoft.OpenApi v2 (AspNetCore.OpenApi 10.0.9). Could not run the full app, so spot-check /openapi/v1.json for the 500 before merge. Resolves #905.
…ision gate)
Migrates the 6-endpoint ModelAuthorController to a Minimal-API endpoint group (ModelAuthorEndpoints.MapModelAuthorEndpoints): MapGroup('/api/ModelAuthor') + RequireAuthorization + an IEndpointFilter, handlers take deps as parameters. Establishes the reusable conventions a broader migration needs: OperationLoggingEndpointFilter (IEndpointFilter success logging) and AdminAudit (static audit logging, since there's no AdminControllerBase to inherit). Behavior-identical: exceptions still propagate to the global AdminExceptionMiddleware, 404s return the same ErrorResponseDto, audit logs unchanged. Deletes ModelAuthorController.
Decision-gate finding: Minimal APIs are ~LOC-neutral vs an already-cleaned MVC controller here (endpoint group 178 lines vs a post-1a/2b controller ~188) and cost convenience-reimplementation (audit/logging/not-found helpers) + MVC-feature loss ([ApiController] auto-validation, ProducesResponseType OpenAPI). Recommendation: do NOT mass-migrate the 282 endpoints; keep the lean MVC controllers, optionally use Minimal APIs for new simple endpoints. No WebApplicationFactory harness exists, so endpoints are build/unit-verified but not integration-tested.
Verified: Admin builds 0/0; ConduitLLM.Tests.Admin 534/534. Part of #906.
refactor(admin): drop 151 boilerplate [ProducesResponseType(500)] attributes + document 500 globally (Tier 2b)
feat(admin): Tier 3 pilot — ModelAuthor as Minimal-API endpoints (decision gate)
…ts (#905, #906) Closes the two verification gaps flagged when Epic #901 landed: - Tier 2b (#905): unit tests for DefaultErrorResponsesOperationTransformer (adds 500 when absent, initializes null Responses, never overwrites an existing 500) plus a runtime test that boots the OpenAPI pipeline and asserts the generated /openapi/v1.json documents a 500 on every operation. - Tier 3 (#906): HTTP-level integration tests for the Minimal-API ModelAuthorEndpoints (which replaced ModelAuthorController and had no behavior tests) — every branch incl. the throw -> AdminExceptionMiddleware -> ErrorResponseDto mapping (dup-name -> 400 invalid_operation, missing -> 404 not_found, id-mismatch -> 400, create -> 201, update/delete -> 204). Uses the existing in-process TestServer harness pattern (no WebApplicationFactory<Program> — Program runs Postgres migrations inline). Enables Microsoft.AspNetCore.OpenApi.Generated interceptors in the test csproj (required once AddOpenApi() is called in the test assembly). 17 new tests; Admin slice 551/551 green.
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.
No description provided.