From 3b40e35ba3ff2bdabf7f676eb2cb6baf6ea0797a Mon Sep 17 00:00:00 2001 From: Robert Good Date: Sat, 7 Feb 2026 23:23:43 -0800 Subject: [PATCH 1/5] flipped to RLS --- README.md | 19 +- data/Chat/Actor.sql | 1 + src/.github/copilot-instructions.md | 4 + .../Abstractions/ICurrentUserContext.cs | 7 + .../Abstractions/IRequiresUserContext.cs | 20 + .../Abstractions/IUserInfoRequest.cs | 16 - .../Actor/CreateActorCommand.cs | 4 +- .../Actor/DeleteActorByExternalIdCommand.cs | 2 +- .../Actor/DeleteActorCommand.cs | 2 +- .../Actor/GetActorChatSessionQuery.cs | 2 +- .../GetActorChatSessionsPaginatedQuery.cs | 2 +- .../Actor/GetActorChatSessionsQuery.cs | 2 +- src/Core.Application/Actor/GetActorQuery.cs | 2 +- src/Core.Application/Actor/GetMyActorQuery.cs | 8 +- .../Actor/GetMyActorQueryValidator.cs | 2 +- .../Actor/SaveMyActorCommand.cs | 10 +- .../Actor/SaveMyActorCommandValidator.cs | 2 +- .../Actor/UpdateActorCommand.cs | 2 +- .../CreateChatMessageCommandValidator.cs | 10 - .../CreateChatSessionCommandValidator.cs | 9 - ...mmand.cs => CreateMyChatMessageCommand.cs} | 41 +- .../CreateMyChatMessageCommandValidator.cs | 24 + ...mmand.cs => CreateMyChatSessionCommand.cs} | 66 +- .../CreateMyChatSessionCommandValidator.cs | 21 + .../DeleteChatSessionCommand.cs | 30 - .../DeleteChatSessionCommandValidator.cs | 9 - .../DeleteMyChatSessionCommand.cs | 50 + .../DeleteMyChatSessionCommandValidator.cs | 21 + .../ChatCompletion/GetChatMessageQuery.cs | 30 - .../GetChatMessageQueryValidator.cs | 9 - .../GetChatMessagesPaginatedQuery.cs | 30 - .../GetChatMessagesPaginatedQueryValidator.cs | 20 - .../ChatCompletion/GetChatMessagesQuery.cs | 26 - .../GetChatMessagesQueryValidator.cs | 16 - .../ChatCompletion/GetChatSessionQuery.cs | 30 - .../GetChatSessionQueryValidator.cs | 9 - .../GetChatSessionsPaginatedQuery.cs | 30 - .../GetChatSessionsPaginatedQueryValidator.cs | 20 - .../ChatCompletion/GetChatSessionsQuery.cs | 26 - .../GetChatSessionsQueryValidator.cs | 16 - .../ChatCompletion/GetMyChatMessageQuery.cs | 50 + .../GetMyChatMessageQueryValidator.cs | 21 + .../GetMyChatMessagesPaginatedQuery.cs | 46 + ...etMyChatMessagesPaginatedQueryValidator.cs | 34 + .../ChatCompletion/GetMyChatSessionQuery.cs | 43 +- .../GetMyChatSessionQueryValidator.cs | 15 +- .../GetMyChatSessionsPaginatedQuery.cs | 27 +- ...etMyChatSessionsPaginatedQueryValidator.cs | 24 +- .../ChatCompletion/GetMyChatSessionsQuery.cs | 33 +- .../GetMyChatSessionsQueryValidator.cs | 18 +- .../PatchChatSessionCommandValidator.cs | 9 - ...ommand.cs => PatchMyChatSessionCommand.cs} | 29 +- .../PatchMyChatSessionCommandValidator.cs | 24 + .../UpdateChatSessionCommand.cs | 33 - .../UpdateChatSessionCommandValidator.cs | 10 - .../CustomForbiddenAccessException.cs | 2 +- src/Core.Application/Core.Application.csproj | 4 +- src/Core.Domain/Actor/ActorEntity.cs | 6 +- src/Core.Domain/Auth/IUserContext.cs | 19 + src/Core.Domain/Auth/IUserEntity.cs | 14 - .../Auth/{UserEntity.cs => UserContext.cs} | 11 +- .../ChatCompletion/ChatMessageEntity.cs | 2 +- .../ChatCompletion/ChatSessionEntity.cs | 7 +- src/Core.Domain/Core.Domain.csproj | 2 +- .../Tools/ActorsTool.cs | 8 +- .../Tools/ChatSessionsTool.cs | 2 +- ... 20260208060746_InitialCreate.Designer.cs} | 27 +- ...ate.cs => 20260208060746_InitialCreate.cs} | 25 +- .../AgentFrameworkContextModelSnapshot.cs | 25 +- .../Persistence/AgentFrameworkContext.cs | 43 +- .../Clients/BackendApiClient.g.cs | 1518 ++++------------- .../ConfigureServicesAuth.cs | 3 - .../Pages/Chat/Services/ChatService.cs | 15 +- .../Presentation.Blazor.csproj | 4 +- .../Services/UserSyncService.cs | 10 +- .../Auth/ClaimsCurrentUserContext.cs | 32 + .../Auth/ClaimsUserInfo.cs | 32 +- .../Auth/ConfigureServices.cs | 19 +- .../Auth/IClaimsUserInfo.cs | 32 +- .../Auth/UserInfoBehavior.cs | 70 +- .../ChatCompletion/ChatSessionController.cs | 241 --- ...ntroller.cs => MyChatMessageController.cs} | 72 +- .../ChatCompletion/MyChatSessionController.cs | 56 +- .../Common/ApiControllerBase.cs | 4 +- .../Presentation.WebApi.csproj | 4 +- .../CreateActorCommandStepDefinitions.cs | 4 +- .../DeleteActorCommandStepDefinitions.cs | 7 +- ...feature => GetActorByOwnerIdQuery.feature} | 0 ...e.cs => GetActorByOwnerIdQuery.feature.cs} | 4 +- ... GetActorByOwnerIdQueryStepDefinitions.cs} | 8 +- .../Actor/GetActorChatSessionQuery.feature | 20 - .../Actor/GetActorChatSessionQuery.feature.cs | 187 -- ...GetActorChatSessionQueryStepDefinitions.cs | 96 -- ...GetActorChatSessionsPaginatedQuery.feature | 35 - ...ActorChatSessionsPaginatedQuery.feature.cs | 231 --- ...atSessionsPaginatedQueryStepDefinitions.cs | 179 -- .../Actor/GetActorChatSessionsQuery.feature | 27 - .../GetActorChatSessionsQuery.feature.cs | 208 --- ...etActorChatSessionsQueryStepDefinitions.cs | 132 -- .../Actor/GetActorQueryStepDefinitions.cs | 10 +- .../Actor/SaveActorCommandStepDefinitions.cs | 6 +- .../UpdateActorCommandStepDefinitions.cs | 6 +- ...CreateChatMessageCommandStepDefinitions.cs | 9 +- ...CreateChatSessionCommandStepDefinitions.cs | 16 +- ...DeleteChatSessionCommandStepDefinitions.cs | 7 +- .../GetChatMessageQueryStepDefinitions.cs | 7 +- ...atMessagesPaginatedQueryStepDefinitions.cs | 7 +- .../GetChatMessagesQuery.feature | 26 - .../GetChatMessagesQuery.feature.cs | 202 --- .../GetChatMessagesQueryStepDefinitions.cs | 120 -- ....feature => GetMyChatSessionQuery.feature} | 0 ...re.cs => GetMyChatSessionQuery.feature.cs} | 4 +- ...> GetMyChatSessionQueryStepDefinitions.cs} | 11 +- ...> GetMyChatSessionsPaginatedQuery.feature} | 0 ...etMyChatSessionsPaginatedQuery.feature.cs} | 4 +- ...tSessionsPaginatedQueryStepDefinitions.cs} | 11 +- ...feature => GetMyChatSessionsQuery.feature} | 0 ...e.cs => GetMyChatSessionsQuery.feature.cs} | 4 +- ... GetMyChatSessionsQueryStepDefinitions.cs} | 11 +- .../PatchChatSessionCommandStepDefinitions.cs | 7 +- .../UpdateChatSessionCommand.feature | 19 - .../UpdateChatSessionCommand.feature.cs | 182 -- ...UpdateChatSessionCommandStepDefinitions.cs | 77 - src/Tests.Integration/TestBase.cs | 21 +- src/Tests.Integration/TestUserInfo.cs | 5 +- .../Tests.Integration.csproj | 4 +- 126 files changed, 1353 insertions(+), 3936 deletions(-) create mode 100644 data/Chat/Actor.sql create mode 100644 src/.github/copilot-instructions.md create mode 100644 src/Core.Application/Abstractions/ICurrentUserContext.cs create mode 100644 src/Core.Application/Abstractions/IRequiresUserContext.cs delete mode 100644 src/Core.Application/Abstractions/IUserInfoRequest.cs delete mode 100644 src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs rename src/Core.Application/ChatCompletion/{CreateChatMessageCommand.cs => CreateMyChatMessageCommand.cs} (65%) create mode 100644 src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs rename src/Core.Application/ChatCompletion/{CreateChatSessionCommand.cs => CreateMyChatSessionCommand.cs} (61%) create mode 100644 src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs delete mode 100644 src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs create mode 100644 src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs create mode 100644 src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatMessageQuery.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatSessionQuery.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs delete mode 100644 src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs create mode 100644 src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs create mode 100644 src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs create mode 100644 src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs create mode 100644 src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs rename src/Core.Application/ChatCompletion/{PatchChatSessionCommand.cs => PatchMyChatSessionCommand.cs} (51%) create mode 100644 src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs delete mode 100644 src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs delete mode 100644 src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs create mode 100644 src/Core.Domain/Auth/IUserContext.cs delete mode 100644 src/Core.Domain/Auth/IUserEntity.cs rename src/Core.Domain/Auth/{UserEntity.cs => UserContext.cs} (68%) rename src/Infrastructure.SqlServer/Migrations/{20260204064841_InitialCreate.Designer.cs => 20260208060746_InitialCreate.Designer.cs} (92%) rename src/Infrastructure.SqlServer/Migrations/{20260204064841_InitialCreate.cs => 20260208060746_InitialCreate.cs} (91%) create mode 100644 src/Presentation.WebApi/Auth/ClaimsCurrentUserContext.cs delete mode 100644 src/Presentation.WebApi/ChatCompletion/ChatSessionController.cs rename src/Presentation.WebApi/ChatCompletion/{ChatMessageController.cs => MyChatMessageController.cs} (79%) rename src/Tests.Integration/Actor/{GetActorByExternalIdQuery.feature => GetActorByOwnerIdQuery.feature} (100%) rename src/Tests.Integration/Actor/{GetActorByExternalIdQuery.feature.cs => GetActorByOwnerIdQuery.feature.cs} (98%) rename src/Tests.Integration/Actor/{GetActorByExternalIdQueryStepDefinitions.cs => GetActorByOwnerIdQueryStepDefinitions.cs} (88%) delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionQuery.feature delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionQuery.feature.cs delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionQueryStepDefinitions.cs delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature.cs delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQueryStepDefinitions.cs delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature.cs delete mode 100644 src/Tests.Integration/Actor/GetActorChatSessionsQueryStepDefinitions.cs delete mode 100644 src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature delete mode 100644 src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature.cs delete mode 100644 src/Tests.Integration/ChatCompletion/GetChatMessagesQueryStepDefinitions.cs rename src/Tests.Integration/ChatCompletion/{GetChatSessionQuery.feature => GetMyChatSessionQuery.feature} (100%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionQuery.feature.cs => GetMyChatSessionQuery.feature.cs} (98%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionQueryStepDefinitions.cs => GetMyChatSessionQueryStepDefinitions.cs} (90%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionsPaginatedQuery.feature => GetMyChatSessionsPaginatedQuery.feature} (100%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionsPaginatedQuery.feature.cs => GetMyChatSessionsPaginatedQuery.feature.cs} (98%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionsPaginatedQueryStepDefinitions.cs => GetMyChatSessionsPaginatedQueryStepDefinitions.cs} (93%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionsQuery.feature => GetMyChatSessionsQuery.feature} (100%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionsQuery.feature.cs => GetMyChatSessionsQuery.feature.cs} (98%) rename src/Tests.Integration/ChatCompletion/{GetChatSessionsQueryStepDefinitions.cs => GetMyChatSessionsQueryStepDefinitions.cs} (91%) delete mode 100644 src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature delete mode 100644 src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature.cs delete mode 100644 src/Tests.Integration/ChatCompletion/UpdateChatSessionCommandStepDefinitions.cs diff --git a/README.md b/README.md index 4a30ecb..44dd814 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Web & API & SQL CI/CD](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql.yml/badge.svg)](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql.yml) -Microsoft Agent Framework Quick-start is a 100% Microsoft, enterprise-ready starter kit for building modern, agentic applications with C#, Blazor (Fluent UI), and ASP.NET Core Web API. This solution demonstrates how to use the Microsoft Agent Framework to create a Copilot-style chat client, fully integrated with SQL Server for persistent storage of authors, chat sessions, and messages—all orchestrated through a clean architecture pattern. +Microsoft Agent Framework Quick-start is a enterprise-ready starter kit for building modern, agentic applications with C#, Blazor (Fluent UI), and ASP.NET Core Web API. This solution demonstrates how to use the Microsoft Agent Framework to create a Copilot-style chat client, fully integrated with SQL Server for persistent storage of authors, chat sessions, and messages—all orchestrated through a clean architecture pattern. With built-in tools (plugins) for querying and managing your own data, automated Azure infrastructure (Bicep), and seamless CI/CD (GitHub Actions), this repo provides everything you need to build, deploy, and extend real-world AI-powered apps on a traditional .NET stack—no JavaScript, no raw HTML, just pure Blazor and Fluent UI. Perfect for teams looking to modernize with AI while leveraging familiar, pragmatic enterprise patterns. @@ -257,26 +257,15 @@ dotnet user-secrets set "ConnectionStrings:DefaultConnection" "YOUR_SQL_CONNECTI 1. Open Windows Terminal in Powershell or Cmd mode 2. cd to root of repository -3. (Optional) If you have an existing database, scaffold current entities into your project - - ``` - dotnet ef dbcontext scaffold "Data Source=localhost;Initial Catalog=AgentFramework;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;" Microsoft.EntityFrameworkCore.SqlServer -t WeatherForecastView -c WeatherChannelContext -f -o WebApi - ``` - -4. Create an initial migration - ``` - dotnet ef migrations add InitialCreate --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext - ``` - -5. Develop new entities and configurations -6. When ready to deploy new entities and configurations +3. Deploy new entities and configurations to database ``` dotnet ef database update --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext --connection "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=AgentFramework;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30" ``` -7. When an entity changes, is created or deleted, create a new migration. Suggest doing this each new version. +4. When an entity changes, is created or deleted, create a new migration. Suggest doing this each new version. ``` dotnet ef migrations add v1.1.1 --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext + dotnet ef database update --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext --connection "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=AgentFramework;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30" ``` # Running the Application diff --git a/data/Chat/Actor.sql b/data/Chat/Actor.sql new file mode 100644 index 0000000..1b1d451 --- /dev/null +++ b/data/Chat/Actor.sql @@ -0,0 +1 @@ +-- Write your own SQL object definition here, and it'll be included in your package. diff --git a/src/.github/copilot-instructions.md b/src/.github/copilot-instructions.md new file mode 100644 index 0000000..35edb6d --- /dev/null +++ b/src/.github/copilot-instructions.md @@ -0,0 +1,4 @@ +# Copilot Instructions + +## General Guidelines +- Use custom .NotEmpty("message") validation convention instead of .NotEmpty().WithMessage("message") in FluentValidation validators. The Goodtocode.Validation library extends FluentValidation with custom syntax where validation methods accept message as a parameter: .NotEmpty("message"), .NotEqual(value, "message"), etc. Do not use .WithMessage() - pass the message directly as a parameter to the validation method. \ No newline at end of file diff --git a/src/Core.Application/Abstractions/ICurrentUserContext.cs b/src/Core.Application/Abstractions/ICurrentUserContext.cs new file mode 100644 index 0000000..e22974e --- /dev/null +++ b/src/Core.Application/Abstractions/ICurrentUserContext.cs @@ -0,0 +1,7 @@ +namespace Goodtocode.AgentFramework.Core.Application.Abstractions; + +public interface ICurrentUserContext +{ + Guid OwnerId { get; } + Guid TenantId { get; } +} \ No newline at end of file diff --git a/src/Core.Application/Abstractions/IRequiresUserContext.cs b/src/Core.Application/Abstractions/IRequiresUserContext.cs new file mode 100644 index 0000000..f7d031d --- /dev/null +++ b/src/Core.Application/Abstractions/IRequiresUserContext.cs @@ -0,0 +1,20 @@ +using Goodtocode.AgentFramework.Core.Domain.Auth; + +namespace Goodtocode.AgentFramework.Core.Application.Abstractions; + +/// +/// Marker interface for requests that require user context to be injected via pipeline behavior. +/// +/// This interface is used to identify requests that need authenticated user information. +/// When a request implements this interface, the UserInfoBehavior pipeline will automatically +/// populate the property with the current user's context before +/// the request handler executes. +public interface IRequiresUserContext +{ + /// + /// Gets or sets the authenticated user's context. + /// + /// This property is automatically populated by the pipeline behavior + /// before the request handler is invoked. + IUserContext? UserContext { get; set; } +} diff --git a/src/Core.Application/Abstractions/IUserInfoRequest.cs b/src/Core.Application/Abstractions/IUserInfoRequest.cs deleted file mode 100644 index ffb011f..0000000 --- a/src/Core.Application/Abstractions/IUserInfoRequest.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Goodtocode.AgentFramework.Core.Domain.Auth; - -namespace Goodtocode.AgentFramework.Core.Application.Abstractions; - -/// -/// Represents a request containing user information. -/// -/// This interface is used to encapsulate user information in a request. The property allows getting or setting the associated user details. -public interface IUserInfoRequest -{ - /// - /// Gets or sets the user information associated with the current context. - /// - IUserEntity? UserInfo { get; set; } -} diff --git a/src/Core.Application/Actor/CreateActorCommand.cs b/src/Core.Application/Actor/CreateActorCommand.cs index 947c171..de69dc6 100644 --- a/src/Core.Application/Actor/CreateActorCommand.cs +++ b/src/Core.Application/Actor/CreateActorCommand.cs @@ -14,7 +14,7 @@ public class CreateActorCommand : IRequest public Guid TenantId { get; set; } } -public class CreateAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class CreateActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; @@ -23,7 +23,7 @@ public async Task Handle(CreateActorCommand request, CancellationToken GuardAgainstEmptyOwnerId(request?.OwnerId); GuardAgainstIdExists(_context.Actors, request!.Id); - var Actor = ActorEntity.Create(request!.Id == Guid.Empty ? Guid.NewGuid() : request!.Id, request.OwnerId, request.TenantId, request.FirstName, request.LastName, request.Email); + var Actor = ActorEntity.Create(request!.Id == Guid.Empty ? Guid.NewGuid() : request!.Id, request.FirstName, request.LastName, request.Email); _context.Actors.Add(Actor); try { diff --git a/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs b/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs index c42fe6f..024a12a 100644 --- a/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs +++ b/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs @@ -9,7 +9,7 @@ public class DeleteActorByOwnerIdCommand : IRequest public Guid OwnerId { get; set; } } -public class DeleteAuthorByOwnerIdCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class DeleteActorByOwnerIdCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/Actor/DeleteActorCommand.cs b/src/Core.Application/Actor/DeleteActorCommand.cs index bcfafa8..57c6714 100644 --- a/src/Core.Application/Actor/DeleteActorCommand.cs +++ b/src/Core.Application/Actor/DeleteActorCommand.cs @@ -9,7 +9,7 @@ public class DeleteActorCommand : IRequest public Guid Id { get; set; } } -public class DeleteAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class DeleteActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/Actor/GetActorChatSessionQuery.cs b/src/Core.Application/Actor/GetActorChatSessionQuery.cs index ac3ae26..f0885a2 100644 --- a/src/Core.Application/Actor/GetActorChatSessionQuery.cs +++ b/src/Core.Application/Actor/GetActorChatSessionQuery.cs @@ -11,7 +11,7 @@ public class GetActorChatSessionQuery : IRequest public Guid ChatSessionId { get; set; } } -public class GetAuthorChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetActorChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs b/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs index b39c80d..15ce0db 100644 --- a/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs +++ b/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs @@ -14,7 +14,7 @@ public class GetActorChatSessionsPaginatedQuery : IRequest> +public class GetActorChatSessionsPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler> { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/Actor/GetActorChatSessionsQuery.cs b/src/Core.Application/Actor/GetActorChatSessionsQuery.cs index b424343..20cf0ae 100644 --- a/src/Core.Application/Actor/GetActorChatSessionsQuery.cs +++ b/src/Core.Application/Actor/GetActorChatSessionsQuery.cs @@ -10,7 +10,7 @@ public class GetActorChatSessionsQuery : IRequest> public DateTime? EndDate { get; set; } } -public class GetAuthorChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler> +public class GetActorChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler> { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/Actor/GetActorQuery.cs b/src/Core.Application/Actor/GetActorQuery.cs index 0ef2316..98596a2 100644 --- a/src/Core.Application/Actor/GetActorQuery.cs +++ b/src/Core.Application/Actor/GetActorQuery.cs @@ -9,7 +9,7 @@ public class GetActorQuery : IRequest public Guid ActorId { get; set; } } -public class GetAuthorQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetActorQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/Actor/GetMyActorQuery.cs b/src/Core.Application/Actor/GetMyActorQuery.cs index d152ea2..058e17b 100644 --- a/src/Core.Application/Actor/GetMyActorQuery.cs +++ b/src/Core.Application/Actor/GetMyActorQuery.cs @@ -5,18 +5,18 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class GetMyActorQuery : IRequest, IUserInfoRequest +public class GetMyActorQuery : IRequest, IRequiresUserContext { - public IUserEntity? UserInfo { get; set; } + public IUserContext? UserContext { get; set; } } -public class GetAuthorByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetActorByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; public async Task Handle(GetMyActorQuery request, CancellationToken cancellationToken) { - var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserInfo!.OwnerId).FirstOrDefaultAsync(cancellationToken: cancellationToken); + var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserContext!.OwnerId).FirstOrDefaultAsync(cancellationToken: cancellationToken); GuardAgainstNotFound(actor); return ActorDto.CreateFrom(actor); diff --git a/src/Core.Application/Actor/GetMyActorQueryValidator.cs b/src/Core.Application/Actor/GetMyActorQueryValidator.cs index 65469c3..53c6117 100644 --- a/src/Core.Application/Actor/GetMyActorQueryValidator.cs +++ b/src/Core.Application/Actor/GetMyActorQueryValidator.cs @@ -4,6 +4,6 @@ public class GetMyActorQueryValidator : Validator { public GetMyActorQueryValidator() { - RuleFor(x => x.UserInfo).NotEmpty(); + RuleFor(x => x.UserContext).NotEmpty(); } } \ No newline at end of file diff --git a/src/Core.Application/Actor/SaveMyActorCommand.cs b/src/Core.Application/Actor/SaveMyActorCommand.cs index 3ad8303..0cd0937 100644 --- a/src/Core.Application/Actor/SaveMyActorCommand.cs +++ b/src/Core.Application/Actor/SaveMyActorCommand.cs @@ -4,16 +4,16 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class SaveMyActorCommand : IRequest, IUserInfoRequest +public class SaveMyActorCommand : IRequest, IRequiresUserContext { public string? FirstName { get; set; } public string? LastName { get; set; } public string? Email { get; set; } public Guid TenantId { get; set; } - public IUserEntity? UserInfo { get; set; } + public IUserContext? UserContext { get; set; } } -public class SaveAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class SaveActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; @@ -21,7 +21,7 @@ public async Task Handle(SaveMyActorCommand request, CancellationToken { GuardAgainstEmptyTenantId(request?.TenantId); - var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserInfo!.OwnerId && x.TenantId == request.TenantId).FirstOrDefaultAsync(cancellationToken); + var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserContext!.OwnerId && x.TenantId == request.TenantId).FirstOrDefaultAsync(cancellationToken); if (actor is not null) { actor.Update(request?.FirstName, request?.LastName ?? actor.LastName, request?.Email); @@ -29,7 +29,7 @@ public async Task Handle(SaveMyActorCommand request, CancellationToken } else { - actor = ActorEntity.Create(Guid.NewGuid(), request!.UserInfo!.OwnerId, request.TenantId, request.FirstName, request.LastName, request.Email); + actor = ActorEntity.Create(Guid.NewGuid(), request?.FirstName, request?.LastName, request?.Email); _context.Actors.Add(actor); } diff --git a/src/Core.Application/Actor/SaveMyActorCommandValidator.cs b/src/Core.Application/Actor/SaveMyActorCommandValidator.cs index 955ad78..2d19fb9 100644 --- a/src/Core.Application/Actor/SaveMyActorCommandValidator.cs +++ b/src/Core.Application/Actor/SaveMyActorCommandValidator.cs @@ -4,7 +4,7 @@ public class SaveMyActorCommandValidator : Validator { public SaveMyActorCommandValidator() { - RuleFor(x => x.UserInfo).NotEmpty(); + RuleFor(x => x.UserContext).NotEmpty(); RuleFor(x => x.TenantId).NotEmpty(); } } \ No newline at end of file diff --git a/src/Core.Application/Actor/UpdateActorCommand.cs b/src/Core.Application/Actor/UpdateActorCommand.cs index e3866ee..3f745c5 100644 --- a/src/Core.Application/Actor/UpdateActorCommand.cs +++ b/src/Core.Application/Actor/UpdateActorCommand.cs @@ -10,7 +10,7 @@ public class UpdateActorCommand : IRequest public string Name { get; set; } = string.Empty; } -public class UpdateAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class UpdateActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; diff --git a/src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs deleted file mode 100644 index 0e4ec20..0000000 --- a/src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class CreateChatMessageCommandValidator : Validator -{ - public CreateChatMessageCommandValidator() - { - RuleFor(x => x.ChatSessionId).NotEmpty(); - RuleFor(x => x.Message).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs deleted file mode 100644 index 5763664..0000000 --- a/src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class CreateChatSessionCommandValidator : Validator -{ - public CreateChatSessionCommandValidator() - { - RuleFor(x => x.Message).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/CreateChatMessageCommand.cs b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs similarity index 65% rename from src/Core.Application/ChatCompletion/CreateChatMessageCommand.cs rename to src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs index d75107a..c2d2730 100644 --- a/src/Core.Application/ChatCompletion/CreateChatMessageCommand.cs +++ b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs @@ -7,28 +7,28 @@ namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; -public class CreateChatMessageCommand : IRequest, IUserInfoRequest +public class CreateMyChatMessageCommand : IRequest, IRequiresUserContext { public Guid Id { get; set; } public Guid ChatSessionId { get; set; } public string? Message { get; set; } - public IUserEntity? UserInfo { get; set; } + public IUserContext? UserContext { get; set; } } -public class CreateChatMessageCommandHandler(AIAgent agent, IAgentFrameworkContext context) : IRequestHandler +public class CreateChatMessageCommandHandler(AIAgent agent, IAgentFrameworkContext context) : IRequestHandler { private readonly AIAgent _agent = agent; private readonly IAgentFrameworkContext _context = context; - public async Task Handle(CreateChatMessageCommand request, CancellationToken cancellationToken) + public async Task Handle(CreateMyChatMessageCommand request, CancellationToken cancellationToken) { - GuardAgainstSessionNotFound(_context.ChatSessions, request!.ChatSessionId); GuardAgainstEmptyMessage(request?.Message); GuardAgainstIdExists(_context.ChatMessages, request!.Id); - GuardAgainstEmptyUser(request?.UserInfo); - GuardAgainstUnauthorizedUser(_context.ChatSessions, request!.UserInfo!); + GuardAgainstEmptyUser(request?.UserContext); - var chatSession = _context.ChatSessions.Find(request.ChatSessionId); + var chatSession = _context.ChatSessions.Find(request!.ChatSessionId); + GuardAgainstSessionNotFound(chatSession); + GuardAgainstUnauthorizedUser(chatSession!, request.UserContext!); var chatHistory = new List(); foreach (ChatMessageEntity message in chatSession!.Messages) @@ -69,13 +69,10 @@ public async Task Handle(CreateChatMessageCommand request, Cance return ChatMessageDto.CreateFrom(chatMessage); } - private static void GuardAgainstSessionNotFound(DbSet dbSet, Guid sessionId) + private static void GuardAgainstSessionNotFound(ChatSessionEntity? chatSession) { - if (sessionId != Guid.Empty && !dbSet.Any(x => x.Id == sessionId)) - throw new CustomValidationException( - [ - new("ChatSessionId", "Chat Session does not exist") - ]); + if (chatSession == null) + throw new CustomNotFoundException("Chat Session Not Found"); } private static void GuardAgainstEmptyMessage(string? message) @@ -93,23 +90,19 @@ private static void GuardAgainstIdExists(DbSet dbSet, Guid id throw new CustomConflictException("Id already exists"); } - private static void GuardAgainstEmptyUser(IUserEntity? userInfo) + private static void GuardAgainstEmptyUser(IUserContext? userContext) { - if (userInfo == null || userInfo.OwnerId == Guid.Empty || userInfo.TenantId == Guid.Empty) + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) throw new CustomValidationException( [ - new("UserInfo", "User information is required to create a chat message") + new("UserContext", "User information is required to create a chat message") ]); } - private static void GuardAgainstUnauthorizedUser(DbSet dbSet, IUserEntity userInfo) + private static void GuardAgainstUnauthorizedUser(ChatSessionEntity chatSession, IUserContext userContext) { - bool isAuthorized = dbSet.Any(x => x.Actor != null && x.Actor.OwnerId == userInfo.OwnerId); - if (!isAuthorized) - throw new CustomValidationException( - [ - new("UserInfo", "User is not authorized to create a chat message in this session") - ]); + if (chatSession.OwnerId != userContext.OwnerId) + throw new CustomForbiddenAccessException("ChatSession", chatSession.Id); } private static void GuardAgainstNullAgentResponse(ChatMessage? response) diff --git a/src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs new file mode 100644 index 0000000..e4eba49 --- /dev/null +++ b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs @@ -0,0 +1,24 @@ +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class CreateMyChatMessageCommandValidator : Validator +{ + public CreateMyChatMessageCommandValidator() + { + RuleFor(x => x.ChatSessionId) + .NotEmpty("ChatSessionId is required"); + + RuleFor(x => x.Message) + .NotEmpty("Message is required"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/CreateChatSessionCommand.cs b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs similarity index 61% rename from src/Core.Application/ChatCompletion/CreateChatSessionCommand.cs rename to src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs index 4b7efcb..72e6580 100644 --- a/src/Core.Application/ChatCompletion/CreateChatSessionCommand.cs +++ b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs @@ -1,34 +1,53 @@ using Goodtocode.AgentFramework.Core.Application.Abstractions; using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; using Goodtocode.AgentFramework.Core.Domain.Actor; +using Goodtocode.AgentFramework.Core.Domain.Auth; using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; +using Goodtocode.Domain.Entities; using Microsoft.Agents.AI; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; -public class CreateChatSessionCommand : IRequest +public class CreateMyChatSessionCommand : IRequest, IRequiresUserContext { public Guid Id { get; set; } - public Guid ActorId { get; set; } public string? Title { get; set; } public string? Message { get; set; } + public IUserContext? UserContext { get; set; } } -public class CreateChatSessionCommandHandler(AIAgent kernel, IAgentFrameworkContext context) : IRequestHandler +public class CreateChatSessionCommandHandler(AIAgent kernel, IAgentFrameworkContext context) : IRequestHandler { private readonly AIAgent _agent = kernel; private readonly IAgentFrameworkContext _context = context; - public async Task Handle(CreateChatSessionCommand request, CancellationToken cancellationToken) + public async Task Handle(CreateMyChatSessionCommand request, CancellationToken cancellationToken) { - GuardAgainstEmtpyActorId(request.ActorId); GuardAgainstEmptyMessage(request?.Message); GuardAgainstIdExists(_context.ChatSessions, request!.Id); + GuardAgainstEmptyUser(request?.UserContext); + + var actor = await _context.Actors + .FirstOrDefaultAsync(a => a.OwnerId == request!.UserContext!.OwnerId + && a.TenantId == request.UserContext.TenantId, cancellationToken); + + if (actor == null) + { + actor = ActorEntity.Create( + Guid.NewGuid(), + request?.UserContext?.FirstName, + request?.UserContext?.LastName, + request?.UserContext?.Email + ); + _context.Actors.Add(actor); + await _context.SaveChangesAsync(cancellationToken); + } var chatHistory = new List { - new(ChatRole.User, request.Message!) + new(ChatRole.User, request!.Message!) }; var agentResponse = await _agent.RunAsync(chatHistory, cancellationToken: cancellationToken); @@ -36,14 +55,10 @@ public async Task Handle(CreateChatSessionCommand request, Cance GuardAgainstNullAgentResponse(response); - var actor = await _context.Actors - .FirstOrDefaultAsync(x => x.OwnerId == request.ActorId, cancellationToken); - GuardAgainstActorNotFound(actor); - var title = request!.Title ?? $"{request!.Message![..(request.Message!.Length >= 25 ? 25 : request.Message!.Length)]}"; var chatSession = ChatSessionEntity.Create( request.Id, - actor!.Id, + actor.Id, title, Enum.TryParse(response!.Role.ToString().ToLowerInvariant(), out var role) ? role : ChatMessageRole.assistant, request.Message!, @@ -58,19 +73,7 @@ public async Task Handle(CreateChatSessionCommand request, Cance private static void GuardAgainstActorNotFound(ActorEntity? actor) { if (actor == null) - throw new CustomValidationException( - [ - new("ActorId", "ActorId required for sessions") - ]); - } - - private static void GuardAgainstEmtpyActorId(Guid actorId) - { - if (actorId == Guid.Empty) - throw new CustomValidationException( - [ - new("ActorId", "ActorId required for sessions") - ]); + throw new CustomNotFoundException("Actor Not Found"); } private static void GuardAgainstEmptyMessage(string? message) @@ -88,6 +91,21 @@ private static void GuardAgainstIdExists(DbSet dbSet, Guid id throw new CustomConflictException("Id already exists"); } + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserInfo", "User information is required to create a chat session") + ]); + } + + private static void GuardAgainstUnauthorizedUser(ActorEntity actor, IUserContext userContext) + { + if (actor.OwnerId != userContext.OwnerId) + throw new CustomForbiddenAccessException("Actor", actor.Id); + } + private static void GuardAgainstNullAgentResponse(object? response) { if (response == null) diff --git a/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs new file mode 100644 index 0000000..047aad8 --- /dev/null +++ b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs @@ -0,0 +1,21 @@ +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class CreateMyChatSessionCommandValidator : Validator +{ + public CreateMyChatSessionCommandValidator() + { + RuleFor(x => x.Message) + .NotEmpty("Message is required"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs b/src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs deleted file mode 100644 index ebdc4fb..0000000 --- a/src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; -using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class DeleteChatSessionCommand : IRequest -{ - public Guid Id { get; set; } -} - -public class DeleteChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task Handle(DeleteChatSessionCommand request, CancellationToken cancellationToken) - { - var chatSession = _context.ChatSessions.Find(request.Id); - GuardAgainstNotFound(chatSession); - - _context.ChatSessions.Remove(chatSession!); - await _context.SaveChangesAsync(cancellationToken); - } - - private static void GuardAgainstNotFound(ChatSessionEntity? chatSession) - { - if (chatSession == null) - throw new CustomNotFoundException("Chat Session Not Found"); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs deleted file mode 100644 index 844d1c2..0000000 --- a/src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class DeleteChatSessionCommandValidator : Validator -{ - public DeleteChatSessionCommandValidator() - { - RuleFor(x => x.Id).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs new file mode 100644 index 0000000..3dba1ca --- /dev/null +++ b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs @@ -0,0 +1,50 @@ +using Goodtocode.AgentFramework.Core.Application.Abstractions; +using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; +using Goodtocode.AgentFramework.Core.Domain.Auth; +using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; + +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class DeleteMyChatSessionCommand : IRequest, IRequiresUserContext +{ + public Guid Id { get; set; } + public IUserContext? UserContext { get; set; } +} + +public class DeleteChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler +{ + private readonly IAgentFrameworkContext _context = context; + + public async Task Handle(DeleteMyChatSessionCommand request, CancellationToken cancellationToken) + { + GuardAgainstEmptyUser(request?.UserContext); + + var chatSession = _context.ChatSessions.Find(request!.Id); + GuardAgainstNotFound(chatSession); + GuardAgainstUnauthorized(chatSession!, request.UserContext!); + + _context.ChatSessions.Remove(chatSession!); + await _context.SaveChangesAsync(cancellationToken); + } + + private static void GuardAgainstNotFound(ChatSessionEntity? chatSession) + { + if (chatSession == null) + throw new CustomNotFoundException("Chat Session Not Found"); + } + + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserContext", "User information is required to delete a chat session") + ]); + } + + private static void GuardAgainstUnauthorized(ChatSessionEntity chatSession, IUserContext userContext) + { + if (chatSession.OwnerId != userContext.OwnerId) + throw new CustomForbiddenAccessException("ChatSession", chatSession.Id); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs new file mode 100644 index 0000000..a2d7b9e --- /dev/null +++ b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs @@ -0,0 +1,21 @@ +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class DeleteMyChatSessionCommandValidator : Validator +{ + public DeleteMyChatSessionCommandValidator() + { + RuleFor(x => x.Id) + .NotEmpty("Id is required"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatMessageQuery.cs b/src/Core.Application/ChatCompletion/GetChatMessageQuery.cs deleted file mode 100644 index dd659d0..0000000 --- a/src/Core.Application/ChatCompletion/GetChatMessageQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; -using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatMessageQuery : IRequest -{ - public Guid Id { get; set; } -} - -public class GetChatMessageQueryHandler(IAgentFrameworkContext context) : IRequestHandler -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task Handle(GetChatMessageQuery request, - CancellationToken cancellationToken) - { - var chatMessage = await _context.ChatMessages.FindAsync([request.Id, cancellationToken], cancellationToken: cancellationToken); - GuardAgainstNotFound(chatMessage); - - return ChatMessageDto.CreateFrom(chatMessage); - } - - private static void GuardAgainstNotFound(ChatMessageEntity? chatMessage) - { - if (chatMessage == null) - throw new CustomNotFoundException("Chat Message Not Found"); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs deleted file mode 100644 index 9129e09..0000000 --- a/src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatMessageQueryValidator : Validator -{ - public GetChatMessageQueryValidator() - { - RuleFor(x => x.Id).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs deleted file mode 100644 index c8ea763..0000000 --- a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; -using Goodtocode.AgentFramework.Core.Application.Common.Mappings; -using Goodtocode.AgentFramework.Core.Application.Common.Models; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatMessagesPaginatedQuery : IRequest> -{ - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } - public int PageNumber { get; init; } = 1; - public int PageSize { get; init; } = 10; -} - -public class GetChatMessagesPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler> -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task> Handle(GetChatMessagesPaginatedQuery request, CancellationToken cancellationToken) - { - var returnData = await _context.ChatMessages - .OrderByDescending(x => x.Timestamp) - .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate) - && (request.EndDate == null || x.Timestamp < request.EndDate)) - .Select(x => ChatMessageDto.CreateFrom(x)) - .PaginatedListAsync(request.PageNumber, request.PageSize); - - return returnData; - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs deleted file mode 100644 index 3070658..0000000 --- a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatMessagesPaginatedQueryValidator : Validator -{ - public GetChatMessagesPaginatedQueryValidator() - { - RuleFor(v => v.StartDate).NotEmpty() - .When(v => v.EndDate != null) - .LessThanOrEqualTo(v => v.EndDate); - - RuleFor(v => v.EndDate) - .NotEmpty() - .When(v => v.StartDate != null) - .GreaterThanOrEqualTo(v => v.StartDate); - - RuleFor(x => x.PageNumber).NotEqual(0); - - RuleFor(x => x.PageSize).NotEqual(0); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs b/src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs deleted file mode 100644 index 9579430..0000000 --- a/src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatMessagesQuery : IRequest> -{ - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } -} - -public class GetChatMessagesQueryHandler(IAgentFrameworkContext context) : IRequestHandler> -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task> Handle(GetChatMessagesQuery request, CancellationToken cancellationToken) - { - var returnData = await _context.ChatMessages - .OrderByDescending(x => x.Timestamp) - .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate) - && (request.EndDate == null || x.Timestamp < request.EndDate)) - .Select(x => ChatMessageDto.CreateFrom(x)) - .ToListAsync(cancellationToken); - - return returnData; - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs deleted file mode 100644 index 7b54b41..0000000 --- a/src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatMessagesQueryValidator : Validator -{ - public GetChatMessagesQueryValidator() - { - RuleFor(v => v.StartDate).NotEmpty() - .When(v => v.EndDate != null) - .LessThanOrEqualTo(v => v.EndDate); - - RuleFor(v => v.EndDate) - .NotEmpty() - .When(v => v.StartDate != null) - .GreaterThanOrEqualTo(v => v.StartDate); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatSessionQuery.cs b/src/Core.Application/ChatCompletion/GetChatSessionQuery.cs deleted file mode 100644 index ed2e271..0000000 --- a/src/Core.Application/ChatCompletion/GetChatSessionQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; -using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatSessionQuery : IRequest -{ - public Guid Id { get; set; } -} - -public class GetChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task Handle(GetChatSessionQuery request, - CancellationToken cancellationToken) - { - var chatSession = await _context.ChatSessions.FindAsync([request.Id, cancellationToken], cancellationToken: cancellationToken); - GuardAgainstNotFound(chatSession); - - return ChatSessionDto.CreateFrom(chatSession); - } - - private static void GuardAgainstNotFound(ChatSessionEntity? chatSession) - { - if (chatSession == null) - throw new CustomNotFoundException("Chat Session Not Found"); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs deleted file mode 100644 index 964ae49..0000000 --- a/src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatSessionQueryValidator : Validator -{ - public GetChatSessionQueryValidator() - { - RuleFor(x => x.Id).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs deleted file mode 100644 index a98b117..0000000 --- a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; -using Goodtocode.AgentFramework.Core.Application.Common.Mappings; -using Goodtocode.AgentFramework.Core.Application.Common.Models; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatSessionsPaginatedQuery : IRequest> -{ - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } - public int PageNumber { get; init; } = 1; - public int PageSize { get; init; } = 10; -} - -public class GetChatSessionsPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler> -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task> Handle(GetChatSessionsPaginatedQuery request, CancellationToken cancellationToken) - { - var returnData = await _context.ChatSessions - .OrderByDescending(x => x.Timestamp) - .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate) - && (request.EndDate == null || x.Timestamp < request.EndDate)) - .Select(x => ChatSessionDto.CreateFrom(x)) - .PaginatedListAsync(request.PageNumber, request.PageSize); - - return returnData; - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs deleted file mode 100644 index 605b382..0000000 --- a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatSessionsPaginatedQueryValidator : Validator -{ - public GetChatSessionsPaginatedQueryValidator() - { - RuleFor(v => v.StartDate).NotEmpty() - .When(v => v.EndDate != null) - .LessThanOrEqualTo(v => v.EndDate); - - RuleFor(v => v.EndDate) - .NotEmpty() - .When(v => v.StartDate != null) - .GreaterThanOrEqualTo(v => v.StartDate); - - RuleFor(x => x.PageNumber).NotEqual(0); - - RuleFor(x => x.PageSize).NotEqual(0); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs b/src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs deleted file mode 100644 index c8f0636..0000000 --- a/src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatSessionsQuery : IRequest> -{ - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } -} - -public class GetChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler> -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task> Handle(GetChatSessionsQuery request, CancellationToken cancellationToken) - { - var returnData = await _context.ChatSessions - .OrderByDescending(x => x.Timestamp) - .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate) - && (request.EndDate == null || x.Timestamp < request.EndDate)) - .Select(x => ChatSessionDto.CreateFrom(x)) - .ToListAsync(cancellationToken); - - return returnData; - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs deleted file mode 100644 index b0f50ff..0000000 --- a/src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class GetChatSessionsQueryValidator : Validator -{ - public GetChatSessionsQueryValidator() - { - RuleFor(v => v.StartDate).NotEmpty() - .When(v => v.EndDate != null) - .LessThanOrEqualTo(v => v.EndDate); - - RuleFor(v => v.EndDate) - .NotEmpty() - .When(v => v.StartDate != null) - .GreaterThanOrEqualTo(v => v.StartDate); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs new file mode 100644 index 0000000..7efbcec --- /dev/null +++ b/src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs @@ -0,0 +1,50 @@ +using Goodtocode.AgentFramework.Core.Application.Abstractions; +using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; +using Goodtocode.AgentFramework.Core.Domain.Auth; +using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; + +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class GetMyChatMessageQuery : IRequest, IRequiresUserContext +{ + public Guid Id { get; set; } + public IUserContext? UserContext { get; set; } +} + +public class GetChatMessageQueryHandler(IAgentFrameworkContext context) : IRequestHandler +{ + private readonly IAgentFrameworkContext _context = context; + + public async Task Handle(GetMyChatMessageQuery request, + CancellationToken cancellationToken) + { + GuardAgainstEmptyUser(request?.UserContext); + + var chatMessage = await _context.ChatMessages.FindAsync([request!.Id, cancellationToken], cancellationToken: cancellationToken); + GuardAgainstNotFound(chatMessage); + GuardAgainstUnauthorized(chatMessage!, request.UserContext!); + + return ChatMessageDto.CreateFrom(chatMessage); + } + + private static void GuardAgainstNotFound(ChatMessageEntity? chatMessage) + { + if (chatMessage == null) + throw new CustomNotFoundException("Chat Message Not Found"); + } + + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserInfo", "User information is required to retrieve a chat message") + ]); + } + + private static void GuardAgainstUnauthorized(ChatMessageEntity chatMessage, IUserContext userInfo) + { + if (chatMessage.ChatSession?.OwnerId != userInfo.OwnerId) + throw new CustomForbiddenAccessException("ChatMessage", chatMessage.Id); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs new file mode 100644 index 0000000..b125777 --- /dev/null +++ b/src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs @@ -0,0 +1,21 @@ +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class GetMyChatMessageQueryValidator : Validator +{ + public GetMyChatMessageQueryValidator() + { + RuleFor(x => x.Id) + .NotEmpty("Id is required"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs new file mode 100644 index 0000000..877e4c8 --- /dev/null +++ b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs @@ -0,0 +1,46 @@ +using Goodtocode.AgentFramework.Core.Application.Abstractions; +using Goodtocode.AgentFramework.Core.Application.Common.Mappings; +using Goodtocode.AgentFramework.Core.Application.Common.Models; +using Goodtocode.AgentFramework.Core.Domain.Auth; + +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class GetMyChatMessagesPaginatedQuery : IRequest>, IRequiresUserContext +{ + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int PageNumber { get; init; } = 1; + public int PageSize { get; init; } = 10; + public IUserContext? UserContext { get; set; } +} + +public class GetChatMessagesPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler> +{ + private readonly IAgentFrameworkContext _context = context; + + public async Task> Handle(GetMyChatMessagesPaginatedQuery request, CancellationToken cancellationToken) + { + GuardAgainstEmptyUser(request?.UserContext); + + var userContext = request!.UserContext!; + + var returnData = await _context.ChatMessages + .Where(x => x.ChatSession != null && x.ChatSession.OwnerId == userContext.OwnerId) + .OrderByDescending(x => x.Timestamp) + .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate.Value) + && (request.EndDate == null || x.Timestamp < request.EndDate.Value)) + .Select(x => ChatMessageDto.CreateFrom(x)) + .PaginatedListAsync(request.PageNumber, request.PageSize); + + return returnData; + } + + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserInfo", "User information is required to retrieve chat messages") + ]); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs new file mode 100644 index 0000000..e7b7d1c --- /dev/null +++ b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs @@ -0,0 +1,34 @@ +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class GetMyChatMessagesPaginatedQueryValidator : Validator +{ + public GetMyChatMessagesPaginatedQueryValidator() + { + RuleFor(v => v.StartDate) + .NotEmpty("StartDate is required when EndDate is provided") + .When(v => v.EndDate != null) + .LessThanOrEqualTo(v => v.EndDate); + + RuleFor(v => v.EndDate) + .NotEmpty("EndDate is required when StartDate is provided") + .When(v => v.StartDate != null) + .GreaterThanOrEqualTo(v => v.StartDate); + + RuleFor(x => x.PageNumber) + .NotEqual(0, "PageNumber must be greater than 0"); + + RuleFor(x => x.PageSize) + .NotEqual(0, "PageSize must be greater than 0"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs index e8470bb..7f6a5ed 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs @@ -5,31 +5,25 @@ namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; -public class GetMyChatSessionQuery : IRequest, IUserInfoRequest +public class GetMyChatSessionQuery : IRequest, IRequiresUserContext { - public Guid ChatSessionId { get; set; } - public IUserEntity? UserInfo { get; set; } + public Guid Id { get; set; } + public IUserContext? UserContext { get; set; } } -public class GetAuthorChatSessionByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetMyChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; public async Task Handle(GetMyChatSessionQuery request, CancellationToken cancellationToken) { - var returnData = await _context.ChatSessions - .Where(cs => cs.Id == request.ChatSessionId) - .Join(_context.Actors, - cs => cs.ActorId, - a => a.Id, - (cs, a) => new { ChatSession = cs, Actor = a }) - .Where(joined => joined.Actor.OwnerId == request.UserInfo!.OwnerId) - .Select(joined => joined.ChatSession) - .FirstOrDefaultAsync(cancellationToken); - - GuardAgainstNotFound(returnData); - - return ChatSessionDto.CreateFrom(returnData); + GuardAgainstEmptyUser(request?.UserContext); + + var chatSession = await _context.ChatSessions.FindAsync([request!.Id], cancellationToken: cancellationToken); + GuardAgainstNotFound(chatSession); + GuardAgainstUnauthorized(chatSession!, request.UserContext!); + + return ChatSessionDto.CreateFrom(chatSession); } private static void GuardAgainstNotFound(ChatSessionEntity? entity) @@ -37,4 +31,19 @@ private static void GuardAgainstNotFound(ChatSessionEntity? entity) if (entity is null) throw new CustomNotFoundException("Chat Session Not Found"); } + + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserInfo", "User information is required to retrieve a chat session") + ]); + } + + private static void GuardAgainstUnauthorized(ChatSessionEntity chatSession, IUserContext userInfo) + { + if (chatSession.OwnerId != userInfo.OwnerId) + throw new CustomForbiddenAccessException("ChatSession", chatSession.Id); + } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs index 7352d7d..bce0538 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs @@ -4,7 +4,18 @@ public class GetMyChatSessionQueryValidator : Validator { public GetMyChatSessionQueryValidator() { - RuleFor(x => x.UserInfo).NotEmpty(); - RuleFor(x => x.ChatSessionId).NotEmpty(); + RuleFor(x => x.Id) + .NotEmpty("ChatSessionId is required"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs index 10c7754..502ac77 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs @@ -1,40 +1,47 @@ using Goodtocode.AgentFramework.Core.Application.Abstractions; +using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; using Goodtocode.AgentFramework.Core.Application.Common.Mappings; using Goodtocode.AgentFramework.Core.Application.Common.Models; using Goodtocode.AgentFramework.Core.Domain.Auth; namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; -public class GetMyChatSessionsPaginatedQuery : IRequest>, IUserInfoRequest +public class GetMyChatSessionsPaginatedQuery : IRequest>, IRequiresUserContext { public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public int PageNumber { get; init; } = 1; public int PageSize { get; init; } = 10; - public IUserEntity? UserInfo { get; set; } + public IUserContext? UserContext { get; set; } } -public class GetAuthorChatSessionsByExternalIdPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler> +public class GetMyChatSessionsPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler> { private readonly IAgentFrameworkContext _context = context; public async Task> Handle(GetMyChatSessionsPaginatedQuery request, CancellationToken cancellationToken) { + GuardAgainstEmptyUser(request?.UserContext); + var ownerId = request!.UserContext!.OwnerId; + var returnData = await _context.ChatSessions .Include(x => x.Messages) + .Where(x => x.OwnerId == ownerId) .OrderByDescending(x => x.Timestamp) .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate) && (request.EndDate == null || x.Timestamp < request.EndDate)) - .Join(_context.Actors, - cs => cs.ActorId, - a => a.Id, - (cs, a) => new { ChatSession = cs, Actor = a }) - .Where(joined => joined.Actor.OwnerId == request.UserInfo!.OwnerId) - .Select(joined => joined.ChatSession) - .Select(x => ChatSessionDto.CreateFrom(x)) + .Select(x => ChatSessionDto.CreateFrom(x)) .PaginatedListAsync(request.PageNumber, request.PageSize); return returnData; + } + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserInfo", "User information is required to retrieve chat sessions") + ]); } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs index 981ce8a..132a6ff 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs @@ -4,19 +4,31 @@ public class GetMyChatSessionsPaginatedQueryValidator : Validator x.UserInfo).NotEmpty(); - - RuleFor(v => v.StartDate).NotEmpty() + RuleFor(v => v.StartDate) + .NotEmpty("StartDate is required when EndDate is provided") .When(v => v.EndDate != null) .LessThanOrEqualTo(v => v.EndDate); RuleFor(v => v.EndDate) - .NotEmpty() + .NotEmpty("EndDate is required when StartDate is provided") .When(v => v.StartDate != null) .GreaterThanOrEqualTo(v => v.StartDate); - RuleFor(x => x.PageNumber).NotEqual(0); + RuleFor(x => x.PageNumber) + .NotEqual(0, "PageNumber must be greater than 0"); + + RuleFor(x => x.PageSize) + .NotEqual(0, "PageSize must be greater than 0"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); - RuleFor(x => x.PageSize).NotEqual(0); + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs index 2f221c1..064fa81 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs @@ -1,34 +1,45 @@ using Goodtocode.AgentFramework.Core.Application.Abstractions; +using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; using Goodtocode.AgentFramework.Core.Domain.Auth; namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; -public class GetMyChatSessionsQuery : IRequest>, IUserInfoRequest +public class GetMyChatSessionsQuery : IRequest>, IRequiresUserContext { public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } - public IUserEntity? UserInfo { get; set; } + public IUserContext? UserContext { get; set; } } -public class GetAuthorChatSessionsByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler> +public class GetMyChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler> { private readonly IAgentFrameworkContext _context = context; public async Task> Handle(GetMyChatSessionsQuery request, CancellationToken cancellationToken) { + GuardAgainstEmptyUser(request?.UserContext); + + var startDate = request?.StartDate; + var endDate = request?.EndDate; + var userContext = request?.UserContext; + var returnData = await _context.ChatSessions + .Where(x => userContext != null && x.OwnerId == userContext.OwnerId) .OrderByDescending(x => x.Timestamp) - .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate) - && (request.EndDate == null || x.Timestamp < request.EndDate)) - .Join(_context.Actors, - cs => cs.ActorId, - a => a.Id, - (cs, a) => new { ChatSession = cs, Actor = a }) - .Where(joined => joined.Actor.OwnerId == request.UserInfo!.OwnerId) - .Select(joined => joined.ChatSession) + .Where(x => (startDate == null || x.Timestamp > startDate) + && (endDate == null || x.Timestamp < endDate)) .Select(x => ChatSessionDto.CreateFrom(x)) .ToListAsync(cancellationToken); return returnData; } + + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserInfo", "User information is required to retrieve chat sessions") + ]); + } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs index 9c30ea1..ef768c6 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs @@ -4,15 +4,25 @@ public class GetMyChatSessionsQueryValidator : Validator { public GetMyChatSessionsQueryValidator() { - RuleFor(x => x.UserInfo).NotEmpty(); - - RuleFor(v => v.StartDate).NotEmpty() + RuleFor(v => v.StartDate) + .NotEmpty("StartDate is required when EndDate is provided") .When(v => v.EndDate != null) .LessThanOrEqualTo(v => v.EndDate); RuleFor(v => v.EndDate) - .NotEmpty() + .NotEmpty("EndDate is required when StartDate is provided") .When(v => v.StartDate != null) .GreaterThanOrEqualTo(v => v.StartDate); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs deleted file mode 100644 index aedb7bd..0000000 --- a/src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class PatchChatSessionCommandValidator : Validator -{ - public PatchChatSessionCommandValidator() - { - RuleFor(x => x.Id).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/PatchChatSessionCommand.cs b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommand.cs similarity index 51% rename from src/Core.Application/ChatCompletion/PatchChatSessionCommand.cs rename to src/Core.Application/ChatCompletion/PatchMyChatSessionCommand.cs index ebce817..0d39b3b 100644 --- a/src/Core.Application/ChatCompletion/PatchChatSessionCommand.cs +++ b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommand.cs @@ -1,25 +1,29 @@ using Goodtocode.AgentFramework.Core.Application.Abstractions; using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; +using Goodtocode.AgentFramework.Core.Domain.Auth; using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; -public class PatchChatSessionCommand : IRequest +public class PatchMyChatSessionCommand : IRequest, IRequiresUserContext { public Guid Id { get; set; } public string Title { get; set; } = string.Empty; + public IUserContext? UserContext { get; set; } } -public class PatchChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class PatchChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(PatchChatSessionCommand request, CancellationToken cancellationToken) + public async Task Handle(PatchMyChatSessionCommand request, CancellationToken cancellationToken) { + GuardAgainstEmptyTitle(request.Title); + GuardAgainstEmptyUser(request?.UserContext); - var chatSession = _context.ChatSessions.Find(request.Id); + var chatSession = _context.ChatSessions.Find(request!.Id); GuardAgainstNotFound(chatSession); - GuardAgainstEmptyTitle(request.Title); + GuardAgainstUnauthorized(chatSession!, request.UserContext!); chatSession!.Update(request.Title); @@ -41,4 +45,19 @@ private static void GuardAgainstEmptyTitle(string title) new("Title", "Title cannot be empty") ]); } + + private static void GuardAgainstEmptyUser(IUserContext? userContext) + { + if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty) + throw new CustomValidationException( + [ + new("UserContext", "User information is required to update a chat session") + ]); + } + + private static void GuardAgainstUnauthorized(ChatSessionEntity chatSession, IUserContext userContext) + { + if (chatSession.OwnerId != userContext.OwnerId) + throw new CustomForbiddenAccessException("ChatSession", chatSession.Id); + } } \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs new file mode 100644 index 0000000..06cdd8f --- /dev/null +++ b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs @@ -0,0 +1,24 @@ +namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; + +public class PatchMyChatSessionCommandValidator : Validator +{ + public PatchMyChatSessionCommandValidator() + { + RuleFor(x => x.Id) + .NotEmpty("Id is required"); + + RuleFor(x => x.Title) + .NotEmpty("Title is required"); + + RuleFor(x => x.UserContext) + .NotEmpty("User information is required"); + + RuleFor(x => x.UserContext!.OwnerId) + .NotEmpty("OwnerId is required") + .When(x => x.UserContext != null); + + RuleFor(x => x.UserContext!.TenantId) + .NotEmpty("TenantId is required") + .When(x => x.UserContext != null); + } +} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs b/src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs deleted file mode 100644 index ee71e69..0000000 --- a/src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; -using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class UpdateChatSessionCommand : IRequest -{ - public Guid Id { get; set; } - public string Title { get; set; } = string.Empty; - public ICollection Messages { get; set; } = []; -} - -public class UpdateChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler -{ - private readonly IAgentFrameworkContext _context = context; - - public async Task Handle(UpdateChatSessionCommand request, CancellationToken cancellationToken) - { - var chatSession = _context.ChatSessions.Find(request.Id); - GuardAgainstNotFound(chatSession); - - - _context.ChatSessions.Update(chatSession!); - await _context.SaveChangesAsync(cancellationToken); - } - - private static void GuardAgainstNotFound(ChatSessionEntity? chatSession) - { - if (chatSession == null) - throw new CustomNotFoundException("Chat Session Not Found"); - } -} \ No newline at end of file diff --git a/src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs deleted file mode 100644 index 15ad79b..0000000 --- a/src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -public class UpdateChatSessionCommandValidator : Validator -{ - public UpdateChatSessionCommandValidator() - { - RuleFor(x => x.Id).NotEmpty(); - RuleFor(x => x.Title).NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs b/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs index 9994011..3b21140 100644 --- a/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs +++ b/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs @@ -17,7 +17,7 @@ public CustomForbiddenAccessException(string message, Exception innerException) } public CustomForbiddenAccessException(string name, object id) - : base($"Entity \"{name}\" ({id}) was not found.") + : base($"Access to entity \"{name}\" ({id}) is forbidden.") { } } \ No newline at end of file diff --git a/src/Core.Application/Core.Application.csproj b/src/Core.Application/Core.Application.csproj index 2531e9e..57821d1 100644 --- a/src/Core.Application/Core.Application.csproj +++ b/src/Core.Application/Core.Application.csproj @@ -10,8 +10,8 @@ enable - - + + diff --git a/src/Core.Domain/Actor/ActorEntity.cs b/src/Core.Domain/Actor/ActorEntity.cs index a985aa0..8edaffc 100644 --- a/src/Core.Domain/Actor/ActorEntity.cs +++ b/src/Core.Domain/Actor/ActorEntity.cs @@ -11,20 +11,18 @@ protected ActorEntity() { } public string? LastName { get; private set; } = string.Empty; public string? Email { get; private set; } = string.Empty; - public static ActorEntity Create(Guid id, Guid ownerId, Guid tenantId, string? firstName, string? lastName, string? email) + public static ActorEntity Create(Guid id, string? firstName, string? lastName, string? email) { return new ActorEntity { Id = id == Guid.Empty ? Guid.NewGuid() : id, - OwnerId = ownerId, - TenantId = tenantId, FirstName = firstName, LastName = lastName, Email = email }; } - public static ActorEntity Create(IUserEntity userInfo) + public static ActorEntity Create(IUserContext userInfo) { return new ActorEntity { diff --git a/src/Core.Domain/Auth/IUserContext.cs b/src/Core.Domain/Auth/IUserContext.cs new file mode 100644 index 0000000..8756855 --- /dev/null +++ b/src/Core.Domain/Auth/IUserContext.cs @@ -0,0 +1,19 @@ +namespace Goodtocode.AgentFramework.Core.Domain.Auth; + +/// +/// Represents the authenticated user's context, including their identity, tenant association, and authorization permissions. +/// +/// This interface provides access to user identity information and computed authorization permissions +/// based on assigned roles. It serves as the domain representation of the current user's security context. +public interface IUserContext +{ + Guid OwnerId { get; } + Guid TenantId { get; } + string FirstName { get; } + string LastName { get; } + string Email { get; } + IEnumerable Roles { get; } + bool CanView { get; } + bool CanEdit { get; } + bool CanDelete { get; } +} diff --git a/src/Core.Domain/Auth/IUserEntity.cs b/src/Core.Domain/Auth/IUserEntity.cs deleted file mode 100644 index 55876e0..0000000 --- a/src/Core.Domain/Auth/IUserEntity.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Goodtocode.AgentFramework.Core.Domain.Auth; - -public interface IUserEntity -{ - Guid OwnerId { get; } - Guid TenantId { get; } - string FirstName { get; } - string LastName { get; } - string Email { get; } - IEnumerable Roles { get; } - bool CanView { get; } - bool CanEdit { get; } - bool CanDelete { get; } -} diff --git a/src/Core.Domain/Auth/UserEntity.cs b/src/Core.Domain/Auth/UserContext.cs similarity index 68% rename from src/Core.Domain/Auth/UserEntity.cs rename to src/Core.Domain/Auth/UserContext.cs index 3b807b8..2372034 100644 --- a/src/Core.Domain/Auth/UserEntity.cs +++ b/src/Core.Domain/Auth/UserContext.cs @@ -7,7 +7,12 @@ public struct UserRoles public const string ChatViewer = "AssetViewer"; } -public class UserEntity() : IUserEntity +/// +/// Represents the authenticated user's context in the domain, including identity and authorization state. +/// +/// This class encapsulates user identity information and provides role-based authorization logic +/// through computed properties (CanView, CanEdit, CanDelete). +public class UserContext() : IUserContext { public Guid OwnerId { get; private set; } public Guid TenantId { get; private set; } @@ -19,9 +24,9 @@ public class UserEntity() : IUserEntity public bool CanEdit => Roles.Contains(UserRoles.ChatOwner) || Roles.Contains(UserRoles.ChatEditor); public bool CanDelete => Roles.Contains(UserRoles.ChatOwner); - public static UserEntity Create(Guid ownerId, Guid tenantId, string firstName, string lastName, string email, IEnumerable roles) + public static UserContext Create(Guid ownerId, Guid tenantId, string firstName, string lastName, string email, IEnumerable roles) { - return new UserEntity + return new UserContext { OwnerId = ownerId, TenantId = tenantId, diff --git a/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs b/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs index 5bb8616..9c433a3 100644 --- a/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs +++ b/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs @@ -2,7 +2,7 @@ namespace Goodtocode.AgentFramework.Core.Domain.ChatCompletion; -public class ChatMessageEntity : DomainEntity, IDomainEntity +public class ChatMessageEntity : SecuredEntity, IDomainEntity { protected ChatMessageEntity() { } public Guid ChatSessionId { get; private set; } diff --git a/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs b/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs index 34044cd..5d790a9 100644 --- a/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs +++ b/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs @@ -3,21 +3,20 @@ namespace Goodtocode.AgentFramework.Core.Domain.ChatCompletion; -public class ChatSessionEntity : DomainEntity +public class ChatSessionEntity : SecuredEntity { protected ChatSessionEntity() { } public Guid ActorId { get; private set; } public string? Title { get; private set; } = string.Empty; public virtual ICollection Messages { get; private set; } = []; - public virtual ActorEntity? Actor { get; private set; } - public static ChatSessionEntity Create(Guid id, Guid authorId, string? title, ChatMessageRole responseRole, string initialMessage, string responseMessage) + public static ChatSessionEntity Create(Guid id, Guid actorId, string? title, ChatMessageRole responseRole, string initialMessage, string responseMessage) { var session = new ChatSessionEntity { Id = id == Guid.Empty ? Guid.NewGuid() : id, - ActorId = authorId, + ActorId = actorId, Title = title, Timestamp = DateTime.UtcNow }; diff --git a/src/Core.Domain/Core.Domain.csproj b/src/Core.Domain/Core.Domain.csproj index 65c072e..e54147d 100644 --- a/src/Core.Domain/Core.Domain.csproj +++ b/src/Core.Domain/Core.Domain.csproj @@ -12,6 +12,6 @@ - + diff --git a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs index 6122e40..fc98c91 100644 --- a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs @@ -19,7 +19,7 @@ public sealed class ActorsTool(IServiceProvider serviceProvider) : AITool, IActo { private readonly IServiceProvider _serviceProvider = serviceProvider; - public static string ToolName => "AuthorsTool"; + public static string ToolName => "ActorsTool"; public string FunctionName => _currentFunctionName; public Dictionary Parameters => _currentParameters; @@ -75,7 +75,7 @@ public async Task> GetActorsByNameAsync(string name, var nameTokens = name?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; var normalizedInput = name?.Trim() ?? string.Empty; - var authors = await context.Actors + var actors = await context.Actors .Where(a => nameTokens.Any(token => EF.Functions.Like(a.FirstName, $"%{token}%") || @@ -96,7 +96,7 @@ public async Task> GetActorsByNameAsync(string name, ) .ToListAsync(cancellationToken); - if (authors.Count == 0) + if (actors.Count == 0) { return [ new ActorResponse { @@ -108,7 +108,7 @@ public async Task> GetActorsByNameAsync(string name, } else { - return [.. authors.Select(a => new ActorResponse + return [.. actors.Select(a => new ActorResponse { ActorId = a.Id, Name = $"{a.FirstName} {a.LastName}", diff --git a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs index e14d677..6660bb1 100644 --- a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs @@ -69,6 +69,6 @@ public async Task UpdateChatSessionTitleAsync(Guid sessionId, string new context.ChatSessions.Update(chatSession); await context.SaveChangesAsync(cancellationToken); - return $"{chatSession.Id}: {chatSession.Timestamp} - {chatSession.Title}: {chatSession.Actor?.FirstName} {chatSession.Actor?.LastName}"; + return $"{chatSession.Id}: {chatSession.Timestamp} - {chatSession.Title}"; } } \ No newline at end of file diff --git a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.Designer.cs b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.Designer.cs similarity index 92% rename from src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.Designer.cs rename to src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.Designer.cs index c7f9da6..94032b3 100644 --- a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.Designer.cs +++ b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ namespace Goodtocode.AgentFramework.Infrastructure.SqlServer.Migrations { [DbContext(typeof(AgentFrameworkContext))] - [Migration("20260204064841_InitialCreate")] + [Migration("20260208060746_InitialCreate")] partial class InitialCreate { /// @@ -103,9 +103,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedOn") .HasColumnType("datetime2"); + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + b.Property("Role") .HasColumnType("int"); + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.Property("Timestamp") .HasColumnType("datetimeoffset"); @@ -146,6 +152,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("ModifiedOn") .HasColumnType("datetime2"); + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.Property("Timestamp") .HasColumnType("datetimeoffset"); @@ -156,8 +168,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false); - b.HasIndex("ActorId"); - b.HasIndex("Id") .IsUnique(); @@ -182,17 +192,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("ChatSession"); }); - modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b => - { - b.HasOne("Goodtocode.AgentFramework.Core.Domain.Actor.ActorEntity", "Actor") - .WithMany() - .HasForeignKey("ActorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Actor"); - }); - modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b => { b.Navigation("Messages"); diff --git a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.cs b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.cs similarity index 91% rename from src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.cs rename to src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.cs index 7c6c9f2..89c8dc5 100644 --- a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.cs +++ b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.cs @@ -42,18 +42,14 @@ protected override void Up(MigrationBuilder migrationBuilder) CreatedOn = table.Column(type: "datetime2", nullable: false), ModifiedOn = table.Column(type: "datetime2", nullable: true), DeletedOn = table.Column(type: "datetime2", nullable: true), - Timestamp = table.Column(type: "datetimeoffset", nullable: false) + Timestamp = table.Column(type: "datetimeoffset", nullable: false), + OwnerId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false) }, constraints: table => { table.PrimaryKey("PK_ChatSessions", x => x.Id) .Annotation("SqlServer:Clustered", false); - table.ForeignKey( - name: "FK_ChatSessions_Actors_ActorId", - column: x => x.ActorId, - principalTable: "Actors", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -67,7 +63,9 @@ protected override void Up(MigrationBuilder migrationBuilder) CreatedOn = table.Column(type: "datetime2", nullable: false), ModifiedOn = table.Column(type: "datetime2", nullable: true), DeletedOn = table.Column(type: "datetime2", nullable: true), - Timestamp = table.Column(type: "datetimeoffset", nullable: false) + Timestamp = table.Column(type: "datetimeoffset", nullable: false), + OwnerId = table.Column(type: "uniqueidentifier", nullable: false), + TenantId = table.Column(type: "uniqueidentifier", nullable: false) }, constraints: table => { @@ -120,11 +118,6 @@ protected override void Up(MigrationBuilder migrationBuilder) unique: true) .Annotation("SqlServer:Clustered", true); - migrationBuilder.CreateIndex( - name: "IX_ChatSessions_ActorId", - table: "ChatSessions", - column: "ActorId"); - migrationBuilder.CreateIndex( name: "IX_ChatSessions_Id", table: "ChatSessions", @@ -144,13 +137,13 @@ protected override void Up(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "ChatMessages"); + name: "Actors"); migrationBuilder.DropTable( - name: "ChatSessions"); + name: "ChatMessages"); migrationBuilder.DropTable( - name: "Actors"); + name: "ChatSessions"); } } } diff --git a/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs b/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs index 1da18c0..4b73088 100644 --- a/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs +++ b/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs @@ -100,9 +100,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedOn") .HasColumnType("datetime2"); + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + b.Property("Role") .HasColumnType("int"); + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.Property("Timestamp") .HasColumnType("datetimeoffset"); @@ -143,6 +149,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ModifiedOn") .HasColumnType("datetime2"); + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnType("uniqueidentifier"); + b.Property("Timestamp") .HasColumnType("datetimeoffset"); @@ -153,8 +165,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false); - b.HasIndex("ActorId"); - b.HasIndex("Id") .IsUnique(); @@ -179,17 +189,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("ChatSession"); }); - modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b => - { - b.HasOne("Goodtocode.AgentFramework.Core.Domain.Actor.ActorEntity", "Actor") - .WithMany() - .HasForeignKey("ActorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Actor"); - }); - modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b => { b.Navigation("Messages"); diff --git a/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs b/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs index 72b8d7a..c800ce6 100644 --- a/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs +++ b/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs @@ -2,19 +2,27 @@ using Goodtocode.AgentFramework.Core.Domain.Actor; using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; using Goodtocode.Domain.Entities; +using Microsoft.EntityFrameworkCore.ChangeTracking; using System.Reflection; namespace Goodtocode.AgentFramework.Infrastructure.SqlServer.Persistence; public class AgentFrameworkContext : DbContext, IAgentFrameworkContext { + private readonly ICurrentUserContext? _currentUserContext; + public DbSet ChatMessages => Set(); public DbSet ChatSessions => Set(); public DbSet Actors => Set(); protected AgentFrameworkContext() { } - public AgentFrameworkContext(DbContextOptions options) : base(options) { } + public AgentFrameworkContext( + DbContextOptions options, + ICurrentUserContext currentUserContext) : base(options) + { + _currentUserContext = currentUserContext; + } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -26,10 +34,43 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { + SetSecurityFields(); SetAuditFields(); return base.SaveChangesAsync(cancellationToken); } + private void SetSecurityFields() + { + if (_currentUserContext is null || + _currentUserContext.OwnerId == Guid.Empty || + _currentUserContext.TenantId == Guid.Empty) + { + return; + } + + var entries = ChangeTracker.Entries() + .Where(e => e.State == EntityState.Added && HasSecurityFields(e)); + + foreach (var entry in entries) + { + SetIfEmpty(entry, "OwnerId", _currentUserContext.OwnerId); + SetIfEmpty(entry, "TenantId", _currentUserContext.TenantId); + } + } + + private static bool HasSecurityFields(EntityEntry entry) => + entry.Metadata.FindProperty("OwnerId") != null && + entry.Metadata.FindProperty("TenantId") != null; + + private static void SetIfEmpty(EntityEntry entry, string propertyName, Guid value) + { + var property = entry.Property(propertyName); + if (property.CurrentValue is Guid guid && guid == Guid.Empty) + { + property.CurrentValue = value; + } + } + private void SetAuditFields() { var entries = ChangeTracker.Entries() diff --git a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs index 687c47b..90df5c8 100644 --- a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs +++ b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs @@ -73,980 +73,34 @@ public string BaseUrl partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// - /// Get Chat Message - /// - /// - /// Sample request: - ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetChatMessageQueryAsync(System.Guid id) - { - return GetChatMessageQueryAsync(id, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get Chat Message - /// - /// - /// Sample request: - ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetChatMessageQueryAsync(System.Guid id, System.Threading.CancellationToken cancellationToken) - { - if (id == null) - throw new System.ArgumentNullException("id"); - - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/messages/{id}" - urlBuilder_.Append("api/v1/admin/messages/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 404) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Get All Chat Messages for a session Query - /// - /// - /// Sample request: - ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetChatMessagesQueryAsync() - { - return GetChatMessagesQueryAsync(System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get All Chat Messages for a session Query - /// - /// - /// Sample request: - ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetChatMessagesQueryAsync(System.Threading.CancellationToken cancellationToken) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/messages" - urlBuilder_.Append("api/v1/admin/messages"); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 404) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Creates new Chat Message with initial message prompt/response history - /// - /// - /// Types of Chat Completion are: - ///
1. Informational Prompt: A prompt requesting information - ///
- Example Prompt: "What's the capital of France?" - ///
- Example Response: "The capital of France is Paris." - ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. - ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” - ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” - ///
Sample request: - ///
- ///
HttpPost Body - ///
{ - ///
"Id": 00000000-0000-0000-0000-000000000000, - ///
"Message": "Hi, I am interested in learning about Agent Framework." - ///
} - ///
- ///
"version": 1.0 - ///
- /// Created - /// A server side error occurred. - public virtual System.Threading.Tasks.Task CreateChatMessageCommandAsync(CreateChatMessageCommand body) - { - return CreateChatMessageCommandAsync(body, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Creates new Chat Message with initial message prompt/response history - /// - /// - /// Types of Chat Completion are: - ///
1. Informational Prompt: A prompt requesting information - ///
- Example Prompt: "What's the capital of France?" - ///
- Example Response: "The capital of France is Paris." - ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. - ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” - ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” - ///
Sample request: - ///
- ///
HttpPost Body - ///
{ - ///
"Id": 00000000-0000-0000-0000-000000000000, - ///
"Message": "Hi, I am interested in learning about Agent Framework." - ///
} - ///
- ///
"version": 1.0 - ///
- /// Created - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task CreateChatMessageCommandAsync(CreateChatMessageCommand body, System.Threading.CancellationToken cancellationToken) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings); - var content_ = new System.Net.Http.ByteArrayContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("POST"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/messages" - urlBuilder_.Append("api/v1/admin/messages"); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 201) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - if (status_ == 400) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Get Chat Messages Paginated Query - /// - /// - /// Sample request: - ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"PageNumber": 1 - ///
"PageSize" : 10 - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetChatMessagesPaginatedQueryAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize) - { - return GetChatMessagesPaginatedQueryAsync(startDate, endDate, pageNumber, pageSize, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get Chat Messages Paginated Query - /// - /// - /// Sample request: - ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"PageNumber": 1 - ///
"PageSize" : 10 - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetChatMessagesPaginatedQueryAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Threading.CancellationToken cancellationToken) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/messages/Paginated" - urlBuilder_.Append("api/v1/admin/messages/Paginated"); - urlBuilder_.Append('?'); - if (startDate != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("StartDate")).Append('=').Append(System.Uri.EscapeDataString(startDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (endDate != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("EndDate")).Append('=').Append(System.Uri.EscapeDataString(endDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (pageNumber != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("PageNumber")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageNumber, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (pageSize != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("PageSize")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageSize, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Get Chat Session with history - /// - /// - /// Sample request: - ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetChatSessionQueryAsync(System.Guid id) - { - return GetChatSessionQueryAsync(id, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get Chat Session with history - /// - /// - /// Sample request: - ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 - ///
- /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetChatSessionQueryAsync(System.Guid id, System.Threading.CancellationToken cancellationToken) - { - if (id == null) - throw new System.ArgumentNullException("id"); - - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions/{id}" - urlBuilder_.Append("api/v1/admin/sessions/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 404) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Patch Chat Session Command - /// - /// - /// Sample request: - ///
- ///
HttpPatch Body - ///
{ - ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - ///
"Title": "Agent Framework Chat Session" - ///
} - ///
- ///
"version": 1.0 - ///
- /// No Content - /// A server side error occurred. - public virtual System.Threading.Tasks.Task PatchChatSessionCommandAsync(System.Guid id, PatchChatSessionCommand body) - { - return PatchChatSessionCommandAsync(id, body, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Patch Chat Session Command - /// - /// - /// Sample request: - ///
- ///
HttpPatch Body - ///
{ - ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - ///
"Title": "Agent Framework Chat Session" - ///
} - ///
- ///
"version": 1.0 - ///
- /// No Content - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task PatchChatSessionCommandAsync(System.Guid id, PatchChatSessionCommand body, System.Threading.CancellationToken cancellationToken) - { - if (id == null) - throw new System.ArgumentNullException("id"); - - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings); - var content_ = new System.Net.Http.ByteArrayContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("PATCH"); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions/{id}" - urlBuilder_.Append("api/v1/admin/sessions/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 204) - { - return; - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 404) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Remove ChatSession Command - /// - /// - /// Sample request: - ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 - ///
- /// No Content - /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveChatSessionCommandAsync(System.Guid id) - { - return RemoveChatSessionCommandAsync(id, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Remove ChatSession Command - /// - /// - /// Sample request: - ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 - ///
- /// No Content - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(System.Guid id, System.Threading.CancellationToken cancellationToken) - { - if (id == null) - throw new System.ArgumentNullException("id"); - - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("DELETE"); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions/{id}" - urlBuilder_.Append("api/v1/admin/sessions/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 204) - { - return; - } - else - if (status_ == 401) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 404) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else - if (status_ == 500) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); - } - else - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Get All Chat Sessions Query + /// Retrieves the actor profile by external ID, including session history. /// /// /// Sample request: ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"api-version": 1.0 + ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e + ///
"api-version": 1.0 ///
/// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetChatSessionsQueryAsync() + public virtual System.Threading.Tasks.Task GetMyActorProfileAsync() { - return GetChatSessionsQueryAsync(System.Threading.CancellationToken.None); + return GetMyActorProfileAsync(System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get All Chat Sessions Query + /// Retrieves the actor profile by external ID, including session history. /// /// /// Sample request: ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"api-version": 1.0 + ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e + ///
"api-version": 1.0 ///
/// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetChatSessionsQueryAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetMyActorProfileAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -1059,8 +113,8 @@ public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(S var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions" - urlBuilder_.Append("api/v1/admin/sessions"); + // Operation Path: "api/v1my/actors" + urlBuilder_.Append("api/v1my/actors"); PrepareRequest(client_, request_, urlBuilder_); @@ -1087,7 +141,7 @@ public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(S var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -1145,58 +199,46 @@ public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(S } /// - /// Creates new Chat Session with initial message prompt/response history + /// Creates a new actor session with empty history. /// /// - /// Types of Chat Completion are: - ///
1. Informational Prompt: A prompt requesting information - ///
- Example Prompt: "What's the capital of France?" - ///
- Example Response: "The capital of France is Paris." - ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. - ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” - ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” - ///
Sample request: + /// Sample request: ///
///
HttpPost Body ///
{ ///
"Id": 00000000-0000-0000-0000-000000000000, - ///
"Message": "Hi, I am interested in learning about Agent Framework." + ///
"Name": "John Doe" ///
} ///
///
"version": 1.0 ///
- /// Created + /// The command containing actor creation details. + /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task CreateChatSessionCommandAsync(CreateChatSessionCommand body) + public virtual System.Threading.Tasks.Task SaveMyActorAsync(SaveMyActorCommand body) { - return CreateChatSessionCommandAsync(body, System.Threading.CancellationToken.None); + return SaveMyActorAsync(body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Creates new Chat Session with initial message prompt/response history + /// Creates a new actor session with empty history. /// /// - /// Types of Chat Completion are: - ///
1. Informational Prompt: A prompt requesting information - ///
- Example Prompt: "What's the capital of France?" - ///
- Example Response: "The capital of France is Paris." - ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. - ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” - ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” - ///
Sample request: + /// Sample request: ///
///
HttpPost Body ///
{ ///
"Id": 00000000-0000-0000-0000-000000000000, - ///
"Message": "Hi, I am interested in learning about Agent Framework." + ///
"Name": "John Doe" ///
} ///
///
"version": 1.0 ///
- /// Created + /// The command containing actor creation details. + /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task CreateChatSessionCommandAsync(CreateChatSessionCommand body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task SaveMyActorAsync(SaveMyActorCommand body, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -1213,8 +255,8 @@ public virtual async System.Threading.Tasks.Task CreateChatSessi var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions" - urlBuilder_.Append("api/v1/admin/sessions"); + // Operation Path: "api/v1my/actors" + urlBuilder_.Append("api/v1my/actors"); PrepareRequest(client_, request_, urlBuilder_); @@ -1239,9 +281,19 @@ public virtual async System.Threading.Tasks.Task CreateChatSessi ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else if (status_ == 201) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -1299,63 +351,52 @@ public virtual async System.Threading.Tasks.Task CreateChatSessi } /// - /// Update ChatSession Command, typically with changing the title or adding a new message + /// Get Chat Message /// /// /// Sample request: ///
- ///
HttpPut Body - ///
{ - ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - ///
"Message": "Hi, I am interested in learning about Agent Framework.", - ///
"Content": "Certainly! Agent Framework is a great framework for AI.", - ///
} - ///
- ///
"version": 1.0 + ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e + ///
"api-version": 1.0 ///
- /// No Content + /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task UpdateChatSessionCommandAsync(UpdateChatSessionCommand body) + public virtual System.Threading.Tasks.Task GetMyChatMessageAsync(System.Guid id) { - return UpdateChatSessionCommandAsync(body, System.Threading.CancellationToken.None); + return GetMyChatMessageAsync(id, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Update ChatSession Command, typically with changing the title or adding a new message + /// Get Chat Message /// /// /// Sample request: ///
- ///
HttpPut Body - ///
{ - ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - ///
"Message": "Hi, I am interested in learning about Agent Framework.", - ///
"Content": "Certainly! Agent Framework is a great framework for AI.", - ///
} - ///
- ///
"version": 1.0 + ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e + ///
"api-version": 1.0 ///
- /// No Content + /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task UpdateChatSessionCommandAsync(UpdateChatSessionCommand body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetMyChatMessageAsync(System.Guid id, System.Threading.CancellationToken cancellationToken) { + if (id == null) + throw new System.ArgumentNullException("id"); + var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings); - var content_ = new System.Net.Http.ByteArrayContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("PUT"); + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions" - urlBuilder_.Append("api/v1/admin/sessions"); + // Operation Path: "api/v1/my/messages/{id}" + urlBuilder_.Append("api/v1/my/messages/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -1380,9 +421,14 @@ public virtual async System.Threading.Tasks.Task UpdateChatSessionCommandAsync(U ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 204) + if (status_ == 200) { - return; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else if (status_ == 401) @@ -1435,72 +481,65 @@ public virtual async System.Threading.Tasks.Task UpdateChatSessionCommandAsync(U } /// - /// Get Chat Sessions Paginated Query + /// Patch Chat Session Command /// /// /// Sample request: ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"PageNumber": 1 - ///
"PageSize" : 10 - ///
"api-version": 1.0 + ///
HttpPatch Body + ///
{ + ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", + ///
"Title": "Agent Framework Chat Session" + ///
} + ///
+ ///
"version": 1.0 ///
- /// OK + /// No Content /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetChatSessionsPaginatedQueryAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize) + public virtual System.Threading.Tasks.Task PatchMyChatSessionAsync(System.Guid id, PatchMyChatSessionCommand body) { - return GetChatSessionsPaginatedQueryAsync(startDate, endDate, pageNumber, pageSize, System.Threading.CancellationToken.None); + return PatchMyChatSessionAsync(id, body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get Chat Sessions Paginated Query + /// Patch Chat Session Command /// /// /// Sample request: ///
- ///
"StartDate": "2024-06-01T00:00:00Z" - ///
"EndDate": "2024-12-01T00:00:00Z" - ///
"PageNumber": 1 - ///
"PageSize" : 10 - ///
"api-version": 1.0 + ///
HttpPatch Body + ///
{ + ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", + ///
"Title": "Agent Framework Chat Session" + ///
} + ///
+ ///
"version": 1.0 ///
- /// OK + /// No Content /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetChatSessionsPaginatedQueryAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task PatchMyChatSessionAsync(System.Guid id, PatchMyChatSessionCommand body, System.Threading.CancellationToken cancellationToken) { + if (id == null) + throw new System.ArgumentNullException("id"); + var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings); + var content_ = new System.Net.Http.ByteArrayContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("PATCH"); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/admin/sessions/Paginated" - urlBuilder_.Append("api/v1/admin/sessions/Paginated"); - urlBuilder_.Append('?'); - if (startDate != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("StartDate")).Append('=').Append(System.Uri.EscapeDataString(startDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (endDate != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("EndDate")).Append('=').Append(System.Uri.EscapeDataString(endDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (pageNumber != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("PageNumber")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageNumber, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (pageSize != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("PageSize")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageSize, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + // Operation Path: "api/v1/my/messages/{id}" + urlBuilder_.Append("api/v1/my/messages/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -1525,24 +564,29 @@ public virtual async System.Threading.Tasks.Task Ge ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) + if (status_ == 204) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + return; + } + else + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - return objectResponse_.Object; + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 401) + if (status_ == 404) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } - throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) @@ -1575,34 +619,40 @@ public virtual async System.Threading.Tasks.Task Ge } /// - /// Retrieves the actor profile by external ID, including session history. + /// Get Chat Messages Paginated Query /// /// /// Sample request: ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 + ///
"StartDate": "2024-06-01T00:00:00Z" + ///
"EndDate": "2024-12-01T00:00:00Z" + ///
"PageNumber": 1 + ///
"PageSize" : 10 + ///
"api-version": 1.0 ///
/// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetMyActorProfileAsync() + public virtual System.Threading.Tasks.Task GetMyChatMessagesPaginatedAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Guid? userInfo_OwnerId, System.Guid? userInfo_TenantId, string userInfo_FirstName, string userInfo_LastName, string userInfo_Email, System.Collections.Generic.IEnumerable userInfo_Roles, bool? userInfo_CanView, bool? userInfo_CanEdit, bool? userInfo_CanDelete) { - return GetMyActorProfileAsync(System.Threading.CancellationToken.None); + return GetMyChatMessagesPaginatedAsync(startDate, endDate, pageNumber, pageSize, userInfo_OwnerId, userInfo_TenantId, userInfo_FirstName, userInfo_LastName, userInfo_Email, userInfo_Roles, userInfo_CanView, userInfo_CanEdit, userInfo_CanDelete, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Retrieves the actor profile by external ID, including session history. + /// Get Chat Messages Paginated Query /// /// /// Sample request: ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - ///
"api-version": 1.0 + ///
"StartDate": "2024-06-01T00:00:00Z" + ///
"EndDate": "2024-12-01T00:00:00Z" + ///
"PageNumber": 1 + ///
"PageSize" : 10 + ///
"api-version": 1.0 ///
/// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetMyActorProfileAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetMyChatMessagesPaginatedAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Guid? userInfo_OwnerId, System.Guid? userInfo_TenantId, string userInfo_FirstName, string userInfo_LastName, string userInfo_Email, System.Collections.Generic.IEnumerable userInfo_Roles, bool? userInfo_CanView, bool? userInfo_CanEdit, bool? userInfo_CanDelete, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -1615,8 +665,62 @@ public virtual async System.Threading.Tasks.Task GetMyActorProfileAsyn var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1my/actors" - urlBuilder_.Append("api/v1my/actors"); + // Operation Path: "api/v1/my/messages/Paginated" + urlBuilder_.Append("api/v1/my/messages/Paginated"); + urlBuilder_.Append('?'); + if (startDate != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("StartDate")).Append('=').Append(System.Uri.EscapeDataString(startDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (endDate != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("EndDate")).Append('=').Append(System.Uri.EscapeDataString(endDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (pageNumber != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("PageNumber")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageNumber, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (pageSize != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("PageSize")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageSize, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_OwnerId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.OwnerId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_OwnerId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_TenantId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.TenantId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_TenantId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_FirstName != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.FirstName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_FirstName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_LastName != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.LastName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_LastName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_Email != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.Email")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_Email, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_Roles != null) + { + foreach (var item_ in userInfo_Roles) { urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.Roles")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } + } + if (userInfo_CanView != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.CanView")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_CanView, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_CanEdit != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.CanEdit")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_CanEdit, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (userInfo_CanDelete != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.CanDelete")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_CanDelete, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1643,7 +747,7 @@ public virtual async System.Threading.Tasks.Task GetMyActorProfileAsyn var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -1661,16 +765,6 @@ public virtual async System.Threading.Tasks.Task GetMyActorProfileAsyn throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else - if (status_ == 404) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); - } - else if (status_ == 500) { string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); @@ -1701,46 +795,58 @@ public virtual async System.Threading.Tasks.Task GetMyActorProfileAsyn } /// - /// Creates a new actor session with empty history. + /// Creates new Chat Message with initial message prompt/response history /// /// - /// Sample request: + /// Types of Chat Completion are: + ///
1. Informational Prompt: A prompt requesting information + ///
- Example Prompt: "What's the capital of France?" + ///
- Example Response: "The capital of France is Paris." + ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. + ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” + ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” + ///
Sample request: ///
///
HttpPost Body ///
{ ///
"Id": 00000000-0000-0000-0000-000000000000, - ///
"Name": "John Doe" + ///
"Message": "Hi, I am interested in learning about Agent Framework." ///
} ///
///
"version": 1.0 ///
- /// The command containing actor creation details. - /// OK + /// Created /// A server side error occurred. - public virtual System.Threading.Tasks.Task SaveMyActorAsync(SaveMyActorCommand body) + public virtual System.Threading.Tasks.Task CreateMyChatMessageAsync(CreateMyChatMessageCommand body) { - return SaveMyActorAsync(body, System.Threading.CancellationToken.None); + return CreateMyChatMessageAsync(body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Creates a new actor session with empty history. + /// Creates new Chat Message with initial message prompt/response history /// /// - /// Sample request: + /// Types of Chat Completion are: + ///
1. Informational Prompt: A prompt requesting information + ///
- Example Prompt: "What's the capital of France?" + ///
- Example Response: "The capital of France is Paris." + ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. + ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” + ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” + ///
Sample request: ///
///
HttpPost Body ///
{ ///
"Id": 00000000-0000-0000-0000-000000000000, - ///
"Name": "John Doe" + ///
"Message": "Hi, I am interested in learning about Agent Framework." ///
} ///
///
"version": 1.0 ///
- /// The command containing actor creation details. - /// OK + /// Created /// A server side error occurred. - public virtual async System.Threading.Tasks.Task SaveMyActorAsync(SaveMyActorCommand body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task CreateMyChatMessageAsync(CreateMyChatMessageCommand body, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -1757,8 +863,8 @@ public virtual async System.Threading.Tasks.Task SaveMyActorAsync(Save var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1my/actors" - urlBuilder_.Append("api/v1my/actors"); + // Operation Path: "api/v1/my/messages" + urlBuilder_.Append("api/v1/my/messages"); PrepareRequest(client_, request_, urlBuilder_); @@ -1783,19 +889,9 @@ public virtual async System.Threading.Tasks.Task SaveMyActorAsync(Save ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else if (status_ == 201) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -2140,12 +1236,12 @@ public virtual async System.Threading.Tasks.Task Ge ///
"ChatSessionId": 1efb5e99-3a78-43df-a512-7d8ff498499e ///
"api-version": 1.0 /// - /// The identifier of the chat session. + /// The identifier of the chat session. /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetMyChatSessionAsync(System.Guid chatSessionId) + public virtual System.Threading.Tasks.Task GetMyChatSessionAsync(System.Guid id) { - return GetMyChatSessionAsync(chatSessionId, System.Threading.CancellationToken.None); + return GetMyChatSessionAsync(id, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2159,13 +1255,13 @@ public virtual System.Threading.Tasks.Task GetMyChatSessionAsync ///
"ChatSessionId": 1efb5e99-3a78-43df-a512-7d8ff498499e ///
"api-version": 1.0 /// - /// The identifier of the chat session. + /// The identifier of the chat session. /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetMyChatSessionAsync(System.Guid chatSessionId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetMyChatSessionAsync(System.Guid id, System.Threading.CancellationToken cancellationToken) { - if (chatSessionId == null) - throw new System.ArgumentNullException("chatSessionId"); + if (id == null) + throw new System.ArgumentNullException("id"); var client_ = _httpClient; var disposeClient_ = false; @@ -2178,9 +1274,9 @@ public virtual async System.Threading.Tasks.Task GetMyChatSessio var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/my/chat/{chatSessionId}" + // Operation Path: "api/v1/my/chat/{id}" urlBuilder_.Append("api/v1/my/chat/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(chatSessionId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2264,6 +1360,160 @@ public virtual async System.Threading.Tasks.Task GetMyChatSessio } } + /// + /// Creates new Chat Session with initial message prompt/response history + /// + /// + /// Types of Chat Completion are: + ///
1. Informational Prompt: A prompt requesting information + ///
- Example Prompt: "What's the capital of France?" + ///
- Example Response: "The capital of France is Paris." + ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. + ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” + ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” + ///
Sample request: + ///
+ ///
HttpPost Body + ///
{ + ///
"Id": 00000000-0000-0000-0000-000000000000, + ///
"Message": "Hi, I am interested in learning about Agent Framework." + ///
} + ///
+ ///
"version": 1.0 + ///
+ /// Created + /// A server side error occurred. + public virtual System.Threading.Tasks.Task CreateMyChatSessionAsync(CreateMyChatSessionCommand body) + { + return CreateMyChatSessionAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Creates new Chat Session with initial message prompt/response history + /// + /// + /// Types of Chat Completion are: + ///
1. Informational Prompt: A prompt requesting information + ///
- Example Prompt: "What's the capital of France?" + ///
- Example Response: "The capital of France is Paris." + ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. + ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” + ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” + ///
Sample request: + ///
+ ///
HttpPost Body + ///
{ + ///
"Id": 00000000-0000-0000-0000-000000000000, + ///
"Message": "Hi, I am interested in learning about Agent Framework." + ///
} + ///
+ ///
"version": 1.0 + ///
+ /// Created + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task CreateMyChatSessionAsync(CreateMyChatSessionCommand body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings); + var content_ = new System.Net.Http.ByteArrayContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/my/chat" + urlBuilder_.Append("api/v1/my/chat"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 201) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 400) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 401) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + else + if (status_ == 500) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Internal Server Error", status_, responseText_, headers_, null); + } + else + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + protected struct ObjectResponseResult { public ObjectResponseResult(T responseObject, string responseText) @@ -2520,7 +1770,7 @@ public partial class ChatSessionDtoPaginatedList } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class CreateChatMessageCommand + public partial class CreateMyChatMessageCommand { [System.Text.Json.Serialization.JsonPropertyName("Id")] @@ -2538,21 +1788,21 @@ public partial class CreateChatMessageCommand } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class CreateChatSessionCommand + public partial class CreateMyChatSessionCommand { [System.Text.Json.Serialization.JsonPropertyName("Id")] public System.Guid Id { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("ActorId")] - public System.Guid ActorId { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("Title")] public string Title { get; set; } [System.Text.Json.Serialization.JsonPropertyName("Message")] public string Message { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("UserInfo")] + public IUserEntity UserInfo { get; set; } + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] @@ -2589,7 +1839,7 @@ public partial class IUserEntity } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class PatchChatSessionCommand + public partial class PatchMyChatSessionCommand { [System.Text.Json.Serialization.JsonPropertyName("Id")] @@ -2598,6 +1848,9 @@ public partial class PatchChatSessionCommand [System.Text.Json.Serialization.JsonPropertyName("Title")] public string Title { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("UserInfo")] + public IUserEntity UserInfo { get; set; } + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] @@ -2651,21 +1904,6 @@ public partial class SaveMyActorCommand } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class UpdateChatSessionCommand - { - - [System.Text.Json.Serialization.JsonPropertyName("Id")] - public System.Guid Id { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("Title")] - public string Title { get; set; } - - [System.Text.Json.Serialization.JsonPropertyName("Messages")] - public System.Collections.Generic.ICollection Messages { get; set; } - - } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] diff --git a/src/Presentation.Blazor/ConfigureServicesAuth.cs b/src/Presentation.Blazor/ConfigureServicesAuth.cs index 3cef81c..33be529 100644 --- a/src/Presentation.Blazor/ConfigureServicesAuth.cs +++ b/src/Presentation.Blazor/ConfigureServicesAuth.cs @@ -126,9 +126,6 @@ public static IServiceCollection AddAccessTokenHttpClient( if (string.IsNullOrWhiteSpace(options.ClientName)) throw new ArgumentNullException(nameof(configureOptions), "ClientName must be provided."); - services.AddOptions() - .ValidateDataAnnotations() - .ValidateOnStart(); services.AddScoped(); services.AddScoped(); diff --git a/src/Presentation.Blazor/Pages/Chat/Services/ChatService.cs b/src/Presentation.Blazor/Pages/Chat/Services/ChatService.cs index 487eb06..238fee5 100644 --- a/src/Presentation.Blazor/Pages/Chat/Services/ChatService.cs +++ b/src/Presentation.Blazor/Pages/Chat/Services/ChatService.cs @@ -17,7 +17,7 @@ public interface IChatService public class ChatService(BackendApiClient client, IUserClaimsInfo userInfo) : ApiService, IChatService { private readonly BackendApiClient _apiClient = client; - private readonly IUserClaimsInfo _userInfo = userInfo; + private readonly IUserClaimsInfo _UserContext = userInfo; public async Task> GetChatSessionsAsync() { @@ -41,25 +41,24 @@ public async Task GetChatSessionAsync(Guid chatSessionId) public async Task CreateSessionAsync(string firstMessage) { - var command = new CreateChatSessionCommand - { - ActorId = _userInfo.ObjectId, + var command = new CreateMyChatSessionCommand + { Message = firstMessage }; - var response = await HandleApiException(() => _apiClient.CreateChatSessionCommandAsync(command)); + var response = await HandleApiException(() => _apiClient.CreateMyChatSessionAsync(command)); return ChatSessionModel.Create(response); } public async Task RenameSessionAsync(Guid chatSessionId, string newTitle) { - await HandleApiException(() => _apiClient.PatchChatSessionCommandAsync(chatSessionId, new PatchChatSessionCommand { Id = chatSessionId, Title = newTitle })); + await HandleApiException(() => _apiClient.PatchMyChatSessionAsync(chatSessionId, new PatchMyChatSessionCommand { Id = chatSessionId, Title = newTitle })); } public async Task SendMessageAsync(Guid chatSessionId, string newMessage) { - var response = await HandleApiException(() => _apiClient.CreateChatMessageCommandAsync( - new CreateChatMessageCommand + var response = await HandleApiException(() => _apiClient.CreateMyChatMessageAsync( + new CreateMyChatMessageCommand { ChatSessionId = chatSessionId, Message = newMessage diff --git a/src/Presentation.Blazor/Presentation.Blazor.csproj b/src/Presentation.Blazor/Presentation.Blazor.csproj index 0c510a1..6af47fe 100644 --- a/src/Presentation.Blazor/Presentation.Blazor.csproj +++ b/src/Presentation.Blazor/Presentation.Blazor.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/src/Presentation.Blazor/Services/UserSyncService.cs b/src/Presentation.Blazor/Services/UserSyncService.cs index 9f595bc..3862cce 100644 --- a/src/Presentation.Blazor/Services/UserSyncService.cs +++ b/src/Presentation.Blazor/Services/UserSyncService.cs @@ -9,7 +9,7 @@ namespace Goodtocode.AgentFramework.Presentation.Blazor.Services; public class UserSyncService(BackendApiClient apiClient, IUserClaimsInfo userInfo) : ApiService, IUserSyncService { private readonly BackendApiClient _apiClient = apiClient; - private readonly IUserClaimsInfo _userInfo = userInfo; + private readonly IUserClaimsInfo _userContext = userInfo; public const string UserSyncClaimName = "user_sync_status"; @@ -40,10 +40,10 @@ public async Task SyncUserAsync(ClaimsPrincipal? user) { await HandleApiException(() => _apiClient.SaveMyActorAsync(new SaveMyActorCommand { - TenantId = _userInfo.TenantId, - FirstName = _userInfo.Givenname, - LastName = _userInfo.Surname, - Email = _userInfo.Email + TenantId = _userContext.TenantId, + FirstName = _userContext.Givenname, + LastName = _userContext.Surname, + Email = _userContext.Email })); } SetSyncStatus(user, SyncStatus.Synced); diff --git a/src/Presentation.WebApi/Auth/ClaimsCurrentUserContext.cs b/src/Presentation.WebApi/Auth/ClaimsCurrentUserContext.cs new file mode 100644 index 0000000..35b31e8 --- /dev/null +++ b/src/Presentation.WebApi/Auth/ClaimsCurrentUserContext.cs @@ -0,0 +1,32 @@ +using Goodtocode.AgentFramework.Core.Application.Abstractions; + +namespace Goodtocode.AgentFramework.Presentation.WebApi.Auth; + +/// +/// Provides the current user context by reading from HTTP authentication claims. +/// +/// This class bridges the infrastructure layer (claims reading) with the application layer +/// (ICurrentUserContext) by exposing OwnerId and TenantId from the authenticated user's claims. +public sealed class ClaimsCurrentUserContext : ICurrentUserContext +{ + private readonly IClaimsReader claimsReader; + + /// + /// Initializes a new instance of the class. + /// + /// The claims reader for accessing HTTP authentication context. + public ClaimsCurrentUserContext(IClaimsReader claimsReader) + { + this.claimsReader = claimsReader; + } + + /// + /// Gets the owner identifier from the authenticated user's object identifier claim. + /// + public Guid OwnerId => claimsReader.ObjectId; + + /// + /// Gets the tenant identifier from the authenticated user's tenant claim. + /// + public Guid TenantId => claimsReader.TenantId; +} \ No newline at end of file diff --git a/src/Presentation.WebApi/Auth/ClaimsUserInfo.cs b/src/Presentation.WebApi/Auth/ClaimsUserInfo.cs index d48f71b..2aad957 100644 --- a/src/Presentation.WebApi/Auth/ClaimsUserInfo.cs +++ b/src/Presentation.WebApi/Auth/ClaimsUserInfo.cs @@ -1,15 +1,15 @@ namespace Goodtocode.AgentFramework.Presentation.WebApi.Auth; /// -/// User information implementation that retrieves data from the current HTTP context. +/// Infrastructure implementation that reads user claims from the current HTTP request context. /// -/// HttpContext containing claims -public class ClaimsUserInfo(IHttpContextAccessor contextAccessor) : IClaimsUserInfo +/// HttpContext containing authentication claims +public class HttpClaimsReader(IHttpContextAccessor contextAccessor) : IClaimsReader { private readonly HttpContext? context = contextAccessor?.HttpContext; /// - /// Gets the unique identifier of the user object associated with the current context. + /// Gets the unique identifier of the user object from the objectidentifier claim. /// /// This property retrieves the value of the "objectidentifier" claim from the current user's context. /// Ensure that the claim is present and properly formatted as a GUID in the authentication token. @@ -18,45 +18,45 @@ public class ClaimsUserInfo(IHttpContextAccessor contextAccessor) : IClaimsUserI out var objectId) ? objectId : Guid.Empty; /// - /// Gets the unique identifier of the tenant associated with the current user. + /// Gets the unique identifier of the tenant from the tenantid claim. /// /// The tenant ID is extracted from the user's claims using the claim type - /// "http://schemas.microsoft.com/identity/claims/tenantid". Ensure that the claim is present and valid in the - /// user's identity for this property to return a meaningful value. + /// "http://schemas.microsoft.com/identity/claims/tenantid". Ensure that the claim is present and valid + /// in the user's identity for this property to return a meaningful value. public Guid TenantId => Guid.TryParse( context?.User.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value, out var tenantId) ? tenantId : Guid.Empty; /// - /// Gets the first name of the user based on the associated claims. + /// Gets the first name of the user from the givenname claim. /// - public string Givenname => context?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname")?.Value ?? string.Empty; + public string FirstName => context?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname")?.Value ?? string.Empty; /// - /// Gets the last name of the current user based on their claims. + /// Gets the last name of the user from the surname claim. /// - public string Surname => context?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname")?.Value ?? string.Empty; + public string LastName => context?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname")?.Value ?? string.Empty; /// - /// Gets the email address of the current user based on their User Principal Name (UPN) claim. + /// Gets the email address from the User Principal Name (UPN) claim. /// public string Email => context?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn")?.Value ?? string.Empty; /// - /// Gets the collection of scopes associated with the current user. + /// Gets the collection of OAuth scopes granted for the current request. /// - /// Scopes are typically used to define the permissions or access levels granted to the user. + /// Scopes are typically used to define the permissions or access levels granted to the application. /// This property retrieves the scopes from the user's claims, specifically from the claim with the type /// "http://schemas.microsoft.com/identity/claims/scope". public ICollection Scopes => context?.User.FindFirst("http://schemas.microsoft.com/identity/claims/scope")?.Value?.Split(' ').ToList() ?? []; /// - /// Gets the collection of roles associated with the current user. + /// Gets the collection of roles assigned to the authenticated user. /// public ICollection Roles => context?.User.FindAll("http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(c => c.Value).ToList() ?? []; /// - /// Gets the collection of group names associated with the current user. + /// Gets the collection of security group identifiers from the groups claim. /// public ICollection Groups => context?.User.FindAll("groups").Select(c => c.Value).ToList() ?? []; } \ No newline at end of file diff --git a/src/Presentation.WebApi/Auth/ConfigureServices.cs b/src/Presentation.WebApi/Auth/ConfigureServices.cs index 4fde5a4..2280257 100644 --- a/src/Presentation.WebApi/Auth/ConfigureServices.cs +++ b/src/Presentation.WebApi/Auth/ConfigureServices.cs @@ -1,11 +1,11 @@ - +using Goodtocode.AgentFramework.Core.Application.Abstractions; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; namespace Goodtocode.AgentFramework.Presentation.WebApi.Auth; /// -/// Presentation Layer WebApi Configuration +/// Presentation Layer WebApi Authentication Configuration /// public static class ConfigureServices { @@ -16,11 +16,11 @@ private struct TokenRoleClaimTypes } /// - /// AddUserInfo + /// Configures authentication with Microsoft Identity Platform and registers user context services. /// - /// - /// - /// + /// The service collection to configure. + /// The application configuration. + /// The configured service collection. public static IServiceCollection AddAuthenticationWithRoles(this IServiceCollection services, IConfiguration configuration) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) @@ -35,9 +35,10 @@ public static IServiceCollection AddAuthenticationWithRoles(this IServiceCollect configuration.GetSection("EntraExternalId").Bind(identityOptions); }); - services.AddScoped(); - services.AddScoped(typeof(IPipelineBehavior<>), typeof(UserInfoBehavior<>)); - services.AddScoped(typeof(IPipelineBehavior<,>), typeof(UserInfoBehavior<,>)); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(typeof(IPipelineBehavior<>), typeof(UserContextBehavior<>)); + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(UserContextBehavior<,>)); return services; } diff --git a/src/Presentation.WebApi/Auth/IClaimsUserInfo.cs b/src/Presentation.WebApi/Auth/IClaimsUserInfo.cs index 5851963..324af75 100644 --- a/src/Presentation.WebApi/Auth/IClaimsUserInfo.cs +++ b/src/Presentation.WebApi/Auth/IClaimsUserInfo.cs @@ -1,50 +1,50 @@ namespace Goodtocode.AgentFramework.Presentation.WebApi.Auth; /// -/// Represents a user's information, including identifiers, personal details, and contact information. +/// Infrastructure service for reading user identity claims from HTTP authentication context. /// -/// This interface provides a standardized structure for accessing user-related data, such as unique -/// identifiers, name details, and email address. It is commonly used in scenarios where user identity and contact -/// information need to be retrieved or processed. -public interface IClaimsUserInfo +/// This interface provides access to claims extracted from the HTTP authentication token, +/// including user identifiers, personal information, and authorization scopes. Implementations +/// typically read from HttpContext.User claims. Property names align with Microsoft Identity claim types. +public interface IClaimsReader { /// - /// Gets the unique identifier for the object. + /// Gets the unique identifier for the authenticated user (object identifier from IdP). /// Guid ObjectId { get; } /// - /// Gets the unique identifier of the tenant associated with the current context. + /// Gets the unique identifier of the tenant associated with the authenticated user. /// Guid TenantId { get; } /// - /// Gets the first name of the individual. + /// Gets the first name of the authenticated user. /// - string Givenname { get; } + string FirstName { get; } /// - /// Gets the last name of the individual. + /// Gets the last name of the authenticated user. /// - string Surname { get; } + string LastName { get; } /// - /// Gets the email address associated with the entity. + /// Gets the email address associated with the authenticated user. /// string Email { get; } /// - /// Gets the highest role + /// Gets the collection of roles assigned to the authenticated user. /// - ICollection Roles { get; } + ICollection Roles { get; } /// - /// Gets the collection of scopes associated with the current operation. + /// Gets the collection of OAuth scopes granted to the current request. /// ICollection Scopes { get; } /// - /// Gets the collection of group names associated with the current user. + /// Gets the collection of security group identifiers associated with the authenticated user. /// ICollection Groups { get; } } diff --git a/src/Presentation.WebApi/Auth/UserInfoBehavior.cs b/src/Presentation.WebApi/Auth/UserInfoBehavior.cs index 9a64c13..68ce295 100644 --- a/src/Presentation.WebApi/Auth/UserInfoBehavior.cs +++ b/src/Presentation.WebApi/Auth/UserInfoBehavior.cs @@ -4,59 +4,65 @@ namespace Goodtocode.AgentFramework.Presentation.WebApi.Auth; /// -/// Represents a pipeline behavior that injects user information into a request before passing it to the next -/// delegate in the pipeline. +/// Pipeline behavior that automatically injects authenticated user context into requests +/// that implement . /// -/// This behavior ensures that the instance is assigned to the property of the request before invoking the next delegate. -/// The type of the request. Must implement . -/// -public class UserInfoBehavior(IClaimsUserInfo userInfo) : IPipelineBehavior - where TRequest : IUserInfoRequest +/// This behavior intercepts requests marked with +/// and populates the property with the current +/// authenticated user's context before invoking the request handler. +/// The type of the request. Must implement . +/// Service for reading claims from HTTP authentication context +public class UserContextBehavior(IClaimsReader claimsReader) : IPipelineBehavior + where TRequest : IRequiresUserContext { /// - /// Processes the specified request and invokes the next delegate in the request pipeline. + /// Processes the request by injecting user context before invoking the next handler. /// - /// This method modifies the by setting its UserInfo - /// property before invoking the next handler. Ensure that is not null and is - /// called to continue the request pipeline. - /// The request to be processed. The request object may be modified during processing. - /// The delegate to invoke the next handler in the pipeline. This delegate must be called to continue processing - /// the request. + /// The request to be processed. + /// The delegate to invoke the next handler in the pipeline. /// A token that can be used to propagate notification that the operation should be canceled. public async Task Handle(TRequest request, RequestDelegateInvoker nextInvoker, CancellationToken cancellationToken) { - request.UserInfo = UserEntity.Create(userInfo.ObjectId, userInfo.TenantId, userInfo.Givenname, userInfo.Surname, userInfo.Email, userInfo.Roles); + request.UserContext = UserContext.Create( + claimsReader.ObjectId, + claimsReader.TenantId, + claimsReader.FirstName, + claimsReader.LastName, + claimsReader.Email, + claimsReader.Roles); await nextInvoker(); } } /// -/// Represents a pipeline behavior that injects user information into a request before passing it to the next -/// delegate in the pipeline. +/// Pipeline behavior that automatically injects authenticated user context into requests +/// that implement and return a response. /// -/// This behavior ensures that the instance is assigned to the property of the request before invoking the next delegate. -/// The type of the request. Must implement . +/// This behavior intercepts requests marked with +/// and populates the property with the current +/// authenticated user's context before invoking the request handler. +/// The type of the request. Must implement . /// The type of the response. -/// -public class UserInfoBehavior(IClaimsUserInfo userInfo) : IPipelineBehavior - where TRequest : IUserInfoRequest +/// Service for reading claims from HTTP authentication context +public class UserContextBehavior(IClaimsReader claimsReader) : IPipelineBehavior + where TRequest : IRequiresUserContext { /// - /// Processes the specified request and invokes the next delegate in the request pipeline. + /// Processes the request by injecting user context before invoking the next handler. /// - /// This method modifies the by setting its UserInfo - /// property before invoking the next handler. Ensure that is not null and is - /// called to continue the request pipeline. - /// The request to be processed. The request object may be modified during processing. - /// The delegate to invoke the next handler in the pipeline. This delegate must be called to continue processing - /// the request. + /// The request to be processed. + /// The delegate to invoke the next handler in the pipeline. /// A token that can be used to propagate notification that the operation should be canceled. /// A task that represents the asynchronous operation. The task result contains the response of type . public async Task Handle(TRequest request, RequestDelegateInvoker nextInvoker, CancellationToken cancellationToken) { - request.UserInfo = UserEntity.Create(userInfo.ObjectId, userInfo.TenantId, userInfo.Givenname, userInfo.Surname, userInfo.Email, userInfo.Roles); + request.UserContext = UserContext.Create( + claimsReader.ObjectId, + claimsReader.TenantId, + claimsReader.FirstName, + claimsReader.LastName, + claimsReader.Email, + claimsReader.Roles); return await nextInvoker(); } } diff --git a/src/Presentation.WebApi/ChatCompletion/ChatSessionController.cs b/src/Presentation.WebApi/ChatCompletion/ChatSessionController.cs deleted file mode 100644 index 3c7a4c8..0000000 --- a/src/Presentation.WebApi/ChatCompletion/ChatSessionController.cs +++ /dev/null @@ -1,241 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; -using Goodtocode.AgentFramework.Core.Application.Common.Models; -using Goodtocode.AgentFramework.Presentation.WebApi.Common; -using Microsoft.AspNetCore.Authorization; - -namespace Goodtocode.AgentFramework.Presentation.WebApi.ChatCompletion; - -/// -/// Chat completion endpoints to create a chat, continue a chat, delete a chat and retrieve chat history -/// -[ApiController] -[ApiConventionType(typeof(DefaultApiConventions))] -[Route("api/v{version:apiVersion}/admin/sessions")] -[ApiVersion("1.0")] -[Authorize] -public class ChatSessionController : ApiControllerBase -{ - /// Get Chat Session with history - /// - /// Sample request: - /// - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - /// "api-version": 1.0 - /// - /// - /// - /// ChatSessionDto - /// { Id: 1efb5e99-3a78-43df-a512-7d8ff498499e - /// AuthorKey: 4dfb5e99-3a78-43df-a512-7d8ff498499e - /// Messages: [ - /// { - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e, - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// } - /// }] - /// - [HttpGet("{id}", Name = "GetChatSessionQuery")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Get(Guid id) - { - return await Mediator.Send(new GetChatSessionQuery - { - Id = id - }); - } - - /// Get All Chat Sessions Query - /// - /// Sample request: - /// - /// "StartDate": "2024-06-01T00:00:00Z" - /// "EndDate": "2024-12-01T00:00:00Z" - /// "api-version": 1.0 - /// - /// - /// - /// ChatSessionDto - /// { Id: 1efb5e99-3a78-43df-a512-7d8ff498499e - /// ActorId: 4dfb5e99-3a78-43df-a512-7d8ff498499e - /// Timestamp: "2024-06-03T11:21:00Z" - /// Messages: [ - /// { - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e, - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// } - /// }] - /// - [HttpGet(Name = "GetChatSessionsQuery")] - [ProducesResponseType>(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> GetAll() - { - return await Mediator.Send(new GetChatSessionsQuery()); - } - - /// Get Chat Sessions Paginated Query - /// - /// Sample request: - /// - /// "StartDate": "2024-06-01T00:00:00Z" - /// "EndDate": "2024-12-01T00:00:00Z" - /// "PageNumber": 1 - /// "PageSize" : 10 - /// "api-version": 1.0 - /// - /// - /// - /// ChatSessionDto - /// { Id: 1efb5e99-3a78-43df-a512-7d8ff498499e - /// ActorId: 4dfb5e99-3a78-43df-a512-7d8ff498499e - /// Messages: [ - /// { - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e, - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// } - /// }] - /// - [HttpGet("Paginated", Name = "GetChatSessionsPaginatedQuery")] - [ProducesResponseType>(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetChatSessionsPaginatedQuery([FromQuery] GetChatSessionsPaginatedQuery query) - { - return await Mediator.Send(query); - } - - /// - /// Creates new Chat Session with initial message prompt/response history - /// - /// - /// Types of Chat Completion are: - /// 1. Informational Prompt: A prompt requesting information - /// - Example Prompt: "What's the capital of France?" - /// - Example Response: "The capital of France is Paris." - /// 2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. - /// - Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” - /// - Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” - /// Sample request: - /// - /// HttpPost Body - /// { - /// "Id": 00000000-0000-0000-0000-000000000000, - /// "Message": "Hi, I am interested in learning about Agent Framework." - /// } - /// - /// "version": 1.0 - /// - /// - /// - /// ChatSessionDto - /// { Id: 1efb5e99-3a78-43df-a512-7d8ff498499e - /// ActorId: 4dfb5e99-3a78-43df-a512-7d8ff498499e - /// Messages: [ - /// { - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e, - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// } - /// }] - /// - [HttpPost(Name = "CreateChatSessionCommand")] - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Post(CreateChatSessionCommand command) - { - var response = await Mediator.Send(command); - return CreatedAtAction(nameof(Get), new { response.Id }, response); - } - - /// - /// Update ChatSession Command, typically with changing the title or adding a new message - /// - /// - /// Sample request: - /// - /// HttpPut Body - /// { - /// "Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - /// "Message": "Hi, I am interested in learning about Agent Framework.", - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// } - /// - /// "version": 1.0 - /// - /// - /// - /// { - /// "Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - /// "Message": "Hi, I am interested in learning about Agent Framework.", - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// } - [HttpPut(Name = "UpdateChatSessionCommand")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Put(UpdateChatSessionCommand command) - { - await Mediator.Send(command); - - return NoContent(); - } - - /// - /// Patch Chat Session Command - /// - /// - /// Sample request: - /// - /// HttpPatch Body - /// { - /// "Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", - /// "Title": "Agent Framework Chat Session" - /// } - /// - /// "version": 1.0 - /// - /// - /// - /// NoContent - [HttpPatch("{id}", Name = "PatchChatSessionCommand")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Patch(Guid id, PatchChatSessionCommand command) - { - command.Id = id; - await Mediator.Send(command); - - return NoContent(); - } - - /// Remove ChatSession Command - /// - /// Sample request: - /// - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e - /// "api-version": 1.0 - /// - /// - /// NoContent - [HttpDelete("{id}", Name = "RemoveChatSessionCommand")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - [ProducesDefaultResponseType] - public async Task Delete(Guid id) - { - await Mediator.Send(new DeleteChatSessionCommand() { Id = id }); - - return NoContent(); - } -} \ No newline at end of file diff --git a/src/Presentation.WebApi/ChatCompletion/ChatMessageController.cs b/src/Presentation.WebApi/ChatCompletion/MyChatMessageController.cs similarity index 79% rename from src/Presentation.WebApi/ChatCompletion/ChatMessageController.cs rename to src/Presentation.WebApi/ChatCompletion/MyChatMessageController.cs index d338d5f..f6c2252 100644 --- a/src/Presentation.WebApi/ChatCompletion/ChatMessageController.cs +++ b/src/Presentation.WebApi/ChatCompletion/MyChatMessageController.cs @@ -10,10 +10,10 @@ namespace Goodtocode.AgentFramework.Presentation.WebApi.ChatCompletion; /// [ApiController] [ApiConventionType(typeof(DefaultApiConventions))] -[Route("api/v{version:apiVersion}/admin/messages")] +[Route("api/v{version:apiVersion}/my/messages")] [ApiVersion("1.0")] [Authorize] -public class ChatMessageController : ApiControllerBase +public class MyChatMessageController : ApiControllerBase { /// Get Chat Message /// @@ -30,45 +30,19 @@ public class ChatMessageController : ApiControllerBase /// "Content": "Certainly! Agent Framework is a great framework for AI." /// } /// - [HttpGet("{id}", Name = "GetChatMessageQuery")] + [HttpGet("{id}", Name = "GetMyChatMessage")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task Get(Guid id) { - return await Mediator.Send(new GetChatMessageQuery + return await Mediator.Send(new GetMyChatMessageQuery { Id = id }); } - /// Get All Chat Messages for a session Query - /// - /// Sample request: - /// - /// "StartDate": "2024-06-01T00:00:00Z" - /// "EndDate": "2024-12-01T00:00:00Z" - /// "api-version": 1.0 - /// - /// - /// - /// ChatMessageDto - /// [{ - /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e, - /// "Content": "Certainly! Agent Framework is a great framework for AI.", - /// }] - /// - [HttpGet(Name = "GetChatMessagesQuery")] - [ProducesResponseType>(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> GetAll() - { - return await Mediator.Send(new GetChatMessagesQuery()); - } - /// Get Chat Messages Paginated Query /// /// Sample request: @@ -91,11 +65,11 @@ public async Task> GetAll() /// } /// }] /// - [HttpGet("Paginated", Name = "GetChatMessagesPaginatedQuery")] + [HttpGet("Paginated", Name = "GetMyChatMessagesPaginated")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetPaginated([FromQuery] GetChatMessagesPaginatedQuery query) + public async Task>> GetPaginated([FromQuery] GetMyChatMessagesPaginatedQuery query) { return await Mediator.Send(query); } @@ -133,14 +107,44 @@ public async Task>> GetPaginated([Fro /// } /// }] /// - [HttpPost(Name = "CreateChatMessageCommand")] + [HttpPost(Name = "CreateMyChatMessage")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Post(CreateChatMessageCommand command) + public async Task Post(CreateMyChatMessageCommand command) { var response = await Mediator.Send(command); return CreatedAtAction(nameof(Get), new { response.Id }, response); } + + /// + /// Patch Chat Session Command + /// + /// + /// Sample request: + /// + /// HttpPatch Body + /// { + /// "Id": "60fb5e99-3a78-43df-a512-7d8ff498499e", + /// "Title": "Agent Framework Chat Session" + /// } + /// + /// "version": 1.0 + /// + /// + /// + /// NoContent + [HttpPatch("{id}", Name = "PatchMyChatSession")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task Patch(Guid id, PatchMyChatSessionCommand command) + { + command.Id = id; + await Mediator.Send(command); + + return NoContent(); + } } \ No newline at end of file diff --git a/src/Presentation.WebApi/ChatCompletion/MyChatSessionController.cs b/src/Presentation.WebApi/ChatCompletion/MyChatSessionController.cs index 5542862..eb3eb95 100644 --- a/src/Presentation.WebApi/ChatCompletion/MyChatSessionController.cs +++ b/src/Presentation.WebApi/ChatCompletion/MyChatSessionController.cs @@ -34,7 +34,7 @@ public class MyChatSessionController : ApiControllerBase [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task> GetMyChatSessions() + public async Task> GetAll() { return await Mediator.Send(new GetMyChatSessionsQuery()); } @@ -63,7 +63,7 @@ public async Task> GetMyChatSessions() [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetMyChatSessionsPaginated(DateTime? startDate, DateTime? endDate, int pageNumber = 1, int pageSize = 10) + public async Task>> GetPaginated(DateTime? startDate, DateTime? endDate, int pageNumber = 1, int pageSize = 10) { var query = new GetMyChatSessionsPaginatedQuery() { @@ -85,17 +85,61 @@ public async Task>> GetMyChatSessions /// "ChatSessionId": 1efb5e99-3a78-43df-a512-7d8ff498499e /// "api-version": 1.0 /// - /// The identifier of the chat session. + /// The identifier of the chat session. /// /// ChatSessionDto representing the chat session details. /// - [HttpGet("{chatSessionId}", Name = "GetMyChatSession")] + [HttpGet("{id}", Name = "GetMyChatSession")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task GetMyChatSession(Guid chatSessionId) + public async Task Get(Guid id) { - return await Mediator.Send(new GetMyChatSessionQuery() { ChatSessionId = chatSessionId }); + return await Mediator.Send(new GetMyChatSessionQuery() { Id = id }); + } + + /// + /// Creates new Chat Session with initial message prompt/response history + /// + /// + /// Types of Chat Completion are: + /// 1. Informational Prompt: A prompt requesting information + /// - Example Prompt: "What's the capital of France?" + /// - Example Response: "The capital of France is Paris." + /// 2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses. + /// - Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games” + /// - Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.” + /// Sample request: + /// + /// HttpPost Body + /// { + /// "Id": 00000000-0000-0000-0000-000000000000, + /// "Message": "Hi, I am interested in learning about Agent Framework." + /// } + /// + /// "version": 1.0 + /// + /// + /// + /// ChatSessionDto + /// { Id: 1efb5e99-3a78-43df-a512-7d8ff498499e + /// ActorId: 4dfb5e99-3a78-43df-a512-7d8ff498499e + /// Messages: [ + /// { + /// "Id": 60fb5e99-3a78-43df-a512-7d8ff498499e, + /// "Content": "Certainly! Agent Framework is a great framework for AI.", + /// } + /// }] + /// + [HttpPost(Name = "CreateMyChatSession")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task Post(CreateMyChatSessionCommand command) + { + var response = await Mediator.Send(command); + return CreatedAtAction(nameof(Get), new { response.Id }, response); } } \ No newline at end of file diff --git a/src/Presentation.WebApi/Common/ApiControllerBase.cs b/src/Presentation.WebApi/Common/ApiControllerBase.cs index c8a6205..42a5b74 100644 --- a/src/Presentation.WebApi/Common/ApiControllerBase.cs +++ b/src/Presentation.WebApi/Common/ApiControllerBase.cs @@ -22,8 +22,8 @@ public abstract class ApiControllerBase : ControllerBase /// /// Gets the user information associated with the current HTTP context. /// - /// The instance is resolved from the dependency injection container + /// The instance is resolved from the dependency injection container /// using the current HTTP context's request services. Ensure that the required service is registered in the /// application's service collection. - protected IUserEntity UserInfo => HttpContext.RequestServices.GetRequiredService(); + protected IUserContext UserContext => HttpContext.RequestServices.GetRequiredService(); } diff --git a/src/Presentation.WebApi/Presentation.WebApi.csproj b/src/Presentation.WebApi/Presentation.WebApi.csproj index 612b847..213d0f7 100644 --- a/src/Presentation.WebApi/Presentation.WebApi.csproj +++ b/src/Presentation.WebApi/Presentation.WebApi.csproj @@ -17,7 +17,7 @@ - + @@ -30,7 +30,7 @@ - + diff --git a/src/Tests.Integration/Actor/CreateActorCommandStepDefinitions.cs b/src/Tests.Integration/Actor/CreateActorCommandStepDefinitions.cs index 5552316..6d5151d 100644 --- a/src/Tests.Integration/Actor/CreateActorCommandStepDefinitions.cs +++ b/src/Tests.Integration/Actor/CreateActorCommandStepDefinitions.cs @@ -55,7 +55,7 @@ public async Task WhenICreateAAuthor() { if (_exists) { - var actor = ActorEntity.Create(_id, _ownerId, _tenantId, "John", "Doe", "jdoe@goodtocode.com"); + var actor = ActorEntity.Create(_id, "John", "Doe", "jdoe@goodtocode.com"); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); } @@ -77,7 +77,7 @@ public async Task WhenICreateAAuthor() { try { - var handler = new CreateAuthorCommandHandler(context); + var handler = new CreateActorCommandHandler(context); await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs b/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs index 3155d68..d1ea451 100644 --- a/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs +++ b/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs @@ -1,5 +1,6 @@ using Goodtocode.AgentFramework.Core.Application.Actor; using Goodtocode.AgentFramework.Core.Domain.Actor; +using Microsoft.AspNetCore.Identity; namespace Goodtocode.AgentFramework.Tests.Integration.Actor { @@ -33,14 +34,14 @@ public async Task WhenIDeleteTheAuthor() { if (_exists) { - var actor = ActorEntity.Create(_id, _id, Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); + var actor = ActorEntity.Create(_id, "John", "Doe", "jdoe@goodtocode.com"); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); } var request = new DeleteActorByOwnerIdCommand() { - OwnerId = _id + OwnerId = userContext.OwnerId }; var validator = new DeleteActorByOwnerIdCommandValidator(); @@ -49,7 +50,7 @@ public async Task WhenIDeleteTheAuthor() if (validationResponse.IsValid) try { - var handler = new DeleteAuthorByOwnerIdCommandHandler(context); + var handler = new DeleteActorByOwnerIdCommandHandler(context); await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/Actor/GetActorByExternalIdQuery.feature b/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature similarity index 100% rename from src/Tests.Integration/Actor/GetActorByExternalIdQuery.feature rename to src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature diff --git a/src/Tests.Integration/Actor/GetActorByExternalIdQuery.feature.cs b/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature.cs similarity index 98% rename from src/Tests.Integration/Actor/GetActorByExternalIdQuery.feature.cs rename to src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature.cs index a0c44d0..012c0d0 100644 --- a/src/Tests.Integration/Actor/GetActorByExternalIdQuery.feature.cs +++ b/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature.cs @@ -30,7 +30,7 @@ public partial class GetActorByExternalIdQueryFeature private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Actor", "Get Actor By External Id Query", "As a actor owner\r\nWhen I select an existing Actor\r\nI can see the actor detail", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); -#line 1 "GetActorByExternalIdQuery.feature" +#line 1 "GetActorByOwnerIdQuery.feature" #line hidden public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext @@ -118,7 +118,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Actor/GetActorByExternalIdQuery.feature.ndjson", 4); + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Actor/GetActorByOwnerIdQuery.feature.ndjson", 4); } [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get Actor By External Id")] diff --git a/src/Tests.Integration/Actor/GetActorByExternalIdQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs similarity index 88% rename from src/Tests.Integration/Actor/GetActorByExternalIdQueryStepDefinitions.cs rename to src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs index d58c594..931cc02 100644 --- a/src/Tests.Integration/Actor/GetActorByExternalIdQueryStepDefinitions.cs +++ b/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs @@ -19,7 +19,7 @@ public void GivenIHaveADefinition(string def) [Given("I have a Actor External Id")] public void GivenIHaveAAuthorExternalId() { - userInfo.OwnerId.ShouldNotBeEmpty(); + userContext.OwnerId.ShouldNotBeEmpty(); } [Given(@"the Actor exists ""([^""]*)""")] @@ -33,14 +33,14 @@ public async Task WhenIGetAAuthor() { if (_exists) { - var actor = ActorEntity.Create(userInfo.OwnerId, userInfo.OwnerId, Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); + var actor = ActorEntity.Create(userContext.OwnerId, "John", "Doe", "jdoe@goodtocode.com"); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); } var request = new GetMyActorQuery() { - UserInfo = userInfo + UserContext = userContext }; var validator = new GetMyActorQueryValidator(); @@ -48,7 +48,7 @@ public async Task WhenIGetAAuthor() if (validationResponse.IsValid) try { - var handler = new GetAuthorByOwnerIdQueryHandler(context); + var handler = new GetActorByOwnerIdQueryHandler(context); _response = await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/Actor/GetActorChatSessionQuery.feature b/src/Tests.Integration/Actor/GetActorChatSessionQuery.feature deleted file mode 100644 index 93f0a4c..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionQuery.feature +++ /dev/null @@ -1,20 +0,0 @@ -@getAuthorChatSessionQuery -Feature: Get Actor Chat Session Query -As a chat user actor -When I select an existing chat session -I can get the chat history messages - -Scenario: Get actor chat session - Given I have a definition "" - And I have a actor id "" - And I have a chat session id "" - And I the chat session exists "" - When I get a chat session - Then The response is "" - And If the response has validation issues I see the "" in the response - And If the response is successful the response has a Id - -Examples: - | def | response | responseErrors | id | chatSessionId | chatSessionExists | - | success | Success | | 098d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | 045d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | true | - | not found | NotFound | | 023d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | 078d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | diff --git a/src/Tests.Integration/Actor/GetActorChatSessionQuery.feature.cs b/src/Tests.Integration/Actor/GetActorChatSessionQuery.feature.cs deleted file mode 100644 index 114539e..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionQuery.feature.cs +++ /dev/null @@ -1,187 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by Reqnroll (https://reqnroll.net/). -// Reqnroll Version:3.0.0.0 -// Reqnroll Generator Version:3.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -#region Designer generated code -#pragma warning disable -using Reqnroll; -namespace Goodtocode.AgentFramework.Tests.Integration.Actor -{ - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] - public partial class GetActorChatSessionQueryFeature - { - - private global::Reqnroll.ITestRunner testRunner; - - private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; - - private static string[] featureTags = new string[] { - "getAuthorChatSessionQuery"}; - - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Actor", "Get Actor Chat Session Query", "As a chat user actor\r\nWhen I select an existing chat session\r\nI can get the chat " + - "history messages", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); - -#line 1 "GetActorChatSessionQuery.feature" -#line hidden - - public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext - { - get - { - return this._testContext; - } - set - { - this._testContext = value; - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] - public static async global::System.Threading.Tasks.Task FeatureSetupAsync(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) - { - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] - public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() - { - await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] - public async global::System.Threading.Tasks.Task TestInitializeAsync() - { - testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); - try - { - if (((testRunner.FeatureContext != null) - && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) - { - await testRunner.OnFeatureEndAsync(); - } - } - finally - { - if (((testRunner.FeatureContext != null) - && testRunner.FeatureContext.BeforeFeatureHookFailed)) - { - throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); - } - if ((testRunner.FeatureContext == null)) - { - await testRunner.OnFeatureStartAsync(featureInfo); - } - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] - public async global::System.Threading.Tasks.Task TestTearDownAsync() - { - if ((testRunner == null)) - { - return; - } - try - { - await testRunner.OnScenarioEndAsync(); - } - finally - { - global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); - testRunner = null; - } - } - - public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) - { - testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); - } - - public async global::System.Threading.Tasks.Task ScenarioStartAsync() - { - await testRunner.OnScenarioStartAsync(); - } - - public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() - { - await testRunner.CollectScenarioErrorsAsync(); - } - - private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() - { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Actor/GetActorChatSessionQuery.feature.ndjson", 4); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get actor chat session")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Get actor chat session")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Get Actor Chat Session Query")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("getAuthorChatSessionQuery")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success", "Success", "", "098d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "045d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "true", "0", null, DisplayName="Get actor chat session(success,Success,,098d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,045d" + - "8e7f-f18f-4a8e-8b3c-3b6a6889fed9,true,0)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found", "NotFound", "", "023d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "078d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "1", null, DisplayName="Get actor chat session(not found,NotFound,,023d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,0" + - "78d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,false,1)")] - public async global::System.Threading.Tasks.Task GetActorChatSession(string def, string response, string responseErrors, string id, string chatSessionId, string chatSessionExists, string @__pickleIndex, string[] exampleTags) - { - string[] tagsOfScenario = exampleTags; - global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); - argumentsOfScenario.Add("def", def); - argumentsOfScenario.Add("response", response); - argumentsOfScenario.Add("responseErrors", responseErrors); - argumentsOfScenario.Add("id", id); - argumentsOfScenario.Add("chatSessionId", chatSessionId); - argumentsOfScenario.Add("chatSessionExists", chatSessionExists); - string pickleIndex = @__pickleIndex; - global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Get actor chat session", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); - string[] tagsOfRule = ((string[])(null)); - global::Reqnroll.RuleInfo ruleInfo = null; -#line 7 -this.ScenarioInitialize(scenarioInfo, ruleInfo); -#line hidden - if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) - { - await testRunner.SkipScenarioAsync(); - } - else - { - await this.ScenarioStartAsync(); -#line 8 - await testRunner.GivenAsync(string.Format("I have a definition \"{0}\"", def), ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); -#line hidden -#line 9 - await testRunner.AndAsync(string.Format("I have a actor id \"{0}\"", id), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 10 - await testRunner.AndAsync(string.Format("I have a chat session id \"{0}\"", chatSessionId), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 11 - await testRunner.AndAsync(string.Format("I the chat session exists \"{0}\"", chatSessionExists), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 12 - await testRunner.WhenAsync("I get a chat session", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); -#line hidden -#line 13 - await testRunner.ThenAsync(string.Format("The response is \"{0}\"", response), ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); -#line hidden -#line 14 - await testRunner.AndAsync(string.Format("If the response has validation issues I see the \"{0}\" in the response", responseErrors), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 15 - await testRunner.AndAsync("If the response is successful the response has a Id", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden - } - await this.ScenarioCleanupAsync(); - } - } -} -#pragma warning restore -#endregion diff --git a/src/Tests.Integration/Actor/GetActorChatSessionQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorChatSessionQueryStepDefinitions.cs deleted file mode 100644 index f711988..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionQueryStepDefinitions.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; -using Goodtocode.AgentFramework.Core.Domain.Actor; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Tests.Integration.Actor; - -[Binding] -[Scope(Tag = "getAuthorChatSessionQuery")] -public class GetActorChatSessionQueryStepDefinitions : TestBase -{ - private Guid _id; - private Guid _chatSessionid; - private bool _exists; - private ChatSessionDto? _response; - - [Given(@"I have a definition ""([^""]*)""")] - public void GivenIHaveADefinition(string def) - { - base.def = def; - } - - [Given(@"I have a actor id ""([^""]*)""")] - public void GivenIHaveAAuthorId(string id) - { - if (string.IsNullOrWhiteSpace(id)) return; - Guid.TryParse(id, out _id).ShouldBeTrue(); - } - - [Given(@"I have a chat session id ""([^""]*)""")] - public void GivenIHaveAChatSessionId(string chatSessionId) - { - if (string.IsNullOrWhiteSpace(chatSessionId)) return; - Guid.TryParse(chatSessionId, out _chatSessionid).ShouldBeTrue(); - } - - [Given(@"I the chat session exists ""([^""]*)""")] - public void GivenITheChatSessionExists(string exists) - { - bool.TryParse(exists, out _exists).ShouldBeTrue(); - } - - [When(@"I get a chat session")] - public async Task WhenIGetAChatSession() - { - if (_exists) - { - var actor = ActorEntity.Create(_id, userInfo.OwnerId, Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); - context.Actors.Add(actor); - await context.SaveChangesAsync(CancellationToken.None); - var chatSession = ChatSessionEntity.Create(_chatSessionid, _id, "Test Session", ChatMessageRole.assistant, "First Message", "First Response"); - context.ChatSessions.Add(chatSession); - await context.SaveChangesAsync(CancellationToken.None); - } - - var request = new GetMyChatSessionQuery() - { - ChatSessionId = _chatSessionid, - UserInfo = userInfo - }; - - var validator = new GetMyChatSessionQueryValidator(); - validationResponse = validator.Validate(request); - if (validationResponse.IsValid) - try - { - var handler = new GetAuthorChatSessionByOwnerIdQueryHandler(context); - _response = await handler.Handle(request, CancellationToken.None); - responseType = CommandResponseType.Successful; - } - catch (Exception e) - { - responseType = HandleAssignResponseType(e); - } - else - responseType = CommandResponseType.BadRequest; - } - - [Then(@"The response is ""([^""]*)""")] - public void ThenTheResponseIs(string response) - { - HandleHasResponseType(response); - } - - [Then(@"If the response has validation issues I see the ""([^""]*)"" in the response")] - public void ThenIfTheResponseHasValidationIssuesISeeTheInTheResponse(string expectedErrors) - { - HandleExpectedValidationErrorsAssertions(expectedErrors); - } - - [Then(@"If the response is successful the response has a Id")] - public void ThenIfTheResponseIsSuccessfulTheResponseHasAId() - { - if (responseType != CommandResponseType.Successful) return; - _response?.Id.ShouldNotBeEmpty(); - } -} diff --git a/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature b/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature deleted file mode 100644 index 7f89414..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature +++ /dev/null @@ -1,35 +0,0 @@ -@getAuthorChatSessionsPaginatedQuery -Feature: Get Actor Chat Sessions Paginated Query -As an actor of chat sessions -When I get chat sessions by actor or by date range -I can get a paginated collection of chat sessions - -Scenario: Get actor chat sessions paginated - Given I have a definition "" - And Chat Sessions exist "" - And I have a Actor id "" - And I have a start date "" - And I have a end date "" - And chat sessions within the date range exists "" - And I have a page number "" - And I have a page size "" - When I get the chat sessions paginated - Then The response is "" - And If the response has validation issues I see the "" in the response - And The response has a collection of chat sessions - And Each chat session has a Key - And Each chat session has a Date greater than start date - And Each chat session has a Date less than end date - And The response has a Page Number - And The response has a Total Pages - And The response has a Total Count - - -Examples: - | def | response | responseErrors | id | startDate | endDate | exist | chatSessionsResultExists | pageNumber | pageSize | - | success no date range | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | | | true | true | 1 | 10 | - | success with date range | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | 2024-06-01T11:21:00Z | 2034-06-03T11:21:00Z | true | true | 1 | 10 | - | success with date range | Success | | 9c5f2b35-b380-44f8-8c71-c24a43b3fe63 | 2025-07-19T11:21:00Z | 2034-06-03T11:21:00Z | true | true | 1 | 10 | - | success empty results | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | | | false | false | 1 | 10 | - | bad request page number zero | BadRequest | PageNumber | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | | | false | false | 0 | 10 | - | bad request page size zero | BadRequest | PageSize | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | | | false | false | 1 | 0 | \ No newline at end of file diff --git a/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature.cs b/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature.cs deleted file mode 100644 index 446806e..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQuery.feature.cs +++ /dev/null @@ -1,231 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by Reqnroll (https://reqnroll.net/). -// Reqnroll Version:3.0.0.0 -// Reqnroll Generator Version:3.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -#region Designer generated code -#pragma warning disable -using Reqnroll; -namespace Goodtocode.AgentFramework.Tests.Integration.Actor -{ - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] - public partial class GetActorChatSessionsPaginatedQueryFeature - { - - private global::Reqnroll.ITestRunner testRunner; - - private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; - - private static string[] featureTags = new string[] { - "getAuthorChatSessionsPaginatedQuery"}; - - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Actor", "Get Actor Chat Sessions Paginated Query", "As an actor of chat sessions\r\nWhen I get chat sessions by actor or by date range\r" + - "\nI can get a paginated collection of chat sessions", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); - -#line 1 "GetActorChatSessionsPaginatedQuery.feature" -#line hidden - - public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext - { - get - { - return this._testContext; - } - set - { - this._testContext = value; - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] - public static async global::System.Threading.Tasks.Task FeatureSetupAsync(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) - { - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] - public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() - { - await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] - public async global::System.Threading.Tasks.Task TestInitializeAsync() - { - testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); - try - { - if (((testRunner.FeatureContext != null) - && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) - { - await testRunner.OnFeatureEndAsync(); - } - } - finally - { - if (((testRunner.FeatureContext != null) - && testRunner.FeatureContext.BeforeFeatureHookFailed)) - { - throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); - } - if ((testRunner.FeatureContext == null)) - { - await testRunner.OnFeatureStartAsync(featureInfo); - } - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] - public async global::System.Threading.Tasks.Task TestTearDownAsync() - { - if ((testRunner == null)) - { - return; - } - try - { - await testRunner.OnScenarioEndAsync(); - } - finally - { - global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); - testRunner = null; - } - } - - public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) - { - testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); - } - - public async global::System.Threading.Tasks.Task ScenarioStartAsync() - { - await testRunner.OnScenarioStartAsync(); - } - - public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() - { - await testRunner.CollectScenarioErrorsAsync(); - } - - private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() - { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Actor/GetActorChatSessionsPaginatedQuery.feature.ndjson", 8); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get actor chat sessions paginated")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Get actor chat sessions paginated")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Get Actor Chat Sessions Paginated Query")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("getAuthorChatSessionsPaginatedQuery")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success no date range", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "", "", "true", "true", "1", "10", "0", null, DisplayName="Get actor chat sessions paginated(success no date range,Success,,038d8e7f-f18f-4a" + - "8e-8b3c-3b6a6889fed9,,,true,true,1,10,0)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success with date range", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "2024-06-01T11:21:00Z", "2034-06-03T11:21:00Z", "true", "true", "1", "10", "1", null, DisplayName="Get actor chat sessions paginated(success with date range,Success,,038d8e7f-f18f-" + - "4a8e-8b3c-3b6a6889fed9,2024-06-01T11:21:00Z,2034-06-03T11:21:00Z,true,true,1,10," + - "1)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success with date range", "Success", "", "9c5f2b35-b380-44f8-8c71-c24a43b3fe63", "2025-07-19T11:21:00Z", "2034-06-03T11:21:00Z", "true", "true", "1", "10", "2", null, DisplayName="Get actor chat sessions paginated(success with date range,Success,,9c5f2b35-b380-" + - "44f8-8c71-c24a43b3fe63,2025-07-19T11:21:00Z,2034-06-03T11:21:00Z,true,true,1,10," + - "2)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success empty results", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "", "", "false", "false", "1", "10", "3", null, DisplayName="Get actor chat sessions paginated(success empty results,Success,,038d8e7f-f18f-4a" + - "8e-8b3c-3b6a6889fed9,,,false,false,1,10,3)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("bad request page number zero", "BadRequest", "PageNumber", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "", "", "false", "false", "0", "10", "4", null, DisplayName="Get actor chat sessions paginated(bad request page number zero,BadRequest,PageNum" + - "ber,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,,,false,false,0,10,4)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("bad request page size zero", "BadRequest", "PageSize", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "", "", "false", "false", "1", "0", "5", null, DisplayName="Get actor chat sessions paginated(bad request page size zero,BadRequest,PageSize," + - "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,,,false,false,1,0,5)")] - public async global::System.Threading.Tasks.Task GetActorChatSessionsPaginated(string def, string response, string responseErrors, string id, string startDate, string endDate, string exist, string chatSessionsResultExists, string pageNumber, string pageSize, string @__pickleIndex, string[] exampleTags) - { - string[] tagsOfScenario = exampleTags; - global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); - argumentsOfScenario.Add("def", def); - argumentsOfScenario.Add("response", response); - argumentsOfScenario.Add("responseErrors", responseErrors); - argumentsOfScenario.Add("id", id); - argumentsOfScenario.Add("startDate", startDate); - argumentsOfScenario.Add("endDate", endDate); - argumentsOfScenario.Add("exist", exist); - argumentsOfScenario.Add("chatSessionsResultExists", chatSessionsResultExists); - argumentsOfScenario.Add("pageNumber", pageNumber); - argumentsOfScenario.Add("pageSize", pageSize); - string pickleIndex = @__pickleIndex; - global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Get actor chat sessions paginated", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); - string[] tagsOfRule = ((string[])(null)); - global::Reqnroll.RuleInfo ruleInfo = null; -#line 7 -this.ScenarioInitialize(scenarioInfo, ruleInfo); -#line hidden - if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) - { - await testRunner.SkipScenarioAsync(); - } - else - { - await this.ScenarioStartAsync(); -#line 8 - await testRunner.GivenAsync(string.Format("I have a definition \"{0}\"", def), ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); -#line hidden -#line 9 - await testRunner.AndAsync(string.Format("Chat Sessions exist \"{0}\"", exist), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 10 - await testRunner.AndAsync(string.Format("I have a Actor id \"{0}\"", id), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 11 - await testRunner.AndAsync(string.Format("I have a start date \"{0}\"", startDate), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 12 - await testRunner.AndAsync(string.Format("I have a end date \"{0}\"", endDate), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 13 - await testRunner.AndAsync(string.Format("chat sessions within the date range exists \"{0}\"", chatSessionsResultExists), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 14 - await testRunner.AndAsync(string.Format("I have a page number \"{0}\"", pageNumber), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 15 - await testRunner.AndAsync(string.Format("I have a page size \"{0}\"", pageSize), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 16 - await testRunner.WhenAsync("I get the chat sessions paginated", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); -#line hidden -#line 17 - await testRunner.ThenAsync(string.Format("The response is \"{0}\"", response), ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); -#line hidden -#line 18 - await testRunner.AndAsync(string.Format("If the response has validation issues I see the \"{0}\" in the response", responseErrors), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 19 - await testRunner.AndAsync("The response has a collection of chat sessions", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 20 - await testRunner.AndAsync("Each chat session has a Key", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 21 - await testRunner.AndAsync("Each chat session has a Date greater than start date", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 22 - await testRunner.AndAsync("Each chat session has a Date less than end date", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 23 - await testRunner.AndAsync("The response has a Page Number", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 24 - await testRunner.AndAsync("The response has a Total Pages", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 25 - await testRunner.AndAsync("The response has a Total Count", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden - } - await this.ScenarioCleanupAsync(); - } - } -} -#pragma warning restore -#endregion diff --git a/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQueryStepDefinitions.cs deleted file mode 100644 index abded29..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionsPaginatedQueryStepDefinitions.cs +++ /dev/null @@ -1,179 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; -using Goodtocode.AgentFramework.Core.Application.Common.Models; -using Goodtocode.AgentFramework.Core.Domain.Actor; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Tests.Integration.Actor -{ - [Binding] - [Scope(Tag = "getAuthorChatSessionsPaginatedQuery")] - public class GetActorChatSessionsPaginatedQueryStepDefinitions : TestBase - { - private bool _exists; - private DateTime _startDate; - private DateTime _endDate; - private bool _withinDateRangeExists; - private int _pageNumber; - private int _pageSize; - private PaginatedList? _response; - private Guid _id; - - [Given(@"I have a definition ""([^""]*)""")] - public void GivenIHaveADefinition(string def) - { - base.def = def; - } - - [Given(@"Chat Sessions exist ""([^""]*)""")] - public void GivenAuthorChatSessionsExist(string exists) - { - bool.TryParse(exists, out _exists).ShouldBeTrue(); - } - - [Given(@"I have a Actor id ""([^""]*)""")] - public void GivenIHaveAAuthorId(string authorId) - { - if (string.IsNullOrWhiteSpace(authorId)) return; - Guid.TryParse(authorId, out _id).ShouldBeTrue(); - } - - [Given(@"I have a start date ""([^""]*)""")] - public void GivenIHaveAStartDate(string startDate) - { - if (string.IsNullOrWhiteSpace(startDate)) return; - DateTime.TryParse(startDate, out _startDate).ShouldBeTrue(); - } - - [Given(@"I have a end date ""([^""]*)""")] - public void GivenIHaveAEndDate(string endDate) - { - if (string.IsNullOrWhiteSpace(endDate)) return; - DateTime.TryParse(endDate, out _endDate).ShouldBeTrue(); - } - - [Given(@"chat sessions within the date range exists ""([^""]*)""")] - public void GivenAuthorChatSessionsWithinTheDateRangeExists(string withinDateRangeExists) - { - bool.TryParse(withinDateRangeExists, out _withinDateRangeExists).ShouldBeTrue(); - } - - [Given(@"I have a page number ""([^""]*)""")] - public void GivenIHaveAPageNumber(string pageNumber) - { - int.TryParse(pageNumber, out _pageNumber).ShouldBeTrue(); - } - - [Given(@"I have a page size ""([^""]*)""")] - public void GivenIHaveAPageSize(string pageSize) - { - int.TryParse(pageSize, out _pageSize).ShouldBeTrue(); ; - } - - [When(@"I get the chat sessions paginated")] - public async Task WhenIGetTheAuthorChatSessionsPaginated() - { - if (_exists) - { - var actor = ActorEntity.Create(_id, userInfo.OwnerId, Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); - context.Actors.Add(actor); - await context.SaveChangesAsync(CancellationToken.None); - var chatSession = ChatSessionEntity.Create(_id, _id, "Test Session", ChatMessageRole.assistant, "First Message", "First Response"); - for(var count = 0; count <= _pageSize; count++) - { - context.ChatMessages.Add(ChatMessageEntity.Create(Guid.NewGuid(), _id, ChatMessageRole.user, $"Message {count + 1}")); - chatSession.Messages.Add(ChatMessageEntity.Create(Guid.NewGuid(), _id, ChatMessageRole.user, $"Message {count + 1}")); - context.ChatMessages.Add(ChatMessageEntity.Create(Guid.NewGuid(), _id, ChatMessageRole.assistant, $"Response {count + 1}")); - chatSession.Messages.Add(ChatMessageEntity.Create(Guid.NewGuid(), _id, ChatMessageRole.assistant, $"Response {count + 1}")); - } - context.ChatSessions.Add(chatSession); - await context.SaveChangesAsync(CancellationToken.None); - } - - var request = new GetMyChatSessionsPaginatedQuery() - { - PageNumber = _pageNumber, - PageSize = _pageSize, - StartDate = _startDate == default ? null : _startDate, - EndDate = _endDate == default ? null : _endDate, - UserInfo = userInfo - }; - - var validator = new GetMyChatSessionsPaginatedQueryValidator(); - validationResponse = validator.Validate(request); - if (validationResponse.IsValid) - try - { - var handler = new GetAuthorChatSessionsByExternalIdPaginatedQueryHandler(context); - _response = await handler.Handle(request, CancellationToken.None); - responseType = CommandResponseType.Successful; - } - catch (Exception e) - { - responseType = HandleAssignResponseType(e); - } - else - responseType = CommandResponseType.BadRequest; - } - - [Then(@"The response is ""([^""]*)""")] - public void ThenTheResponseIs(string response) - { - HandleHasResponseType(response); - } - - [Then(@"If the response has validation issues I see the ""([^""]*)"" in the response")] - public void ThenIfTheResponseHasValidationIssuesISeeTheInTheResponse(string expectedErrors) - { - HandleExpectedValidationErrorsAssertions(expectedErrors); - } - - [Then(@"The response has a collection of chat sessions")] - public void ThenTheResponseHasACollectionOfAuthorChatSessions() - { - if (responseType != CommandResponseType.Successful) return; - _response?.TotalCount.ShouldBe(_withinDateRangeExists == false ? 0 : _response.TotalCount); - } - - [Then(@"Each chat session has a Key")] - public void ThenEachChatSessionHasAKey() - { - if (responseType != CommandResponseType.Successful) return; - _response?.Items.FirstOrDefault(x => x.Id == default).ShouldBeNull(); - } - - [Then(@"Each chat session has a Date greater than start date")] - public void ThenEachChatSessionHasADateGreaterThanStartDate() - { - if (responseType == CommandResponseType.Successful && _withinDateRangeExists) - _response?.Items.FirstOrDefault(x => _startDate == default || x.Timestamp > _startDate).ShouldNotBeNull(); - } - - [Then(@"Each chat session has a Date less than end date")] - public void ThenEachChatSessionHasADateLessThanEndDate() - { - if (responseType == CommandResponseType.Successful && _withinDateRangeExists) - _response?.Items.FirstOrDefault(x => _endDate == default || x.Timestamp < _endDate).ShouldNotBeNull(); - } - - [Then(@"The response has a Page Number")] - public void ThenTheResponseHasAPageNumber() - { - if (responseType != CommandResponseType.Successful) return; - _response?.PageNumber.Should(); - } - - [Then(@"The response has a Total Pages")] - public void ThenTheResponseHasATotalPages() - { - if (responseType != CommandResponseType.Successful) return; - _response?.TotalPages.Should(); - } - - [Then(@"The response has a Total Count")] - public void ThenTheResponseHasATotalCount() - { - if (responseType != CommandResponseType.Successful) return; - _response?.TotalCount.Should(); - } - } -} diff --git a/src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature b/src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature deleted file mode 100644 index ff45c63..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature +++ /dev/null @@ -1,27 +0,0 @@ -@getAuthorChatSessionsQuery -Feature: Get Actor Chat Sessions Query -As an actor -When I query chat sessions optionally by date range -I get all sessions that fit the date range - -Scenario: Get actor chat sessions - Given I have a definition "" - And Chat Sessions exist "" - And chat sessions within the date range exists "" - And I have a Actor id "" - And I have a start date "" - And I have a end date "" - When I get the chat sessions - Then The response is "" - And If the response has validation issues I see the "" in the response - And The response has a collection of chat sessions - And Each chat session has a Key - And Each chat session has a Date greater than start date - And Each chat session has a Date less than end date - -Examples: - | def | response | responseErrors | id | startDate | endDate | exist | chatSessionsResultExists | - | success no date range | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | | | true | true | - | success with date range | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | 2024-06-01T11:21:00Z | 2034-06-03T11:21:00Z | true | true | - | success filtered results | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | 2024-06-01T11:21:00Z | 2034-06-03T11:21:00Z | true | false | - | success empty results | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | | | false | false | \ No newline at end of file diff --git a/src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature.cs b/src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature.cs deleted file mode 100644 index 38b28be..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionsQuery.feature.cs +++ /dev/null @@ -1,208 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by Reqnroll (https://reqnroll.net/). -// Reqnroll Version:3.0.0.0 -// Reqnroll Generator Version:3.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -#region Designer generated code -#pragma warning disable -using Reqnroll; -namespace Goodtocode.AgentFramework.Tests.Integration.Actor -{ - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] - public partial class GetActorChatSessionsQueryFeature - { - - private global::Reqnroll.ITestRunner testRunner; - - private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; - - private static string[] featureTags = new string[] { - "getAuthorChatSessionsQuery"}; - - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "Actor", "Get Actor Chat Sessions Query", "As an actor\r\nWhen I query chat sessions optionally by date range\r\nI get all sessi" + - "ons that fit the date range", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); - -#line 1 "GetActorChatSessionsQuery.feature" -#line hidden - - public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext - { - get - { - return this._testContext; - } - set - { - this._testContext = value; - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] - public static async global::System.Threading.Tasks.Task FeatureSetupAsync(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) - { - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] - public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() - { - await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] - public async global::System.Threading.Tasks.Task TestInitializeAsync() - { - testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); - try - { - if (((testRunner.FeatureContext != null) - && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) - { - await testRunner.OnFeatureEndAsync(); - } - } - finally - { - if (((testRunner.FeatureContext != null) - && testRunner.FeatureContext.BeforeFeatureHookFailed)) - { - throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); - } - if ((testRunner.FeatureContext == null)) - { - await testRunner.OnFeatureStartAsync(featureInfo); - } - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] - public async global::System.Threading.Tasks.Task TestTearDownAsync() - { - if ((testRunner == null)) - { - return; - } - try - { - await testRunner.OnScenarioEndAsync(); - } - finally - { - global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); - testRunner = null; - } - } - - public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) - { - testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); - } - - public async global::System.Threading.Tasks.Task ScenarioStartAsync() - { - await testRunner.OnScenarioStartAsync(); - } - - public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() - { - await testRunner.CollectScenarioErrorsAsync(); - } - - private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() - { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Actor/GetActorChatSessionsQuery.feature.ndjson", 6); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get actor chat sessions")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Get actor chat sessions")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Get Actor Chat Sessions Query")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("getAuthorChatSessionsQuery")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success no date range", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "", "", "true", "true", "0", null, DisplayName="Get actor chat sessions(success no date range,Success,,038d8e7f-f18f-4a8e-8b3c-3b" + - "6a6889fed9,,,true,true,0)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success with date range", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "2024-06-01T11:21:00Z", "2034-06-03T11:21:00Z", "true", "true", "1", null, DisplayName="Get actor chat sessions(success with date range,Success,,038d8e7f-f18f-4a8e-8b3c-" + - "3b6a6889fed9,2024-06-01T11:21:00Z,2034-06-03T11:21:00Z,true,true,1)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success filtered results", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "2024-06-01T11:21:00Z", "2034-06-03T11:21:00Z", "true", "false", "2", null, DisplayName="Get actor chat sessions(success filtered results,Success,,038d8e7f-f18f-4a8e-8b3c" + - "-3b6a6889fed9,2024-06-01T11:21:00Z,2034-06-03T11:21:00Z,true,false,2)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success empty results", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "", "", "false", "false", "3", null, DisplayName="Get actor chat sessions(success empty results,Success,,038d8e7f-f18f-4a8e-8b3c-3b" + - "6a6889fed9,,,false,false,3)")] - public async global::System.Threading.Tasks.Task GetActorChatSessions(string def, string response, string responseErrors, string id, string startDate, string endDate, string exist, string chatSessionsResultExists, string @__pickleIndex, string[] exampleTags) - { - string[] tagsOfScenario = exampleTags; - global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); - argumentsOfScenario.Add("def", def); - argumentsOfScenario.Add("response", response); - argumentsOfScenario.Add("responseErrors", responseErrors); - argumentsOfScenario.Add("id", id); - argumentsOfScenario.Add("startDate", startDate); - argumentsOfScenario.Add("endDate", endDate); - argumentsOfScenario.Add("exist", exist); - argumentsOfScenario.Add("chatSessionsResultExists", chatSessionsResultExists); - string pickleIndex = @__pickleIndex; - global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Get actor chat sessions", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); - string[] tagsOfRule = ((string[])(null)); - global::Reqnroll.RuleInfo ruleInfo = null; -#line 7 -this.ScenarioInitialize(scenarioInfo, ruleInfo); -#line hidden - if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) - { - await testRunner.SkipScenarioAsync(); - } - else - { - await this.ScenarioStartAsync(); -#line 8 - await testRunner.GivenAsync(string.Format("I have a definition \"{0}\"", def), ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); -#line hidden -#line 9 - await testRunner.AndAsync(string.Format("Chat Sessions exist \"{0}\"", exist), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 10 - await testRunner.AndAsync(string.Format("chat sessions within the date range exists \"{0}\"", chatSessionsResultExists), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 11 - await testRunner.AndAsync(string.Format("I have a Actor id \"{0}\"", id), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 12 - await testRunner.AndAsync(string.Format("I have a start date \"{0}\"", startDate), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 13 - await testRunner.AndAsync(string.Format("I have a end date \"{0}\"", endDate), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 14 - await testRunner.WhenAsync("I get the chat sessions", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); -#line hidden -#line 15 - await testRunner.ThenAsync(string.Format("The response is \"{0}\"", response), ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); -#line hidden -#line 16 - await testRunner.AndAsync(string.Format("If the response has validation issues I see the \"{0}\" in the response", responseErrors), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 17 - await testRunner.AndAsync("The response has a collection of chat sessions", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 18 - await testRunner.AndAsync("Each chat session has a Key", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 19 - await testRunner.AndAsync("Each chat session has a Date greater than start date", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 20 - await testRunner.AndAsync("Each chat session has a Date less than end date", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden - } - await this.ScenarioCleanupAsync(); - } - } -} -#pragma warning restore -#endregion diff --git a/src/Tests.Integration/Actor/GetActorChatSessionsQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorChatSessionsQueryStepDefinitions.cs deleted file mode 100644 index 5427e5c..0000000 --- a/src/Tests.Integration/Actor/GetActorChatSessionsQueryStepDefinitions.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; -using Goodtocode.AgentFramework.Core.Domain.Actor; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Tests.Integration.Actor; - -[Binding] -[Scope(Tag = "getAuthorChatSessionsQuery")] -public class GetActorChatSessionsQueryStepDefinitions : TestBase -{ - private bool _exists; - private bool _withinDateRangeExists; - private DateTime _endDate; - private DateTime _startDate; - private ICollection? _response; - private Guid _id; - - [Given(@"I have a definition ""([^""]*)""")] - public void GivenIHaveADefinition(string def) - { - base.def = def; - } - - [Given(@"Chat Sessions exist ""([^""]*)""")] - public void GivenChatSessionsExist(string exists) - { - bool.TryParse(exists, out _exists).ShouldBeTrue(); - } - - [Given(@"chat sessions within the date range exists ""([^""]*)""")] - public void GivenChatSessionsWithinTheDateRangeExists(string withinDateRangeExists) - { - bool.TryParse(withinDateRangeExists, out _withinDateRangeExists).ShouldBeTrue(); - } - - [Given(@"I have a Actor id ""([^""]*)""")] - public void GivenIHaveAAuthorId(string authorId) - { - if (string.IsNullOrWhiteSpace(authorId)) return; - Guid.TryParse(authorId, out _id).ShouldBeTrue(); - } - - [Given(@"I have a start date ""([^""]*)""")] - public void GivenIHaveAStartDate(string startDate) - { - if (string.IsNullOrWhiteSpace(startDate)) return; - DateTime.TryParse(startDate, out _startDate).ShouldBeTrue(); - _startDate = DateTime.UtcNow.AddMinutes(_withinDateRangeExists ? -1 : 1); //Handle for desired not-found scenarios - } - - [Given(@"I have a end date ""([^""]*)""")] - public void GivenIHaveAEndDate(string endDate) - { - if (string.IsNullOrWhiteSpace(endDate)) return; - DateTime.TryParse(endDate, out _endDate).ShouldBeTrue(); - } - - [When(@"I get the chat sessions")] - public async Task WhenIGetTheChatSessions() - { - if (_exists) - { - var actor = ActorEntity.Create(_id, userInfo.OwnerId, Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); - context.Actors.Add(actor); - await context.SaveChangesAsync(CancellationToken.None); - var chatSession = ChatSessionEntity.Create(_id, actor.Id, "Test Session", ChatMessageRole.assistant, "First Message", "First Response"); - context.ChatSessions.Add(chatSession); - await context.SaveChangesAsync(CancellationToken.None); - } - - var request = new GetMyChatSessionsQuery() - { - StartDate = _startDate == default ? null : _startDate, - EndDate = _endDate == default ? null : _endDate, - UserInfo = userInfo - }; - - var validator = new GetMyChatSessionsQueryValidator(); - validationResponse = validator.Validate(request); - if (validationResponse.IsValid) - try - { - var handler = new GetAuthorChatSessionsByOwnerIdQueryHandler(context); - _response = await handler.Handle(request, CancellationToken.None); - responseType = CommandResponseType.Successful; - } - catch (Exception e) - { - responseType = HandleAssignResponseType(e); - } - else - responseType = CommandResponseType.BadRequest; - } - - [Then(@"The response is ""([^""]*)""")] - public void ThenTheResponseIs(string response) - { - HandleHasResponseType(response); - } - - [Then(@"If the response has validation issues I see the ""([^""]*)"" in the response")] - public void ThenIfTheResponseHasValidationIssuesISeeTheInTheResponse(string expectedErrors) - { - HandleExpectedValidationErrorsAssertions(expectedErrors); - } - - [Then(@"The response has a collection of chat sessions")] - public void ThenTheResponseHasACollectionOfChatSessions() - { - _response?.Count.ShouldBe(_withinDateRangeExists == false ? 0 : _response.Count); - } - - [Then(@"Each chat session has a Key")] - public void ThenEachChatSessionHasAKey() - { - _response?.FirstOrDefault(x => x.Id == default).ShouldBeNull(); - } - - [Then(@"Each chat session has a Date greater than start date")] - public void ThenEachChatSessionHasADateGreaterThanStartDate() - { - if (_withinDateRangeExists) - _response?.FirstOrDefault(x => _startDate == default || x.Timestamp > _startDate).ShouldNotBeNull(); - } - - [Then(@"Each chat session has a Date less than end date")] - public void ThenEachChatSessionHasADateLessThanEndDate() - { - if (_withinDateRangeExists) - _response?.FirstOrDefault(x => _endDate == default || x.Timestamp < _endDate).ShouldNotBeNull(); - } -} diff --git a/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs index 7b20772..50eb78a 100644 --- a/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs +++ b/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs @@ -18,10 +18,10 @@ public void GivenIHaveADefinition(string def) } [Given(@"I have a Actor id ""([^""]*)""")] - public void GivenIHaveAAuthorId(string authorId) + public void GivenIHaveAAuthorId(string actorId) { - if (string.IsNullOrWhiteSpace(authorId)) return; - Guid.TryParse(authorId, out _id).ShouldBeTrue(); + if (string.IsNullOrWhiteSpace(actorId)) return; + Guid.TryParse(actorId, out _id).ShouldBeTrue(); } [Given(@"the Actor exists ""([^""]*)""")] @@ -35,7 +35,7 @@ public async Task WhenIGetAAuthor() { if (_exists) { - var actor = ActorEntity.Create(_id, Guid.NewGuid(), Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); + var actor = ActorEntity.Create(_id, "John", "Doe", "jdoe@goodtocode.com"); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); } @@ -50,7 +50,7 @@ public async Task WhenIGetAAuthor() if (validationResponse.IsValid) try { - var handler = new GetAuthorQueryHandler(context); + var handler = new GetActorQueryHandler(context); _response = await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/Actor/SaveActorCommandStepDefinitions.cs b/src/Tests.Integration/Actor/SaveActorCommandStepDefinitions.cs index 4075b27..d2d9b45 100644 --- a/src/Tests.Integration/Actor/SaveActorCommandStepDefinitions.cs +++ b/src/Tests.Integration/Actor/SaveActorCommandStepDefinitions.cs @@ -55,7 +55,7 @@ public async Task WhenICreateAAuthor() { if (_exists) { - var actor = ActorEntity.Create(_id, _ownerId, _tenantId, "John", "Doe", "jdoe@goodtocode.com"); + var actor = ActorEntity.Create(_id, "John", "Doe", "jdoe@goodtocode.com"); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); } @@ -66,7 +66,7 @@ public async Task WhenICreateAAuthor() FirstName = _name.Split(" ").FirstOrDefault(), LastName = _name.Split(" ").LastOrDefault(), Email = _email, - UserInfo = userInfo + UserContext = userContext }; var validator = new SaveMyActorCommandValidator(); @@ -76,7 +76,7 @@ public async Task WhenICreateAAuthor() { try { - var handler = new SaveAuthorCommandHandler(context); + var handler = new SaveActorCommandHandler(context); await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/Actor/UpdateActorCommandStepDefinitions.cs b/src/Tests.Integration/Actor/UpdateActorCommandStepDefinitions.cs index 15bb658..54b7c8b 100644 --- a/src/Tests.Integration/Actor/UpdateActorCommandStepDefinitions.cs +++ b/src/Tests.Integration/Actor/UpdateActorCommandStepDefinitions.cs @@ -33,14 +33,14 @@ public async Task WhenIUpdateTheAuthor() { if (_exists) { - var actor = ActorEntity.Create(_id, _id, Guid.NewGuid(), "John", "Doe", "jdoe@goodtocode.com"); + var actor = ActorEntity.Create(_id, "John", "Doe", "jdoe@goodtocode.com"); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); } var request = new UpdateActorCommand() { - OwnerId = _id, + OwnerId = userContext.OwnerId, Name = "Joe Doe" }; @@ -50,7 +50,7 @@ public async Task WhenIUpdateTheAuthor() if (validationResponse.IsValid) try { - var handler = new UpdateAuthorCommandHandler(context); + var handler = new UpdateActorCommandHandler(context); await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/ChatCompletion/CreateChatMessageCommandStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/CreateChatMessageCommandStepDefinitions.cs index 8ca9999..8e329f5 100644 --- a/src/Tests.Integration/ChatCompletion/CreateChatMessageCommandStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/CreateChatMessageCommandStepDefinitions.cs @@ -12,7 +12,6 @@ public class CreateChatMessageCommandStepDefinitions : TestBase private Guid _id; private readonly Guid _chatSessionId = Guid.NewGuid(); private bool _exists; - private readonly TestUserInfo _userInfo = new(); [Given(@"I have a def ""([^""]*)""")] public void GivenIHaveADef(string def) @@ -44,7 +43,7 @@ public async Task WhenICreateAChatMessageWithTheMessage() // Setup the database if want to test existing records if (_exists) { - var actor = ActorEntity.Create(_userInfo); + var actor = ActorEntity.Create(userContext); context.Actors.Add(actor); await context.SaveChangesAsync(CancellationToken.None); var chatSession = ChatSessionEntity.Create(_chatSessionId, actor.Id, "Test Session", ChatMessageRole.assistant, "First Message", "First Response"); @@ -53,15 +52,15 @@ public async Task WhenICreateAChatMessageWithTheMessage() } // Test command - var request = new CreateChatMessageCommand() + var request = new CreateMyChatMessageCommand() { Id = _id, ChatSessionId = _chatSessionId, Message = _message, - UserInfo = _userInfo + UserContext = userContext }; - var validator = new CreateChatMessageCommandValidator(); + var validator = new CreateMyChatMessageCommandValidator(); validationResponse = await validator.ValidateAsync(request); if (validationResponse.IsValid) diff --git a/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs index ff1c144..d8bf02b 100644 --- a/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs @@ -10,7 +10,7 @@ public class CreateChatSessionCommandStepDefinitions : TestBase { private string _message = string.Empty; private Guid _id; - private readonly Guid _authorId = Guid.NewGuid(); + private readonly Guid _actorId = Guid.NewGuid(); private bool _exists; [Given(@"I have a def ""([^""]*)""")] @@ -41,25 +41,25 @@ public void GivenTheChatSessionExists(string exists) public async Task WhenICreateAChatSessionWithTheMessage() { // Setup the database if want to test existing records - var actor = ActorEntity.Create(_authorId, _authorId, Guid.NewGuid(), "Test", "Actor", "actor@goodtocode.com"); + var actor = ActorEntity.Create(_actorId, "Test", "Actor", "actor@goodtocode.com"); context.Actors.Add(actor); if (_exists) { - var chatSession = ChatSessionEntity.Create(_id, _authorId, "Test Session", ChatMessageRole.assistant, _message, "First Response"); + var chatSession = ChatSessionEntity.Create(_id, _actorId, "Test Session", ChatMessageRole.assistant, _message, "First Response"); context.ChatSessions.Add(chatSession); } await context.SaveChangesAsync(CancellationToken.None); // Test command - var request = new CreateChatSessionCommand() + var request = new CreateMyChatSessionCommand() { Id = _id, - Title = def, - ActorId = _authorId, - Message = _message + Title = def, + Message = _message, + UserContext = userContext }; - var validator = new CreateChatSessionCommandValidator(); + var validator = new CreateMyChatSessionCommandValidator(); validationResponse = await validator.ValidateAsync(request); if (validationResponse.IsValid) diff --git a/src/Tests.Integration/ChatCompletion/DeleteChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/DeleteChatSessionCommandStepDefinitions.cs index 25b81c7..4f8415e 100644 --- a/src/Tests.Integration/ChatCompletion/DeleteChatSessionCommandStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/DeleteChatSessionCommandStepDefinitions.cs @@ -31,9 +31,10 @@ public void GivenTheChatSessionExists(string exists) [When(@"I delete the chat session")] public async Task WhenIDeleteTheChatSession() { - var request = new DeleteChatSessionCommand() + var request = new DeleteMyChatSessionCommand() { - Id = _id + Id = _id, + UserContext = userContext }; if (_exists) @@ -43,7 +44,7 @@ public async Task WhenIDeleteTheChatSession() await context.SaveChangesAsync(CancellationToken.None); } - var validator = new DeleteChatSessionCommandValidator(); + var validator = new DeleteMyChatSessionCommandValidator(); validationResponse = await validator.ValidateAsync(request); if (validationResponse.IsValid) diff --git a/src/Tests.Integration/ChatCompletion/GetChatMessageQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetChatMessageQueryStepDefinitions.cs index 5d50738..27c3ff4 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatMessageQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetChatMessageQueryStepDefinitions.cs @@ -42,12 +42,13 @@ public async Task WhenIGetAChatMessage() await context.SaveChangesAsync(CancellationToken.None); } - var request = new GetChatMessageQuery() + var request = new GetMyChatMessageQuery() { - Id = _id + Id = _id, + UserContext = userContext }; - var validator = new GetChatMessageQueryValidator(); + var validator = new GetMyChatMessageQueryValidator(); validationResponse = validator.Validate(request); if (validationResponse.IsValid) try diff --git a/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs index 82767af..dfbe554 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs @@ -71,15 +71,16 @@ public async Task WhenIGetTheChatMessagesPaginated() await context.SaveChangesAsync(CancellationToken.None); } - var request = new GetChatMessagesPaginatedQuery() + var request = new GetMyChatMessagesPaginatedQuery() { PageNumber = _pageNumber, PageSize = _pageSize, StartDate = _startDate == default ? null : _startDate, - EndDate = _endDate == default ? null : _endDate + EndDate = _endDate == default ? null : _endDate, + UserContext = userContext }; - var validator = new GetChatMessagesPaginatedQueryValidator(); + var validator = new GetMyChatMessagesPaginatedQueryValidator(); validationResponse = validator.Validate(request); if (validationResponse.IsValid) try diff --git a/src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature b/src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature deleted file mode 100644 index f173f52..0000000 --- a/src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature +++ /dev/null @@ -1,26 +0,0 @@ -@getChatMessagesQuery -Feature: Get Chat Messages Query -As a message owner -When I query Chat Messages optionally by date range -I get all messages that fit the date range - -Scenario: Get Chat Messages - Given I have a definition "" - And Chat Messages exist "" - And Chat Messages within the date range exists "" - And I have a start date "" - And I have a end date "" - When I get the Chat Messages - Then The response is "" - And If the response has validation issues I see the "" in the response - And The response has a collection of Chat Messages - And Each Chat Message has a Key - And Each Chat Message has a Date greater than start date - And Each Chat Message has a Date less than end date - -Examples: - | def | response | responseErrors | startDate | endDate | exist | ChatMessagesResultExists | - | success no date range | Success | | | | true | true | - | success with date range | Success | | 2024-06-01T11:21:00Z | 2034-06-03T11:21:00Z | true | true | - | success filtered results | Success | | 2024-06-01T11:21:00Z | 2034-06-03T11:21:00Z | true | false | - | success empty results | Success | | | | false | false | \ No newline at end of file diff --git a/src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature.cs b/src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature.cs deleted file mode 100644 index adb7d71..0000000 --- a/src/Tests.Integration/ChatCompletion/GetChatMessagesQuery.feature.cs +++ /dev/null @@ -1,202 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by Reqnroll (https://reqnroll.net/). -// Reqnroll Version:3.0.0.0 -// Reqnroll Generator Version:3.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -#region Designer generated code -#pragma warning disable -using Reqnroll; -namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion -{ - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] - public partial class GetChatMessagesQueryFeature - { - - private global::Reqnroll.ITestRunner testRunner; - - private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; - - private static string[] featureTags = new string[] { - "getChatMessagesQuery"}; - - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "ChatCompletion", "Get Chat Messages Query", "As a message owner\r\nWhen I query Chat Messages optionally by date range\r\nI get al" + - "l messages that fit the date range", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); - -#line 1 "GetChatMessagesQuery.feature" -#line hidden - - public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext - { - get - { - return this._testContext; - } - set - { - this._testContext = value; - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] - public static async global::System.Threading.Tasks.Task FeatureSetupAsync(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) - { - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] - public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() - { - await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] - public async global::System.Threading.Tasks.Task TestInitializeAsync() - { - testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); - try - { - if (((testRunner.FeatureContext != null) - && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) - { - await testRunner.OnFeatureEndAsync(); - } - } - finally - { - if (((testRunner.FeatureContext != null) - && testRunner.FeatureContext.BeforeFeatureHookFailed)) - { - throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); - } - if ((testRunner.FeatureContext == null)) - { - await testRunner.OnFeatureStartAsync(featureInfo); - } - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] - public async global::System.Threading.Tasks.Task TestTearDownAsync() - { - if ((testRunner == null)) - { - return; - } - try - { - await testRunner.OnScenarioEndAsync(); - } - finally - { - global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); - testRunner = null; - } - } - - public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) - { - testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); - } - - public async global::System.Threading.Tasks.Task ScenarioStartAsync() - { - await testRunner.OnScenarioStartAsync(); - } - - public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() - { - await testRunner.CollectScenarioErrorsAsync(); - } - - private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() - { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetChatMessagesQuery.feature.ndjson", 6); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get Chat Messages")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Get Chat Messages")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Get Chat Messages Query")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("getChatMessagesQuery")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success no date range", "Success", "", "", "", "true", "true", "0", null, DisplayName="Get Chat Messages(success no date range,Success,,,,true,true,0)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success with date range", "Success", "", "2024-06-01T11:21:00Z", "2034-06-03T11:21:00Z", "true", "true", "1", null, DisplayName="Get Chat Messages(success with date range,Success,,2024-06-01T11:21:00Z,2034-06-0" + - "3T11:21:00Z,true,true,1)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success filtered results", "Success", "", "2024-06-01T11:21:00Z", "2034-06-03T11:21:00Z", "true", "false", "2", null, DisplayName="Get Chat Messages(success filtered results,Success,,2024-06-01T11:21:00Z,2034-06-" + - "03T11:21:00Z,true,false,2)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success empty results", "Success", "", "", "", "false", "false", "3", null, DisplayName="Get Chat Messages(success empty results,Success,,,,false,false,3)")] - public async global::System.Threading.Tasks.Task GetChatMessages(string def, string response, string responseErrors, string startDate, string endDate, string exist, string chatMessagesResultExists, string @__pickleIndex, string[] exampleTags) - { - string[] tagsOfScenario = exampleTags; - global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); - argumentsOfScenario.Add("def", def); - argumentsOfScenario.Add("response", response); - argumentsOfScenario.Add("responseErrors", responseErrors); - argumentsOfScenario.Add("startDate", startDate); - argumentsOfScenario.Add("endDate", endDate); - argumentsOfScenario.Add("exist", exist); - argumentsOfScenario.Add("ChatMessagesResultExists", chatMessagesResultExists); - string pickleIndex = @__pickleIndex; - global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Get Chat Messages", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); - string[] tagsOfRule = ((string[])(null)); - global::Reqnroll.RuleInfo ruleInfo = null; -#line 7 -this.ScenarioInitialize(scenarioInfo, ruleInfo); -#line hidden - if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) - { - await testRunner.SkipScenarioAsync(); - } - else - { - await this.ScenarioStartAsync(); -#line 8 - await testRunner.GivenAsync(string.Format("I have a definition \"{0}\"", def), ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); -#line hidden -#line 9 - await testRunner.AndAsync(string.Format("Chat Messages exist \"{0}\"", exist), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 10 - await testRunner.AndAsync(string.Format("Chat Messages within the date range exists \"{0}\"", chatMessagesResultExists), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 11 - await testRunner.AndAsync(string.Format("I have a start date \"{0}\"", startDate), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 12 - await testRunner.AndAsync(string.Format("I have a end date \"{0}\"", endDate), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 13 - await testRunner.WhenAsync("I get the Chat Messages", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); -#line hidden -#line 14 - await testRunner.ThenAsync(string.Format("The response is \"{0}\"", response), ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); -#line hidden -#line 15 - await testRunner.AndAsync(string.Format("If the response has validation issues I see the \"{0}\" in the response", responseErrors), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 16 - await testRunner.AndAsync("The response has a collection of Chat Messages", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 17 - await testRunner.AndAsync("Each Chat Message has a Key", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 18 - await testRunner.AndAsync("Each Chat Message has a Date greater than start date", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 19 - await testRunner.AndAsync("Each Chat Message has a Date less than end date", ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden - } - await this.ScenarioCleanupAsync(); - } - } -} -#pragma warning restore -#endregion diff --git a/src/Tests.Integration/ChatCompletion/GetChatMessagesQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetChatMessagesQueryStepDefinitions.cs deleted file mode 100644 index e8066cf..0000000 --- a/src/Tests.Integration/ChatCompletion/GetChatMessagesQueryStepDefinitions.cs +++ /dev/null @@ -1,120 +0,0 @@ -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; - -namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion; - -[Binding] -[Scope(Tag = "getChatMessagesQuery")] -public class GetChatMessagesQueryStepDefinitions : TestBase -{ - private bool _exists; - private bool _withinDateRangeExists; - private DateTime _endDate; - private DateTime _startDate; - private readonly Guid _chatSessionId = Guid.NewGuid(); - private ICollection? _response; - - [Given(@"I have a definition ""([^""]*)""")] - public void GivenIHaveADefinition(string def) - { - base.def = def; - } - - [Given(@"Chat Messages exist ""([^""]*)""")] - public void GivenChatMessagesExist(string exists) - { - bool.TryParse(exists, out _exists).ShouldBeTrue(); - } - - [Given(@"Chat Messages within the date range exists ""([^""]*)""")] - public void GivenChatMessagesWithinTheDateRangeExists(string withinDateRangeExists) - { - bool.TryParse(withinDateRangeExists, out _withinDateRangeExists).ShouldBeTrue(); - } - - [Given(@"I have a start date ""([^""]*)""")] - public void GivenIHaveAStartDate(string startDate) - { - if (string.IsNullOrWhiteSpace(startDate)) return; - DateTime.TryParse(startDate, out _startDate).ShouldBeTrue(); - _startDate = DateTime.UtcNow.AddMinutes(_withinDateRangeExists ? -1 : 1); //Handle for desired not-found scenarios - } - - [Given(@"I have a end date ""([^""]*)""")] - public void GivenIHaveAEndDate(string endDate) - { - if (string.IsNullOrWhiteSpace(endDate)) return; - DateTime.TryParse(endDate, out _endDate).ShouldBeTrue(); - } - - [When(@"I get the Chat Messages")] - public async Task WhenIGetTheChatMessages() - { - if (_exists) - { - var chatSession = ChatSessionEntity.Create(_chatSessionId, Guid.NewGuid(), "Test Session", ChatMessageRole.assistant, "First Message", "First Response"); - context.ChatSessions.Add(chatSession); - await context.SaveChangesAsync(CancellationToken.None); - } - - var request = new GetChatMessagesQuery() - { - StartDate = _startDate == default ? null : _startDate, - EndDate = _endDate == default ? null : _endDate - }; - - var validator = new GetChatMessagesQueryValidator(); - validationResponse = validator.Validate(request); - if (validationResponse.IsValid) - try - { - var handler = new GetChatMessagesQueryHandler(context); - _response = await handler.Handle(request, CancellationToken.None); - responseType = CommandResponseType.Successful; - } - catch (Exception e) - { - responseType = HandleAssignResponseType(e); - } - else - responseType = CommandResponseType.BadRequest; - } - - [Then(@"The response is ""([^""]*)""")] - public void ThenTheResponseIs(string response) - { - HandleHasResponseType(response); - } - - [Then(@"If the response has validation issues I see the ""([^""]*)"" in the response")] - public void ThenIfTheResponseHasValidationIssuesISeeTheInTheResponse(string expectedErrors) - { - HandleExpectedValidationErrorsAssertions(expectedErrors); - } - - [Then(@"The response has a collection of Chat Messages")] - public void ThenTheResponseHasACollectionOfChatMessages() - { - _response?.Count.ShouldBe(_withinDateRangeExists == false ? 0 : _response.Count); - } - - [Then(@"Each Chat Message has a Key")] - public void ThenEachChatMessageHasAKey() - { - _response?.FirstOrDefault(x => x.Id == default).ShouldBeNull(); - } - - [Then(@"Each Chat Message has a Date greater than start date")] - public void ThenEachChatMessageHasADateGreaterThanStartDate() - { - if (_withinDateRangeExists) - _response?.FirstOrDefault(x => _startDate == default || x.Timestamp > _startDate).ShouldNotBeNull(); - } - - [Then(@"Each Chat Message has a Date less than end date")] - public void ThenEachChatMessageHasADateLessThanEndDate() - { - if (_withinDateRangeExists) - _response?.FirstOrDefault(x => _endDate == default || x.Timestamp < _endDate).ShouldNotBeNull(); - } -} diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionQuery.feature b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQuery.feature similarity index 100% rename from src/Tests.Integration/ChatCompletion/GetChatSessionQuery.feature rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionQuery.feature diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionQuery.feature.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQuery.feature.cs similarity index 98% rename from src/Tests.Integration/ChatCompletion/GetChatSessionQuery.feature.cs rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionQuery.feature.cs index 448e7c6..5669cb1 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatSessionQuery.feature.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQuery.feature.cs @@ -31,7 +31,7 @@ public partial class GetChatSessionQueryFeature private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "ChatCompletion", "Get Chat Session Query", "As a chat user\r\nWhen I select an existing chat session\r\nI can see the chat histor" + "y messages", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); -#line 1 "GetChatSessionQuery.feature" +#line 1 "GetMyChatSessionQuery.feature" #line hidden public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext @@ -119,7 +119,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetChatSessionQuery.feature.ndjson", 5); + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetMyChatSessionQuery.feature.ndjson", 5); } [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get chat session")] diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs similarity index 90% rename from src/Tests.Integration/ChatCompletion/GetChatSessionQueryStepDefinitions.cs rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs index 299d5af..c719d20 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatSessionQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs @@ -6,7 +6,7 @@ namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion; [Binding] [Scope(Tag = "getChatSessionQuery")] -public class GetChatSessionQueryStepDefinitions : TestBase +public class GetMyChatSessionQueryStepDefinitions : TestBase { private Guid _id; private bool _exists; @@ -48,17 +48,18 @@ public async Task WhenIGetAChatSession() await context.SaveChangesAsync(CancellationToken.None); } - var request = new GetChatSessionQuery() + var request = new GetMyChatSessionQuery() { - Id = _id + Id = _id, + UserContext = userContext }; - var validator = new GetChatSessionQueryValidator(); + var validator = new GetMyChatSessionQueryValidator(); validationResponse = validator.Validate(request); if (validationResponse.IsValid) try { - var handler = new GetChatSessionQueryHandler(context); + var handler = new GetMyChatSessionQueryHandler(context); _response = await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQuery.feature b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQuery.feature similarity index 100% rename from src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQuery.feature rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQuery.feature diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQuery.feature.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQuery.feature.cs similarity index 98% rename from src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQuery.feature.cs rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQuery.feature.cs index 0b161c3..ecf1d9a 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQuery.feature.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQuery.feature.cs @@ -31,7 +31,7 @@ public partial class GetChatSessionsPaginatedQueryFeature private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "ChatCompletion", "Get Chat Sessions Paginated Query", "As an owner of chat sessions\r\nWhen I get chat sessions all or by date range\r\nI ca" + "n get a paginated collection of chat sessions", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); -#line 1 "GetChatSessionsPaginatedQuery.feature" +#line 1 "GetMyChatSessionsPaginatedQuery.feature" #line hidden public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext @@ -119,7 +119,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetChatSessionsPaginatedQuery.feature.ndjson", 7); + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetMyChatSessionsPaginatedQuery.feature.ndjson", 7); } [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get chat sessions paginated")] diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs similarity index 93% rename from src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQueryStepDefinitions.cs rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs index 723a15e..b44a32a 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatSessionsPaginatedQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs @@ -6,7 +6,7 @@ namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion { [Binding] [Scope(Tag = "getChatSessionsPaginatedQuery")] - public class GetChatSessionsPaginatedQueryStepDefinitions : TestBase + public class GetMyChatSessionsPaginatedQueryStepDefinitions : TestBase { private bool _exists; private DateTime _startDate; @@ -70,20 +70,21 @@ public async Task WhenIGetTheChatSessionsPaginated() await context.SaveChangesAsync(CancellationToken.None); } - var request = new GetChatSessionsPaginatedQuery() + var request = new GetMyChatSessionsPaginatedQuery() { PageNumber = _pageNumber, PageSize = _pageSize, StartDate = _startDate == default ? null : _startDate, - EndDate = _endDate == default ? null : _endDate + EndDate = _endDate == default ? null : _endDate, + UserContext = userContext }; - var validator = new GetChatSessionsPaginatedQueryValidator(); + var validator = new GetMyChatSessionsPaginatedQueryValidator(); validationResponse = validator.Validate(request); if (validationResponse.IsValid) try { - var handler = new GetChatSessionsPaginatedQueryHandler(context); + var handler = new GetMyChatSessionsPaginatedQueryHandler(context); _response = await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionsQuery.feature b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQuery.feature similarity index 100% rename from src/Tests.Integration/ChatCompletion/GetChatSessionsQuery.feature rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionsQuery.feature diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionsQuery.feature.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQuery.feature.cs similarity index 98% rename from src/Tests.Integration/ChatCompletion/GetChatSessionsQuery.feature.cs rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionsQuery.feature.cs index de634a6..57e29c8 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatSessionsQuery.feature.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQuery.feature.cs @@ -31,7 +31,7 @@ public partial class GetChatSessionsQueryFeature private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "ChatCompletion", "Get Chat Sessions Query", "As a session owner\r\nWhen I query chat sessions optionally by date range\r\nI get al" + "l sessions that fit the date range", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); -#line 1 "GetChatSessionsQuery.feature" +#line 1 "GetMyChatSessionsQuery.feature" #line hidden public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext @@ -119,7 +119,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetChatSessionsQuery.feature.ndjson", 6); + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/GetMyChatSessionsQuery.feature.ndjson", 6); } [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Get chat sessions")] diff --git a/src/Tests.Integration/ChatCompletion/GetChatSessionsQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs similarity index 91% rename from src/Tests.Integration/ChatCompletion/GetChatSessionsQueryStepDefinitions.cs rename to src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs index 9415b1e..e4770e8 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatSessionsQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs @@ -5,7 +5,7 @@ namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion; [Binding] [Scope(Tag = "getChatSessionsQuery")] -public class GetChatSessionsQueryStepDefinitions : TestBase +public class GetMyChatSessionsQueryStepDefinitions : TestBase { private bool _exists; private bool _withinDateRangeExists; @@ -56,18 +56,19 @@ public async Task WhenIGetTheChatSessions() await context.SaveChangesAsync(CancellationToken.None); } - var request = new GetChatSessionsQuery() + var request = new GetMyChatSessionsQuery() { StartDate = _startDate == default ? null : _startDate, - EndDate = _endDate == default ? null : _endDate + EndDate = _endDate == default ? null : _endDate, + UserContext = userContext }; - var validator = new GetChatSessionsQueryValidator(); + var validator = new GetMyChatSessionsQueryValidator(); validationResponse = validator.Validate(request); if (validationResponse.IsValid) try { - var handler = new GetChatSessionsQueryHandler(context); + var handler = new GetMyChatSessionsQueryHandler(context); _response = await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; } diff --git a/src/Tests.Integration/ChatCompletion/PatchChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/PatchChatSessionCommandStepDefinitions.cs index d5c7fbf..6c8ad07 100644 --- a/src/Tests.Integration/ChatCompletion/PatchChatSessionCommandStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/PatchChatSessionCommandStepDefinitions.cs @@ -38,10 +38,11 @@ public void GivenIHaveANewChatSessionTitle(string title) [When(@"I patch the chatSession")] public async Task WhenIPatchTheChatSession() { - var request = new PatchChatSessionCommand() + var request = new PatchMyChatSessionCommand() { Id = _id, - Title = _title + Title = _title, + UserContext = userContext }; if (_exists) @@ -51,7 +52,7 @@ public async Task WhenIPatchTheChatSession() await context.SaveChangesAsync(CancellationToken.None); } - var validator = new PatchChatSessionCommandValidator(); + var validator = new PatchMyChatSessionCommandValidator(); validationResponse = await validator.ValidateAsync(request); if (validationResponse.IsValid) diff --git a/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature b/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature deleted file mode 100644 index 72d8b5a..0000000 --- a/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature +++ /dev/null @@ -1,19 +0,0 @@ -@updateChatSessionCommand -Feature: Update Chat Session Command -As a chat session owner -When I edit a chat session -I am able to change or add to the chat session - -Scenario: Update Chat Session - Given I have a def "" - And I have a chat session id "" - And the chat session exists "" - When I update the chat session - Then The response is "" - And If the response has validation issues I see the "" in the response - -Examples: - | def | response | responseErrors | id | exists | - | success | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | true | - | not found | NotFound | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | - | bad request: empty id | BadRequest | Id | 00000000-0000-0000-0000-000000000000 | false | \ No newline at end of file diff --git a/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature.cs b/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature.cs deleted file mode 100644 index 26637f7..0000000 --- a/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommand.feature.cs +++ /dev/null @@ -1,182 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by Reqnroll (https://reqnroll.net/). -// Reqnroll Version:3.0.0.0 -// Reqnroll Generator Version:3.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -#region Designer generated code -#pragma warning disable -using Reqnroll; -namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion -{ - - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Reqnroll", "3.0.0.0")] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute()] - public partial class UpdateChatSessionCommandFeature - { - - private global::Reqnroll.ITestRunner testRunner; - - private Microsoft.VisualStudio.TestTools.UnitTesting.TestContext _testContext; - - private static string[] featureTags = new string[] { - "updateChatSessionCommand"}; - - private static global::Reqnroll.FeatureInfo featureInfo = new global::Reqnroll.FeatureInfo(new global::System.Globalization.CultureInfo("en-US"), "ChatCompletion", "Update Chat Session Command", "As a chat session owner\r\nWhen I edit a chat session\r\nI am able to change or add t" + - "o the chat session", global::Reqnroll.ProgrammingLanguage.CSharp, featureTags, InitializeCucumberMessages()); - -#line 1 "UpdateChatSessionCommand.feature" -#line hidden - - public virtual Microsoft.VisualStudio.TestTools.UnitTesting.TestContext TestContext - { - get - { - return this._testContext; - } - set - { - this._testContext = value; - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute()] - public static async global::System.Threading.Tasks.Task FeatureSetupAsync(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext testContext) - { - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute()] - public static async global::System.Threading.Tasks.Task FeatureTearDownAsync() - { - await global::Reqnroll.TestRunnerManager.ReleaseFeatureAsync(featureInfo); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute()] - public async global::System.Threading.Tasks.Task TestInitializeAsync() - { - testRunner = global::Reqnroll.TestRunnerManager.GetTestRunnerForAssembly(featureHint: featureInfo); - try - { - if (((testRunner.FeatureContext != null) - && (testRunner.FeatureContext.FeatureInfo.Equals(featureInfo) == false))) - { - await testRunner.OnFeatureEndAsync(); - } - } - finally - { - if (((testRunner.FeatureContext != null) - && testRunner.FeatureContext.BeforeFeatureHookFailed)) - { - throw new global::Reqnroll.ReqnrollException("Scenario skipped because of previous before feature hook error"); - } - if ((testRunner.FeatureContext == null)) - { - await testRunner.OnFeatureStartAsync(featureInfo); - } - } - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute()] - public async global::System.Threading.Tasks.Task TestTearDownAsync() - { - if ((testRunner == null)) - { - return; - } - try - { - await testRunner.OnScenarioEndAsync(); - } - finally - { - global::Reqnroll.TestRunnerManager.ReleaseTestRunner(testRunner); - testRunner = null; - } - } - - public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, global::Reqnroll.RuleInfo ruleInfo) - { - testRunner.OnScenarioInitialize(scenarioInfo, ruleInfo); - testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(_testContext); - } - - public async global::System.Threading.Tasks.Task ScenarioStartAsync() - { - await testRunner.OnScenarioStartAsync(); - } - - public async global::System.Threading.Tasks.Task ScenarioCleanupAsync() - { - await testRunner.CollectScenarioErrorsAsync(); - } - - private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() - { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("ChatCompletion/UpdateChatSessionCommand.feature.ndjson", 5); - } - - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Update Chat Session")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Update Chat Session")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Update Chat Session Command")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("updateChatSessionCommand")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "true", "0", null, DisplayName="Update Chat Session(success,Success,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,true,0)" + - "")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found", "NotFound", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "1", null, DisplayName="Update Chat Session(not found,NotFound,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,fals" + - "e,1)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("bad request: empty id", "BadRequest", "Id", "00000000-0000-0000-0000-000000000000", "false", "2", null, DisplayName="Update Chat Session(bad request: empty id,BadRequest,Id,00000000-0000-0000-0000-0" + - "00000000000,false,2)")] - public async global::System.Threading.Tasks.Task UpdateChatSession(string def, string response, string responseErrors, string id, string exists, string @__pickleIndex, string[] exampleTags) - { - string[] tagsOfScenario = exampleTags; - global::System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new global::System.Collections.Specialized.OrderedDictionary(); - argumentsOfScenario.Add("def", def); - argumentsOfScenario.Add("response", response); - argumentsOfScenario.Add("responseErrors", responseErrors); - argumentsOfScenario.Add("id", id); - argumentsOfScenario.Add("exists", exists); - string pickleIndex = @__pickleIndex; - global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Update Chat Session", null, tagsOfScenario, argumentsOfScenario, featureTags, pickleIndex); - string[] tagsOfRule = ((string[])(null)); - global::Reqnroll.RuleInfo ruleInfo = null; -#line 7 -this.ScenarioInitialize(scenarioInfo, ruleInfo); -#line hidden - if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags))) - { - await testRunner.SkipScenarioAsync(); - } - else - { - await this.ScenarioStartAsync(); -#line 8 - await testRunner.GivenAsync(string.Format("I have a def \"{0}\"", def), ((string)(null)), ((global::Reqnroll.Table)(null)), "Given "); -#line hidden -#line 9 - await testRunner.AndAsync(string.Format("I have a chat session id \"{0}\"", id), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 10 - await testRunner.AndAsync(string.Format("the chat session exists \"{0}\"", exists), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden -#line 11 - await testRunner.WhenAsync("I update the chat session", ((string)(null)), ((global::Reqnroll.Table)(null)), "When "); -#line hidden -#line 12 - await testRunner.ThenAsync(string.Format("The response is \"{0}\"", response), ((string)(null)), ((global::Reqnroll.Table)(null)), "Then "); -#line hidden -#line 13 - await testRunner.AndAsync(string.Format("If the response has validation issues I see the \"{0}\" in the response", responseErrors), ((string)(null)), ((global::Reqnroll.Table)(null)), "And "); -#line hidden - } - await this.ScenarioCleanupAsync(); - } - } -} -#pragma warning restore -#endregion diff --git a/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommandStepDefinitions.cs deleted file mode 100644 index ae82879..0000000 --- a/src/Tests.Integration/ChatCompletion/UpdateChatSessionCommandStepDefinitions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; - -namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion -{ - [Binding] - [Scope(Tag = "updateChatSessionCommand")] - public class UpdateChatSessionCommandStepDefinitions : TestBase - { - private bool _exists; - private Guid _id; - - [Given(@"I have a def ""([^""]*)""")] - public void GivenIHaveADef(string def) - { - base.def = def; - } - - [Given(@"I have a chat session id ""([^""]*)""")] - public void GivenIHaveAChatSessionId(string id) - { - Guid.TryParse(id, out _id).ShouldBeTrue(); - } - - [Given(@"the chat session exists ""([^""]*)""")] - public void GivenTheChatSessionExists(string exists) - { - bool.TryParse(exists, out _exists).ShouldBeTrue(); - } - - [When(@"I update the chat session")] - public async Task WhenIUpdateTheChatSession() - { - var request = new UpdateChatSessionCommand() - { - Id = _id, - Title = "My Title" - }; - - if (_exists) - { - var chatSession = ChatSessionEntity.Create(_id, Guid.NewGuid(), "Test Session", ChatMessageRole.assistant, "First Message", "First Response"); - context.ChatSessions.Add(chatSession); - await context.SaveChangesAsync(CancellationToken.None); - } - - var validator = new UpdateChatSessionCommandValidator(); - validationResponse = await validator.ValidateAsync(request); - - if (validationResponse.IsValid) - try - { - var handler = new UpdateChatSessionCommandHandler(context); - await handler.Handle(request, CancellationToken.None); - responseType = CommandResponseType.Successful; - } - catch (Exception e) - { - HandleAssignResponseType(e); - } - else - responseType = CommandResponseType.BadRequest; - } - - [Then(@"The response is ""([^""]*)""")] - public void ThenTheResponseIs(string response) - { - HandleHasResponseType(response); - } - - [Then(@"If the response has validation issues I see the ""([^""]*)"" in the response")] - public void ThenIfTheResponseHasValidationIssuesISeeTheInTheResponse(string expectedErrors) - { - HandleExpectedValidationErrorsAssertions(expectedErrors); - } - } -} diff --git a/src/Tests.Integration/TestBase.cs b/src/Tests.Integration/TestBase.cs index 1757528..b969b2d 100644 --- a/src/Tests.Integration/TestBase.cs +++ b/src/Tests.Integration/TestBase.cs @@ -31,13 +31,20 @@ public enum CommandResponseType internal IConfiguration configuration; internal MockAIAgent agent = new(); internal OpenAIOptions optionsOpenAi = new(); - internal UserEntity userInfo = UserEntity.Create(firstName: "John", lastName: "Doe", email: "john.doe@goodtocode.com", + internal UserContext userContext = UserContext.Create(firstName: "John", lastName: "Doe", email: "john.doe@goodtocode.com", ownerId: Guid.NewGuid(), tenantId: Guid.NewGuid(), roles: ["Admin"]); + private readonly ICurrentUserContext? _currentUserContext; + public TestBase() { - context = new AgentFrameworkContext(new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + _currentUserContext = new MockCurrentUserContext(userContext); + + context = new AgentFrameworkContext( + new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options, + _currentUserContext + ); var executingType = Assembly.GetExecutingAssembly().GetTypes() .FirstOrDefault(x => x.Name == "AutoGeneratedProgram") ?? typeof(TestBase); @@ -132,4 +139,12 @@ protected virtual void Dispose(bool disposing) context?.Dispose(); } } + + private class MockCurrentUserContext(UserContext userContext) : ICurrentUserContext + { + private readonly UserContext _userContext = userContext; + + public Guid OwnerId => _userContext.OwnerId; + public Guid TenantId => _userContext.TenantId; + } } \ No newline at end of file diff --git a/src/Tests.Integration/TestUserInfo.cs b/src/Tests.Integration/TestUserInfo.cs index 4346020..ea0da24 100644 --- a/src/Tests.Integration/TestUserInfo.cs +++ b/src/Tests.Integration/TestUserInfo.cs @@ -2,7 +2,10 @@ namespace Goodtocode.AgentFramework.Tests.Integration; -public class TestUserInfo() : IUserEntity +/// +/// Test implementation of IUserContext for integration testing. +/// +public class TestUserContext() : IUserContext { private readonly Guid _userId = Guid.NewGuid(); private readonly Guid _tenantId = Guid.NewGuid(); diff --git a/src/Tests.Integration/Tests.Integration.csproj b/src/Tests.Integration/Tests.Integration.csproj index e764b25..9c3128f 100644 --- a/src/Tests.Integration/Tests.Integration.csproj +++ b/src/Tests.Integration/Tests.Integration.csproj @@ -21,8 +21,8 @@ - - + + From c68fc8c2ae6d80553a85fd30e4beb86abc4ed48b Mon Sep 17 00:00:00 2001 From: Robert Good Date: Sat, 7 Feb 2026 23:26:44 -0800 Subject: [PATCH 2/5] queries --- data/Chat/Actor.sql | 3 ++- data/Chat/ChatSessions.sql | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/data/Chat/Actor.sql b/data/Chat/Actor.sql index 1b1d451..58e32b1 100644 --- a/data/Chat/Actor.sql +++ b/data/Chat/Actor.sql @@ -1 +1,2 @@ --- Write your own SQL object definition here, and it'll be included in your package. +Select top 1000 * +From Actors \ No newline at end of file diff --git a/data/Chat/ChatSessions.sql b/data/Chat/ChatSessions.sql index e69de29..74e55ca 100644 --- a/data/Chat/ChatSessions.sql +++ b/data/Chat/ChatSessions.sql @@ -0,0 +1,2 @@ +Select Top 1000 * +From ChatSessions \ No newline at end of file From db4663ffe9d3c839a1cf9ecedd60718ba9720b63 Mon Sep 17 00:00:00 2001 From: Robert Good Date: Sat, 7 Feb 2026 23:28:42 -0800 Subject: [PATCH 3/5] v1my --- src/Presentation.Blazor/Clients/BackendApiClient.g.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs index 90df5c8..0d94fd9 100644 --- a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs +++ b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs @@ -113,8 +113,8 @@ public virtual async System.Threading.Tasks.Task GetMyActorProfileAsyn var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1my/actors" - urlBuilder_.Append("api/v1my/actors"); + // Operation Path: "api/v1/my/actors" + urlBuilder_.Append("api/v1/my/actors"); PrepareRequest(client_, request_, urlBuilder_); @@ -255,8 +255,8 @@ public virtual async System.Threading.Tasks.Task SaveMyActorAsync(Save var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1my/actors" - urlBuilder_.Append("api/v1my/actors"); + // Operation Path: "api/v1/my/actors" + urlBuilder_.Append("api/v1/my/actors"); PrepareRequest(client_, request_, urlBuilder_); From f82cb9c9443973940caeabe8c74ee06fd8289a02 Mon Sep 17 00:00:00 2001 From: Robert Good Date: Sat, 7 Feb 2026 23:31:58 -0800 Subject: [PATCH 4/5] fixed actor route --- .../Clients/BackendApiClient.g.cs | 60 +++++++++---------- .../Actor/MyActorController.cs | 2 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs index 0d94fd9..c9de742 100644 --- a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs +++ b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs @@ -632,9 +632,9 @@ public virtual async System.Threading.Tasks.Task PatchMyChatSessionAsync(System. /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetMyChatMessagesPaginatedAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Guid? userInfo_OwnerId, System.Guid? userInfo_TenantId, string userInfo_FirstName, string userInfo_LastName, string userInfo_Email, System.Collections.Generic.IEnumerable userInfo_Roles, bool? userInfo_CanView, bool? userInfo_CanEdit, bool? userInfo_CanDelete) + public virtual System.Threading.Tasks.Task GetMyChatMessagesPaginatedAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Guid? userContext_OwnerId, System.Guid? userContext_TenantId, string userContext_FirstName, string userContext_LastName, string userContext_Email, System.Collections.Generic.IEnumerable userContext_Roles, bool? userContext_CanView, bool? userContext_CanEdit, bool? userContext_CanDelete) { - return GetMyChatMessagesPaginatedAsync(startDate, endDate, pageNumber, pageSize, userInfo_OwnerId, userInfo_TenantId, userInfo_FirstName, userInfo_LastName, userInfo_Email, userInfo_Roles, userInfo_CanView, userInfo_CanEdit, userInfo_CanDelete, System.Threading.CancellationToken.None); + return GetMyChatMessagesPaginatedAsync(startDate, endDate, pageNumber, pageSize, userContext_OwnerId, userContext_TenantId, userContext_FirstName, userContext_LastName, userContext_Email, userContext_Roles, userContext_CanView, userContext_CanEdit, userContext_CanDelete, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -652,7 +652,7 @@ public virtual System.Threading.Tasks.Task GetMyCha /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetMyChatMessagesPaginatedAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Guid? userInfo_OwnerId, System.Guid? userInfo_TenantId, string userInfo_FirstName, string userInfo_LastName, string userInfo_Email, System.Collections.Generic.IEnumerable userInfo_Roles, bool? userInfo_CanView, bool? userInfo_CanEdit, bool? userInfo_CanDelete, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetMyChatMessagesPaginatedAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Guid? userContext_OwnerId, System.Guid? userContext_TenantId, string userContext_FirstName, string userContext_LastName, string userContext_Email, System.Collections.Generic.IEnumerable userContext_Roles, bool? userContext_CanView, bool? userContext_CanEdit, bool? userContext_CanDelete, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -684,41 +684,41 @@ public virtual async System.Threading.Tasks.Task Ge { urlBuilder_.Append(System.Uri.EscapeDataString("PageSize")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageSize, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_OwnerId != null) + if (userContext_OwnerId != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.OwnerId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_OwnerId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.OwnerId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_OwnerId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_TenantId != null) + if (userContext_TenantId != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.TenantId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_TenantId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.TenantId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_TenantId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_FirstName != null) + if (userContext_FirstName != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.FirstName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_FirstName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.FirstName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_FirstName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_LastName != null) + if (userContext_LastName != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.LastName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_LastName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.LastName")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_LastName, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_Email != null) + if (userContext_Email != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.Email")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_Email, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.Email")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_Email, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_Roles != null) + if (userContext_Roles != null) { - foreach (var item_ in userInfo_Roles) { urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.Roles")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } + foreach (var item_ in userContext_Roles) { urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.Roles")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(item_, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } } - if (userInfo_CanView != null) + if (userContext_CanView != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.CanView")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_CanView, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.CanView")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_CanView, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_CanEdit != null) + if (userContext_CanEdit != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.CanEdit")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_CanEdit, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.CanEdit")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_CanEdit, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } - if (userInfo_CanDelete != null) + if (userContext_CanDelete != null) { - urlBuilder_.Append(System.Uri.EscapeDataString("UserInfo.CanDelete")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userInfo_CanDelete, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + urlBuilder_.Append(System.Uri.EscapeDataString("UserContext.CanDelete")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(userContext_CanDelete, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } urlBuilder_.Length--; @@ -1782,8 +1782,8 @@ public partial class CreateMyChatMessageCommand [System.Text.Json.Serialization.JsonPropertyName("Message")] public string Message { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("UserInfo")] - public IUserEntity UserInfo { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("UserContext")] + public IUserContext UserContext { get; set; } } @@ -1800,13 +1800,13 @@ public partial class CreateMyChatSessionCommand [System.Text.Json.Serialization.JsonPropertyName("Message")] public string Message { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("UserInfo")] - public IUserEntity UserInfo { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("UserContext")] + public IUserContext UserContext { get; set; } } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")] - public partial class IUserEntity + public partial class IUserContext { [System.Text.Json.Serialization.JsonPropertyName("OwnerId")] @@ -1848,8 +1848,8 @@ public partial class PatchMyChatSessionCommand [System.Text.Json.Serialization.JsonPropertyName("Title")] public string Title { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("UserInfo")] - public IUserEntity UserInfo { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("UserContext")] + public IUserContext UserContext { get; set; } } @@ -1899,8 +1899,8 @@ public partial class SaveMyActorCommand [System.Text.Json.Serialization.JsonPropertyName("TenantId")] public System.Guid TenantId { get; set; } - [System.Text.Json.Serialization.JsonPropertyName("UserInfo")] - public IUserEntity UserInfo { get; set; } + [System.Text.Json.Serialization.JsonPropertyName("UserContext")] + public IUserContext UserContext { get; set; } } diff --git a/src/Presentation.WebApi/Actor/MyActorController.cs b/src/Presentation.WebApi/Actor/MyActorController.cs index e55dd17..e5d7118 100644 --- a/src/Presentation.WebApi/Actor/MyActorController.cs +++ b/src/Presentation.WebApi/Actor/MyActorController.cs @@ -9,7 +9,7 @@ namespace Goodtocode.AgentFramework.Presentation.WebApi.Actor; /// [ApiController] [ApiConventionType(typeof(DefaultApiConventions))] -[Route("api/v{version:apiVersion}my/actors")] +[Route("api/v{version:apiVersion}/my/actors")] [ApiVersion("1.0")] [Authorize] public class MyActorController : ApiControllerBase From 21f2a8ee6712d0baa6eae63d0e28766467560153 Mon Sep 17 00:00:00 2001 From: Robert Good Date: Sat, 7 Feb 2026 23:42:39 -0800 Subject: [PATCH 5/5] port update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44dd814..2b12c1b 100644 --- a/README.md +++ b/README.md @@ -275,9 +275,9 @@ Right-click Presentation.WebApi and select Set as Default Project dotnet run --project src/Presentation.WebApi/Presentation.WebApi.csproj ``` -## Open http://localhost:7777/swagger/index.html +## Open http://localhost:7175/swagger/index.html Open Microsoft Edge or modern browser -Navigate to: http://localhost:7777/swagger/index.html in your browser to the Swagger API Interface +Navigate to: http://localhost:7175/swagger/index.html in your browser to the Swagger API Interface # Github Actions for Azure IaC and CI/CD ## GitHub Actions (.github folder)