using Codex.CQRS.Commands; using Codex.CQRS.Queries; using Codex.Dal.QueryProviders; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; using OpenHarbor.CQRS.Abstractions; using OpenHarbor.CQRS.DynamicQuery.Abstractions; using PoweredSoft.Data.Core; using PoweredSoft.DynamicQuery; using PoweredSoft.DynamicQuery.Core; namespace Codex.Api.Endpoints; /// /// Manual endpoint registration to ensure proper OpenAPI documentation for all CQRS endpoints. /// Required because OpenHarbor.CQRS doesn't auto-generate Swagger docs for commands with return values and dynamic queries. /// public static class ManualEndpointRegistration { public static WebApplication MapCodexEndpoints(this WebApplication app) { // ============================================================ // COMMANDS // ============================================================ // CreateAgent - No return value (already auto-documented by OpenHarbor) // UpdateAgent - No return value (already auto-documented by OpenHarbor) // DeleteAgent - No return value (already auto-documented by OpenHarbor) // CreateConversation - Returns Guid app.MapPost("/api/command/createConversation", async ([FromBody] CreateConversationCommand command, ICommandHandler handler) => { var result = await handler.HandleAsync(command); return Results.Ok(new { id = result }); }) .WithName("CreateConversation") .WithTags("Commands") .WithOpenApi(operation => new(operation) { Summary = "Creates a new conversation for grouping related messages", Description = "Returns the newly created conversation ID", RequestBody = new OpenApiRequestBody { Required = true, Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(CreateConversationCommand) } } } } }, Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Conversation created successfully", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["id"] = new OpenApiSchema { Type = "string", Format = "uuid", Description = "The unique identifier of the created conversation" } } } } } }, ["400"] = new OpenApiResponse { Description = "Validation failed" }, ["500"] = new OpenApiResponse { Description = "Internal server error" } } }) .Produces(200) .ProducesValidationProblem(); // StartAgentExecution - Returns Guid app.MapPost("/api/command/startAgentExecution", async ([FromBody] StartAgentExecutionCommand command, ICommandHandler handler) => { var result = await handler.HandleAsync(command); return Results.Ok(new { id = result }); }) .WithName("StartAgentExecution") .WithTags("Commands") .WithOpenApi(operation => new(operation) { Summary = "Starts a new agent execution with a user prompt", Description = "Creates an execution record and returns its ID. Use this to track agent runs.", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Execution started successfully", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["id"] = new OpenApiSchema { Type = "string", Format = "uuid", Description = "The unique identifier of the execution" } } } } } }, ["400"] = new OpenApiResponse { Description = "Validation failed" }, ["404"] = new OpenApiResponse { Description = "Agent not found" }, ["500"] = new OpenApiResponse { Description = "Internal server error" } } }) .Produces(200) .ProducesValidationProblem(); // CompleteAgentExecution - No return value app.MapPost("/api/command/completeAgentExecution", async ([FromBody] CompleteAgentExecutionCommand command, ICommandHandler handler) => { await handler.HandleAsync(command); return Results.Ok(); }) .WithName("CompleteAgentExecution") .WithTags("Commands") .WithOpenApi(operation => new(operation) { Summary = "Marks an agent execution as completed with results", Description = "Updates execution status, tokens used, and stores the response", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Execution completed successfully" }, ["400"] = new OpenApiResponse { Description = "Validation failed" }, ["404"] = new OpenApiResponse { Description = "Execution not found" }, ["500"] = new OpenApiResponse { Description = "Internal server error" } } }) .Produces(200) .ProducesValidationProblem(); // ============================================================ // QUERIES // ============================================================ // Health - Already auto-documented // GetAgent - Already auto-documented // GetAgentExecution app.MapPost("/api/query/getAgentExecution", async ([FromBody] GetAgentExecutionQuery query, IQueryHandler handler) => { var result = await handler.HandleAsync(query); return result != null ? Results.Ok(result) : Results.NotFound(); }) .WithName("GetAgentExecution") .WithTags("Queries") .WithOpenApi(operation => new(operation) { Summary = "Get details of a specific agent execution by ID", Description = "Returns execution details including tokens, cost, messages, and status", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Execution details retrieved successfully", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(AgentExecutionDetails) } } } } }, ["404"] = new OpenApiResponse { Description = "Execution not found" }, ["500"] = new OpenApiResponse { Description = "Internal server error" } } }) .Produces(200) .Produces(404); // GetConversation app.MapPost("/api/query/getConversation", async ([FromBody] GetConversationQuery query, IQueryHandler handler) => { var result = await handler.HandleAsync(query); return result != null ? Results.Ok(result) : Results.NotFound(); }) .WithName("GetConversation") .WithTags("Queries") .WithOpenApi(operation => new(operation) { Summary = "Get details of a specific conversation by ID", Description = "Returns conversation details including messages and execution history", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Conversation details retrieved successfully", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(ConversationDetails) } } } } }, ["404"] = new OpenApiResponse { Description = "Conversation not found" }, ["500"] = new OpenApiResponse { Description = "Internal server error" } } }) .Produces(200) .Produces(404); // ============================================================ // DYNAMIC QUERIES (Paginated Lists) // ============================================================ // NOTE: Dynamic queries are auto-registered by OpenHarbor but not auto-documented. // They work via /api/dynamicquery/{ItemType} but aren't in Swagger without manual registration. // The endpoints exist and function - frontend can use them directly from openapi.json examples below. // Manual registration disabled for now - OpenHarbor handles these automatically // TODO: Add proper schema documentation for dynamic query request/response /* // ListAgents app.MapPost("/api/dynamicquery/ListAgentsQueryItem", async (HttpContext context, IQueryableProvider provider, IAsyncQueryableService queryService) => { var query = await context.Request.ReadFromJsonAsync(); var queryable = await provider.GetQueryableAsync(query!); var result = await queryService.ExecuteAsync(queryable, query!); return Results.Ok(result); }) .WithName("ListAgents") .WithTags("DynamicQuery") .WithOpenApi(operation => new(operation) { Summary = "List agents with filtering, sorting, and pagination", Description = @"Dynamic query endpoint supporting: - **Filtering**: Filter by any property (Name, Type, Status, etc.) - **Sorting**: Sort by one or multiple properties - **Pagination**: Page and PageSize parameters - **Aggregates**: Count, Sum, Average on numeric fields ### Example Request ```json { ""filters"": [ { ""path"": ""Name"", ""operator"": ""Contains"", ""value"": ""search"" }, { ""path"": ""Status"", ""operator"": ""Equal"", ""value"": ""Active"" } ], ""sorts"": [{ ""path"": ""CreatedAt"", ""descending"": true }], ""page"": 1, ""pageSize"": 20 } ```", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Paginated list of agents", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["data"] = new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(ListAgentsQueryItem) } } }, ["page"] = new OpenApiSchema { Type = "integer" }, ["pageSize"] = new OpenApiSchema { Type = "integer" }, ["totalCount"] = new OpenApiSchema { Type = "integer" } } } } } } } }) .Produces(200); // ListConversations app.MapPost("/api/dynamicquery/ListConversationsQueryItem", async (HttpContext context, IQueryableProvider provider, IAsyncQueryableService queryService) => { var query = await context.Request.ReadFromJsonAsync(); var queryable = await provider.GetQueryableAsync(query!); var result = await queryService.ExecuteAsync(queryable, query!); return Results.Ok(result); }) .WithName("ListConversations") .WithTags("DynamicQuery") .WithOpenApi(operation => new(operation) { Summary = "List conversations with filtering, sorting, and pagination", Description = "Returns paginated conversations with message counts and metadata", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Paginated list of conversations", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["data"] = new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(ListConversationsQueryItem) } } }, ["totalCount"] = new OpenApiSchema { Type = "integer" } } } } } } } }) .Produces(200); // ListAgentExecutions app.MapPost("/api/dynamicquery/ListAgentExecutionsQueryItem", async (HttpContext context, IQueryableProvider provider, IAsyncQueryableService queryService) => { var query = await context.Request.ReadFromJsonAsync(); var queryable = await provider.GetQueryableAsync(query!); var result = await queryService.ExecuteAsync(queryable, query!); return Results.Ok(result); }) .WithName("ListAgentExecutions") .WithTags("DynamicQuery") .WithOpenApi(operation => new(operation) { Summary = "List agent executions with filtering, sorting, and pagination", Description = "Returns paginated execution history with tokens, costs, and status", Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Paginated list of executions", Content = new Dictionary { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary { ["data"] = new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(ListAgentExecutionsQueryItem) } } }, ["totalCount"] = new OpenApiSchema { Type = "integer" } } } } } } } }) .Produces(200); */ return app; } }