diff --git a/Constants/XAiDataProviderConstants.cs b/Constants/XAiDataProviderConstants.cs new file mode 100644 index 0000000..52e1c06 --- /dev/null +++ b/Constants/XAiDataProviderConstants.cs @@ -0,0 +1,46 @@ +using xExceptions.Attributes; + +namespace xAiModels.Constants +{ + public struct XAiDataProviderConstants + { + public const string XDefaultAiProjectSuffix = "DefAiPrj"; + public const string XDefaultAiConversationSuffix = "DefAiCnv"; + } + + public enum XAiDataProviderHubActions + { + // + // Project ... + [StringValue("ProjectCreated")] + ProjectCreated, + + [StringValue("ProjectUpdated")] + ProjectUpdated, + + [StringValue("ProjectRemoved")] + ProjectRemoved, + + // + // Conversation ... + [StringValue("ConversationCreated")] + ConversationCreated, + + [StringValue("ConversationUpdated")] + ConversationUpdated, + + [StringValue("ConversationRemoved")] + ConversationRemoved, + + // + // Message ... + [StringValue("MessageCreated")] + MessageCreated, + + [StringValue("MessageUpdated")] + MessageUpdated, + + [StringValue("MessageRemoved")] + MessageRemoved, + } +} \ No newline at end of file diff --git a/Interfaces/Dtos/IXAiConversationRepositoryService.cs b/Interfaces/Dtos/IXAiConversationRepositoryService.cs index 737372b..0fc49d4 100644 --- a/Interfaces/Dtos/IXAiConversationRepositoryService.cs +++ b/Interfaces/Dtos/IXAiConversationRepositoryService.cs @@ -1,10 +1,222 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Query; using xAiModels.Models.Dtos; using xAiModels.Models.Entities; using xDataService.Interfaces; +using xModels.Dtos; namespace xAiModels.Interfaces.Dtos { public interface IXAiConversationRepositoryService : IXBaseRepositoryService - { } + { + // + #region Actions ... + // + #region Add ... + /// + /// add a new Dto ... + /// + /// + /// + /// + /// + /// + Task AddAsync( + XAiConversationDto item, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// add or update a Dto (add if not exists/update if exists) ... + /// + /// + /// + /// + /// + /// + Task AddOrUpdateAsync( + XAiConversationDto item, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region Update ... + /// + /// Update an Dto values ... + /// + /// + /// + /// + /// + /// + /// + Task UpdateAsync( + Guid id, + XAiConversationDto item, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region Remove ... + /// + /// remove an Dto by it's Id ... + /// + /// + /// + /// + /// + /// + /// + Task RemoveAsync( + Guid id, + bool softDelete = false, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// remove an Dto ... + /// + /// + /// + /// + /// + /// + /// + Task RemoveAsync( + XAiConversationDto item, + bool softDelete = false, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region Retrieve ... + /// + /// retrieve an Dto by it's Id ... + /// + /// + /// + /// + /// + /// + /// + Task GetAsync( + Guid id, + bool ignoreSoftDeleteds = true, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// retrieve all exists Dtos ... + /// + /// + /// + /// + /// + /// + /// + Task> GetAllAsync( + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// retrieve all exists Dtos + /// as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + IAsyncEnumerable GetAllAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// find an Dto by providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + Task FindOneAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// find a collection of Dtos by proving a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + Task> FindManyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + + /// + /// retrieve Dtos based on XQuery Pagination structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> QueryAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ); + #endregion + #endregion + } } \ No newline at end of file diff --git a/Interfaces/Dtos/IXAiProjectRepositoryService.cs b/Interfaces/Dtos/IXAiProjectRepositoryService.cs index 82dcc77..adb5c63 100644 --- a/Interfaces/Dtos/IXAiProjectRepositoryService.cs +++ b/Interfaces/Dtos/IXAiProjectRepositoryService.cs @@ -14,7 +14,6 @@ namespace xAiModels.Interfaces.Dtos { public interface IXAiProjectRepositoryService : IXBaseRepositoryService { - // #region Actions ... // @@ -25,12 +24,14 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task AddAsync( XAiProjectDto item, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -40,12 +41,14 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task AddOrUpdateAsync( XAiProjectDto item, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); #endregion @@ -59,6 +62,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task UpdateAsync( @@ -66,6 +70,7 @@ namespace xAiModels.Interfaces.Dtos XAiProjectDto item, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); #endregion @@ -79,6 +84,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task RemoveAsync( @@ -86,6 +92,7 @@ namespace xAiModels.Interfaces.Dtos bool softDelete = false, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -96,6 +103,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task RemoveAsync( @@ -103,6 +111,7 @@ namespace xAiModels.Interfaces.Dtos bool softDelete = false, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); #endregion @@ -116,6 +125,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task GetAsync( @@ -123,6 +133,7 @@ namespace xAiModels.Interfaces.Dtos bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -133,6 +144,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task> GetAllAsync( @@ -140,6 +152,7 @@ namespace xAiModels.Interfaces.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -151,6 +164,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// IAsyncEnumerable GetAllAsAsyncEnumerable( @@ -158,6 +172,7 @@ namespace xAiModels.Interfaces.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -168,6 +183,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task FindOneAsync( @@ -175,6 +191,7 @@ namespace xAiModels.Interfaces.Dtos bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -186,6 +203,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task> FindManyAsync( @@ -194,6 +212,7 @@ namespace xAiModels.Interfaces.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -206,6 +225,7 @@ namespace xAiModels.Interfaces.Dtos /// /// /// + /// /// /// Task> QueryAsync( @@ -215,6 +235,7 @@ namespace xAiModels.Interfaces.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); #endregion diff --git a/Interfaces/Enrichers/IXAiConversationEnricher.cs b/Interfaces/Enrichers/IXAiConversationEnricher.cs index 584b629..33ab43b 100644 --- a/Interfaces/Enrichers/IXAiConversationEnricher.cs +++ b/Interfaces/Enrichers/IXAiConversationEnricher.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using xAiModels.Models.Dtos; using xDataService.Interfaces; @@ -7,5 +10,35 @@ namespace xAiModels.Interfaces.Enrichers /// Ai Conversation Enricher ... /// public interface IXAiConversationEnricher : IXBaseEnricher - { } + { + /// + /// Converts a Signle Entity to Dto ... + /// + /// + /// + /// + /// + /// + Task EnrichAsync( + XAiConversationDto source, + XAiConversationDto destination, + bool fillMessages = false, + CancellationToken cancellationToken = default + ); + + /// + /// Converts a Collection of Entities to Dtos ... + /// + /// + /// + /// + /// + /// + Task EnrichRangeAsync( + IEnumerable sources, + IEnumerable destinations, + bool fillMessages = false, + CancellationToken cancellationToken = default + ); + } } \ No newline at end of file diff --git a/Interfaces/Enrichers/IXAiProjectEnricher.cs b/Interfaces/Enrichers/IXAiProjectEnricher.cs index 3e4a1ae..0a3e379 100644 --- a/Interfaces/Enrichers/IXAiProjectEnricher.cs +++ b/Interfaces/Enrichers/IXAiProjectEnricher.cs @@ -13,6 +13,7 @@ namespace xAiModels.Interfaces.Enrichers XAiProjectDto source, XAiProjectDto destination, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); @@ -20,6 +21,7 @@ namespace xAiModels.Interfaces.Enrichers IEnumerable sources, IEnumerable destinations, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ); } diff --git a/Models/Dtos/XAiConversationDto.cs b/Models/Dtos/XAiConversationDto.cs index 480777f..3cb1e5f 100644 --- a/Models/Dtos/XAiConversationDto.cs +++ b/Models/Dtos/XAiConversationDto.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using xModels.Base; using xModels.Dtos; @@ -40,6 +41,11 @@ namespace xAiModels.Models.Dtos /// public DateTime UpdatedAt { get; set; } + /// + /// Conversation Messages ... + /// + public virtual IEnumerable Messages { get; set; } = []; + /// /// Project ID ... /// diff --git a/Providers/Dtos/XAiConversationRepositoryService.cs b/Providers/Dtos/XAiConversationRepositoryService.cs index 7af7bdb..6b19778 100644 --- a/Providers/Dtos/XAiConversationRepositoryService.cs +++ b/Providers/Dtos/XAiConversationRepositoryService.cs @@ -53,27 +53,12 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.AddAsync( + return await AddAsync( item: item, + fillMessages: false, saveChanges: saveChanges, cancellationToken: cancellationToken ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } /// @@ -89,27 +74,12 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.AddAsync( + return await AddOrUpdateAsync( item: item, + fillMessages: false, saveChanges: saveChanges, cancellationToken: cancellationToken ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } #endregion @@ -130,28 +100,13 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.UpdateAsync( + return await UpdateAsync( id: id, item: item, + fillMessages: false, saveChanges: saveChanges, cancellationToken: cancellationToken ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } #endregion @@ -172,28 +127,13 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.RemoveAsync( + return await RemoveAsync( id: id, + fillMessages: false, softDelete: softDelete, saveChanges: saveChanges, cancellationToken: cancellationToken ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } /// @@ -211,28 +151,13 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.RemoveAsync( + return await RemoveAsync( item: item, + fillMessages: false, softDelete: softDelete, saveChanges: saveChanges, cancellationToken: cancellationToken ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } #endregion @@ -253,28 +178,13 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.GetAsync( + return await GetAsync( id: id, + fillMessages: false, includeBuilder: includeBuilder, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } /// @@ -292,28 +202,13 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.GetAllAsync( + return await GetAllAsync( + fillMessages: false, orderBuilder: orderBuilder, includeBuilder: includeBuilder, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichRangeAsync( - sources: result, - destinations: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } /// @@ -325,35 +220,20 @@ namespace xAiModels.Providers.Dtos /// /// /// - public override async IAsyncEnumerable GetAllAsAsyncEnumerable( + public override IAsyncEnumerable GetAllAsAsyncEnumerable( bool ignoreSoftDeleteds = true, Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default + CancellationToken cancellationToken = default ) { - // - var enumerable = base.GetAllAsAsyncEnumerable( + return GetAllAsAsyncEnumerable( + fillMessages: false, orderBuilder: orderBuilder, includeBuilder: includeBuilder, + cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); - await foreach (var dto in enumerable) - { - // - if (cancellationToken.IsCancellationRequested) - { - yield break; - } - - // - await enricher.EnrichAsync( - source: dto, - destination: dto, - cancellationToken: cancellationToken - ); - yield return dto; - } } /// @@ -371,28 +251,13 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.FindOneAsync( + return await FindOneAsync( + fillMessages: false, predicate: predicate, includeBuilder: includeBuilder, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichAsync( - source: result, - destination: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } /// @@ -412,29 +277,14 @@ namespace xAiModels.Providers.Dtos CancellationToken cancellationToken = default ) { - // - var result = await base.FindManyAsync( + return await FindManyAsync( + fillMessages: false, predicate: predicate, orderBuilder: orderBuilder, includeBuilder: includeBuilder, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); - - // - // Do Enrichment if result Exists ... - if (!result.IsNullOrDefault()) - { - // - await enricher.EnrichRangeAsync( - sources: result, - destinations: result, - cancellationToken: cancellationToken - ); - } - - // - return result; } /// @@ -455,6 +305,471 @@ namespace xAiModels.Providers.Dtos Func, IIncludableQueryable> includeBuilder = null, CancellationToken cancellationToken = default ) + { + return await QueryAsync( + query: query, + fillMessages: false, + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + } + #endregion + #endregion + + // + #region Actions ... + // + #region Add ... + /// + /// add a new Dto ... + /// + /// + /// + /// + /// + /// + public virtual async Task AddAsync( + XAiConversationDto item, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.AddAsync( + item: item, + saveChanges: saveChanges, + cancellationToken: cancellationToken + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// add or update a Dto (add if not exists/update if exists) ... + /// + /// + /// + /// + /// + /// + public virtual async Task AddOrUpdateAsync( + XAiConversationDto item, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.AddAsync( + item: item, + saveChanges: saveChanges, + cancellationToken: cancellationToken + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + + // + #region Update ... + /// + /// Update an Dto values ... + /// + /// + /// + /// + /// + /// + /// + public virtual async Task UpdateAsync( + Guid id, + XAiConversationDto item, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.UpdateAsync( + id: id, + item: item, + saveChanges: saveChanges, + cancellationToken: cancellationToken + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + + // + #region Remove ... + /// + /// remove an Dto by it's Id ... + /// + /// + /// + /// + /// + /// + /// + public virtual async Task RemoveAsync( + Guid id, + bool softDelete = true, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.RemoveAsync( + id: id, + softDelete: softDelete, + saveChanges: saveChanges, + cancellationToken: cancellationToken + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// remove an Dto ... + /// + /// + /// + /// + /// + /// + /// + public virtual async Task RemoveAsync( + XAiConversationDto item, + bool softDelete = true, + bool saveChanges = true, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.RemoveAsync( + item: item, + softDelete: softDelete, + saveChanges: saveChanges, + cancellationToken: cancellationToken + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + + // + #region Retrieve ... + /// + /// retrieve an Dto by it's Id ... + /// + /// + /// + /// + /// + /// + /// + public virtual async Task GetAsync( + Guid id, + bool ignoreSoftDeleteds = true, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.GetAsync( + id: id, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve all exists Dtos ... + /// + /// + /// + /// + /// + /// + /// + public virtual async Task> GetAllAsync( + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.GetAllAsync( + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichRangeAsync( + sources: result, + destinations: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve all exists Dtos + /// as Async Enumerable ... + /// + /// + /// + /// + /// + /// + /// + public virtual async IAsyncEnumerable GetAllAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + [EnumeratorCancellation] CancellationToken cancellationToken = default + ) + { + // + var enumerable = base.GetAllAsAsyncEnumerable( + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + await foreach (var dto in enumerable) + { + // + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + await enricher.EnrichAsync( + source: dto, + destination: dto, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + yield return dto; + } + } + + /// + /// find an Dto by providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + public virtual async Task FindOneAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.FindOneAsync( + predicate: predicate, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichAsync( + source: result, + destination: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// find a collection of Dtos by proving a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + /// + public virtual async Task> FindManyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.FindManyAsync( + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichRangeAsync( + sources: result, + destinations: result, + fillMessages: fillMessages, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve Dtos based on XQuery Pagination structure ... + /// + /// + /// + /// + /// + /// + /// + /// + /// + public virtual async Task> QueryAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + bool fillMessages = true, + CancellationToken cancellationToken = default + ) { // var result = await base.QueryAsync( @@ -474,6 +789,7 @@ namespace xAiModels.Providers.Dtos await enricher.EnrichRangeAsync( sources: result.Items, destinations: result.Items, + fillMessages: fillMessages, cancellationToken: cancellationToken ); } diff --git a/Providers/Dtos/XAiProjectRepositoryService.cs b/Providers/Dtos/XAiProjectRepositoryService.cs index 85cc68a..95b3401 100644 --- a/Providers/Dtos/XAiProjectRepositoryService.cs +++ b/Providers/Dtos/XAiProjectRepositoryService.cs @@ -58,6 +58,7 @@ namespace xAiModels.Providers.Dtos item: item, saveChanges: saveChanges, fillConversations: false, + fillConversationMessages: false, cancellationToken: cancellationToken ); } @@ -79,6 +80,7 @@ namespace xAiModels.Providers.Dtos item: item, saveChanges: saveChanges, fillConversations: false, + fillConversationMessages: false, cancellationToken: cancellationToken ); } @@ -106,6 +108,7 @@ namespace xAiModels.Providers.Dtos item: item, saveChanges: saveChanges, fillConversations: false, + fillConversationMessages: false, cancellationToken: cancellationToken ); } @@ -133,6 +136,7 @@ namespace xAiModels.Providers.Dtos softDelete: softDelete, saveChanges: saveChanges, fillConversations: false, + fillConversationMessages: false, cancellationToken: cancellationToken ); } @@ -157,6 +161,7 @@ namespace xAiModels.Providers.Dtos softDelete: softDelete, saveChanges: saveChanges, fillConversations: false, + fillConversationMessages: false, cancellationToken: cancellationToken ); } @@ -183,6 +188,7 @@ namespace xAiModels.Providers.Dtos id: id, fillConversations: false, includeBuilder: includeBuilder, + fillConversationMessages: false, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); @@ -207,6 +213,7 @@ namespace xAiModels.Providers.Dtos fillConversations: false, orderBuilder: orderBuilder, includeBuilder: includeBuilder, + fillConversationMessages: false, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); @@ -232,6 +239,7 @@ namespace xAiModels.Providers.Dtos fillConversations: false, orderBuilder: orderBuilder, includeBuilder: includeBuilder, + fillConversationMessages: false, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); @@ -256,6 +264,7 @@ namespace xAiModels.Providers.Dtos predicate: predicate, fillConversations: false, includeBuilder: includeBuilder, + fillConversationMessages: false, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); @@ -283,6 +292,7 @@ namespace xAiModels.Providers.Dtos fillConversations: false, orderBuilder: orderBuilder, includeBuilder: includeBuilder, + fillConversationMessages: false, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); @@ -310,8 +320,10 @@ namespace xAiModels.Providers.Dtos return await QueryAsync( query: query, predicate: predicate, + fillConversations: false, orderBuilder: orderBuilder, includeBuilder: includeBuilder, + fillConversationMessages: false, cancellationToken: cancellationToken, ignoreSoftDeleteds: ignoreSoftDeleteds ); @@ -329,12 +341,14 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task AddAsync( XAiProjectDto item, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -354,7 +368,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -368,12 +383,14 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task AddOrUpdateAsync( XAiProjectDto item, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -393,7 +410,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -411,6 +429,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task UpdateAsync( @@ -418,6 +437,7 @@ namespace xAiModels.Providers.Dtos XAiProjectDto item, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -438,7 +458,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -456,6 +477,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task RemoveAsync( @@ -463,6 +485,7 @@ namespace xAiModels.Providers.Dtos bool softDelete = false, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -483,7 +506,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -498,6 +522,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task RemoveAsync( @@ -505,6 +530,7 @@ namespace xAiModels.Providers.Dtos bool softDelete = false, bool saveChanges = true, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -525,7 +551,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -543,6 +570,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task GetAsync( @@ -550,6 +578,7 @@ namespace xAiModels.Providers.Dtos bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -570,7 +599,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -585,6 +615,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task> GetAllAsync( @@ -592,6 +623,7 @@ namespace xAiModels.Providers.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -612,7 +644,8 @@ namespace xAiModels.Providers.Dtos sources: result, destinations: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -628,6 +661,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async IAsyncEnumerable GetAllAsAsyncEnumerable( @@ -635,6 +669,7 @@ namespace xAiModels.Providers.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, [EnumeratorCancellation] CancellationToken cancellationToken = default ) { @@ -657,7 +692,8 @@ namespace xAiModels.Providers.Dtos source: dto, destination: dto, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); yield return dto; } @@ -670,6 +706,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task FindOneAsync( @@ -677,6 +714,7 @@ namespace xAiModels.Providers.Dtos bool ignoreSoftDeleteds = true, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -697,7 +735,8 @@ namespace xAiModels.Providers.Dtos source: result, destination: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -713,6 +752,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task> FindManyAsync( @@ -721,6 +761,7 @@ namespace xAiModels.Providers.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -742,7 +783,8 @@ namespace xAiModels.Providers.Dtos sources: result, destinations: result, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } @@ -759,6 +801,7 @@ namespace xAiModels.Providers.Dtos /// /// /// + /// /// /// public virtual async Task> QueryAsync( @@ -768,6 +811,7 @@ namespace xAiModels.Providers.Dtos Func, IOrderedQueryable> orderBuilder = null, Func, IIncludableQueryable> includeBuilder = null, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { @@ -790,7 +834,8 @@ namespace xAiModels.Providers.Dtos sources: result.Items, destinations: result.Items, fillConversations: fillConversations, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages ); } diff --git a/Providers/Enrichers/XAiConversationEnricher.cs b/Providers/Enrichers/XAiConversationEnricher.cs index d7725d5..9f6452b 100644 --- a/Providers/Enrichers/XAiConversationEnricher.cs +++ b/Providers/Enrichers/XAiConversationEnricher.cs @@ -17,16 +17,19 @@ namespace xAiModels.Providers.Enrichers public class XAiConversationEnricher : IXAiConversationEnricher { private readonly IXIdentityProvider identityProvider; + private readonly IXAiMessageRepositoryService messageRepositoryService; private readonly IXAiProjectRepositoryService projectRepositoryService; private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider; public XAiConversationEnricher( IXIdentityProvider identityProvider, + IXAiMessageRepositoryService messageRepositoryService, IXAiProjectRepositoryService projectRepositoryService, IXAiConversationTitleResourceProvider conversationTitleResourceProvider ) { this.identityProvider = identityProvider; + this.messageRepositoryService = messageRepositoryService; this.projectRepositoryService = projectRepositoryService; this.conversationTitleResourceProvider = conversationTitleResourceProvider; } @@ -43,16 +46,70 @@ namespace xAiModels.Providers.Enrichers XAiConversationDto destination, CancellationToken cancellationToken = default ) + { + await EnrichAsync( + source: source, + fillMessages: false, + destination: destination, + cancellationToken: cancellationToken + ); + } + + /// + /// Converts a Collection of Entities to Dtos ... + /// + /// + /// + /// + /// + public async Task EnrichRangeAsync( + IEnumerable sources, + IEnumerable destinations, + CancellationToken cancellationToken = default + ) + { + await EnrichRangeAsync( + sources: sources, + fillMessages: false, + destinations: destinations, + cancellationToken: cancellationToken + ); + } + + /// + /// Converts a Signle Entity to Dto ... + /// + /// + /// + /// + /// + /// + public async Task EnrichAsync( + XAiConversationDto source, + XAiConversationDto destination, + bool fillMessages = false, + CancellationToken cancellationToken = default + ) { // // Run independent async operations concurrently var ownerTask = ResolveOwnerAsync(source.OwnerId, cancellationToken); var projectTask = ResolveProjectAsync(source.ProjectId, cancellationToken); var titleLocalesTask = ResolveTitleLocalesAsync(source.Title, cancellationToken); + var messagesTask = ResolveMessagesAsync(source.Id, cancellationToken); // // wait until All Tasks Resolved ... - await Task.WhenAll(ownerTask, titleLocalesTask, projectTask); + Task[] tasks = [ + ownerTask, + projectTask, + titleLocalesTask + ]; + if (fillMessages) + { + tasks = [.. tasks, messagesTask]; + } + await Task.WhenAll(tasks); // // Attached Resolved Info to Destination ... @@ -66,6 +123,10 @@ namespace xAiModels.Providers.Enrichers destination.ProjectTitleResource = project.TitleResource; destination.ProjectDescriptionResource = project.DescriptionResource; } + if (!messagesTask.Result.IsNull()) + { + destination.Messages = [.. messagesTask.Result]; + } } /// @@ -73,11 +134,13 @@ namespace xAiModels.Providers.Enrichers /// /// /// + /// /// /// public async Task EnrichRangeAsync( IEnumerable sources, IEnumerable destinations, + bool fillMessages = false, CancellationToken cancellationToken = default ) { @@ -185,6 +248,37 @@ namespace xAiModels.Providers.Enrichers // return result; } + + /// + /// Retrieve Specified Conversation Messages ... + /// + /// + /// + /// + private async Task> ResolveMessagesAsync( + Guid conversationId, + CancellationToken cancellationToken = default + ) + { + // + var result = new List(); + + // + if (conversationId.IsDefaultGuid()) + { + return result; + } + + // + result = [.. await messageRepositoryService.FindManyAsync( + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.CreatedOn), + predicate: x => x.ConversationId == conversationId + )]; + + // + return result; + } #endregion } } \ No newline at end of file diff --git a/Providers/Enrichers/XAiProjectEnricher.cs b/Providers/Enrichers/XAiProjectEnricher.cs index 842343b..5194b90 100644 --- a/Providers/Enrichers/XAiProjectEnricher.cs +++ b/Providers/Enrichers/XAiProjectEnricher.cs @@ -52,14 +52,19 @@ namespace xAiModels.Providers.Enrichers XAiProjectDto source, XAiProjectDto destination, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { // // Run independent async operations concurrently + var conversationsTask = ResolveConversationsAsync( + source.Id, + fillConversationMessages, + cancellationToken + ); var titleTask = ResolveTitleAsync(source.Title, cancellationToken); var ownerTask = ResolveOwnerAsync(source.OwnerId, cancellationToken); - var conversationsTask = ResolveConversationsAsync(source.Id, cancellationToken); var descriptionTask = ResolveDescriptionAsync(source.Description, cancellationToken); // @@ -107,13 +112,20 @@ namespace xAiModels.Providers.Enrichers IEnumerable sources, IEnumerable destinations, bool fillConversations = true, + bool fillConversationMessages = true, CancellationToken cancellationToken = default ) { // // Make Tasks Ready ... var tasks = sources.Zip(destinations, (src, dst) => - EnrichAsync(src, dst, fillConversations, cancellationToken) + EnrichAsync( + source: src, + destination: dst, + fillConversations: fillConversations, + cancellationToken: cancellationToken, + fillConversationMessages: fillConversationMessages + ) ); // @@ -224,10 +236,12 @@ namespace xAiModels.Providers.Enrichers /// Retrieve Project Conversations ... /// /// + /// /// /// private async Task> ResolveConversationsAsync( Guid projectId, + bool fillMessages = false, CancellationToken cancellationToken = default ) { @@ -248,6 +262,7 @@ namespace xAiModels.Providers.Enrichers orderBuilder: null, includeBuilder: null, ignoreSoftDeleteds: true, + fillMessages: fillMessages, predicate: x => x.Id == projectId, cancellationToken: cancellationToken ); diff --git a/Providers/Hubs/XAiDataProviderHub.cs b/Providers/Hubs/XAiDataProviderHub.cs new file mode 100644 index 0000000..ddc0985 --- /dev/null +++ b/Providers/Hubs/XAiDataProviderHub.cs @@ -0,0 +1,271 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using xAiModels.Constants; +using xAiModels.Models.Dtos; +using xCommons.Extensions; +using xPushService.Base; + +namespace xAiModels.Providers.Hubs +{ + public class XAiDataProviderHub : XBaseHub + { + public XAiDataProviderHub( + ILogger logger + ) : base(logger) + { } + + // + #region XAiProject ... + /// + /// Notify a New Item Created ... + /// + /// + /// + /// + public async Task ProjectCreated( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.ProjectCreated.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + + /// + /// Notify an Item Removed ... + /// + /// + /// + /// + public async Task ProjectRemoved( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.ProjectRemoved.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + + /// + /// Notify an Item Updated ... + /// + /// + /// + /// + public async Task ProjectUpdated( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.ProjectUpdated.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + #endregion + + // + #region XAiConversation ... + /// + /// Notify a New Item Created ... + /// + /// + /// + /// + public async Task ConversationCreated( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.ConversationCreated.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + + /// + /// Notify an Item Removed ... + /// + /// + /// + /// + public async Task ConversationRemoved( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.ConversationRemoved.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + + /// + /// Notify an Item Updated ... + /// + /// + /// + /// + public async Task ConversationUpdated( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.ConversationUpdated.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + #endregion + + // + #region XAiMessage ... + /// + /// Notify a New Item Created ... + /// + /// + /// + /// + public async Task MessageCreated( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.MessageCreated.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + + /// + /// Notify an Item Removed ... + /// + /// + /// + /// + public async Task MessageRemoved( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.MessageRemoved.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + + /// + /// Notify an Item Updated ... + /// + /// + /// + /// + public async Task MessageUpdated( + XAiProjectDto item, + CancellationToken cancellationToken = default + ) + { + // + var connectionId = Context.ConnectionId; + if (!connectionId.IsNullOrEmpty()) + { + // + await Clients + .AllExcept(connectionId) + .SendAsync( + XAiDataProviderHubActions.MessageUpdated.GetStringValue(), + item.ToJSON(camelCase: true), + connectionId, + cancellationToken + ); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Providers/XAiDataProvider.cs b/Providers/XAiDataProvider.cs index e0fee04..149f39a 100644 --- a/Providers/XAiDataProvider.cs +++ b/Providers/XAiDataProvider.cs @@ -1,9 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; +using xAiModels.Constants; using xAiModels.Interfaces; -using xAiModels.Interfaces.Entities; -using xAiModels.Providers.Dtos; -using xAiModels.Providers.Entities; +using xAiModels.Interfaces.Dtos; +using xAiModels.Models.Dtos; +using xAiModels.Providers.Hubs; +using xCommons.Configurations; +using xCommons.Extensions; +using xExceptions.Constants; +using xModels.Dtos; +using xStringService.Interfaces; namespace xAiModels.Providers { @@ -12,44 +21,35 @@ namespace xAiModels.Providers /// public class XAiDataProvider : IXAiDataProvider { - private readonly IHubContext messageDtoHub; - private readonly IHubContext projectDtoHub; - private readonly IXAiMessageRepository messageRepositoryService; - private readonly IXAiProjectRepository projectRepositoryService; - private readonly IHubContext messageEntityHub; - private readonly IHubContext projectEntityHub; - private readonly IXAiConversationRepository conversationRepository; - private readonly IHubContext conversationDtoHub; - private readonly IHubContext conversationEntityHub; + private readonly IXStringProvider stringProvider; + private readonly XAppConfiguration appConfiguration; + private readonly IHubContext hub; + private readonly IXAiMessageRepositoryService messageRepositoryService; + private readonly IXAiProjectRepositoryService projectRepositoryService; private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider; + private readonly IXAiConversationRepositoryService conversationRepositoryService; private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider; private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider; public XAiDataProvider( - IHubContext messageDtoHub, - IHubContext projectDtoHub, - IXAiMessageRepository messageRepositoryService, - IXAiProjectRepository projectRepositoryService, - IHubContext messageEntityHub, - IHubContext projectEntityHub, - IXAiConversationRepository conversationRepository, - IHubContext conversationDtoHub, - IHubContext conversationEntityHub, + IXStringProvider stringProvider, + XAppConfiguration appConfiguration, + IHubContext hub, + IXAiMessageRepositoryService messageRepositoryService, + IXAiProjectRepositoryService projectRepositoryService, IXAiProjectTitleResourceProvider projectTitleResourceProvider, + IXAiConversationRepositoryService conversationRepositoryService, IXAiConversationTitleResourceProvider conversationTitleResourceProvider, IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider ) { - this.messageDtoHub = messageDtoHub; - this.projectDtoHub = projectDtoHub; - this.projectEntityHub = projectEntityHub; - this.messageEntityHub = messageEntityHub; - this.conversationDtoHub = conversationDtoHub; - this.conversationEntityHub = conversationEntityHub; - this.conversationRepository = conversationRepository; + this.hub = hub; + this.stringProvider = stringProvider; + this.appConfiguration = appConfiguration; this.messageRepositoryService = messageRepositoryService; this.projectRepositoryService = projectRepositoryService; this.projectTitleResourceProvider = projectTitleResourceProvider; + this.conversationRepositoryService = conversationRepositoryService; this.conversationTitleResourceProvider = conversationTitleResourceProvider; this.projectDescriptionResourceProvider = projectDescriptionResourceProvider; } @@ -61,18 +61,481 @@ namespace xAiModels.Providers // #region Private ... /// - /// Handle Default Project for Specified Owner ... + /// Check Default Project Exists or not ... /// /// + /// /// - private async Task HandleDefaultProject(string ownerId) { } + private async Task HasDefaultProject( + string ownerId, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var result = !ownerId.IsNullOrEmpty(); + if (!result) + { + return result; + } + + // + // Check Default Owner Exists or not ... + result = !(await stringProvider.FindOne( + cancellationToken: cancellationToken, + predicate: x => + x.ResourceTitle + .ToNormalString() + .Contains(ownerId.ToNormalString()) && + x.ResourceTitle + .ToNormalString() + .Contains(XAiModelsConstants.XAiProjectTitleIdentifier + .ToNormalString()) && + x.TranslatedValue + .ToNormalString() + .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix) + )) + .IsNullOrDefault(); + return result; + } /// - /// Handle Default Project's Default Conversation for Specified Owner ... + /// Retrieve Default Project of Specified Owner ... + /// Create it if not Handled ... /// /// + /// + /// /// - private async Task HandleDefaultConversation(string ownerId) { } + private async Task GetDefaultProject( + string ownerId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Check Default Project Exists or noit ... + var isExists = await HasDefaultProject( + ownerId: ownerId, + cancellationToken: cancellationToken + ); + if (!isExists) + { + // + await CreateDefaultProject( + ownerId: ownerId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + } + + // + var resource = await stringProvider.FindOne( + cancellationToken: cancellationToken, + predicate: x => + x.ResourceTitle + .ToNormalString() + .Contains(ownerId.ToNormalString()) && + x.ResourceTitle + .ToNormalString() + .Contains(XAiModelsConstants.XAiProjectTitleIdentifier + .ToNormalString()) && + x.TranslatedValue + .ToNormalString() + .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix) + ); + if (resource.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await projectRepositoryService.FindOneAsync( + cancellationToken: cancellationToken, + predicate: x => x.Title + .Equals( + resource.ResourceTitle, + StringComparison.InvariantCultureIgnoreCase + ) + ); + if (result.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + return result; + } + + /// + /// Create Default Project for Specified Owner ... + /// + /// + /// + /// + /// + private async Task CreateDefaultProject( + string ownerId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + if (ownerId.IsNullOrEmpty()) + { + return; + } + + // + var defaultLang = appConfiguration.DefaultLanguage; + + // + // Check Default Owner Exists or not ... + var isExists = await HasDefaultProject( + ownerId: ownerId, + cancellationToken: cancellationToken + ); + if (isExists) + { + return; + } + + // + // Create Default ... + var item = new XAiProjectDto() + { + OwnerId = ownerId, + CreatedOn = DateTime.UtcNow, + Title = Guid.NewGuid().ToString(), + Description = Guid.NewGuid().ToString(), + }; + item = await projectRepositoryService.AddAsync( + item: item, + saveChanges: true, + cancellationToken: cancellationToken + ); + + // + // Prepare Tile and Description Resource Ids ... + var titleResource = await projectTitleResourceProvider.AddOrUpdateLocale( + owner: ownerId, + identifier: item.Id, + locale: new XLocaleResourceDto + { + Language = defaultLang, + Value = XAiDataProviderConstants.XDefaultAiProjectSuffix + }, + cancellationToken: cancellationToken + ); + var descriptionResource = await projectDescriptionResourceProvider.AddOrUpdateLocale( + owner: ownerId, + identifier: item.Id, + new XLocaleResourceDto + { + Language = defaultLang, + Value = XAiDataProviderConstants.XDefaultAiProjectSuffix + }, + cancellationToken: cancellationToken + ); + if (titleResource.IsNullOrDefault() || + descriptionResource.IsNullOrDefault() + ) + { + return; + } + + // + // Update Project Resources ... + item.Title = titleResource.Resource; + item.Description = descriptionResource.Resource; + item = await projectRepositoryService.UpdateAsync( + id: item.Id, + item: item, + saveChanges: true, + cancellationToken: cancellationToken + ); + + // + // Sending Push ... + if (!connectionId.IsNullOrEmpty()) + { + // + await SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: item.ToJSON(camelCase: true), + action: XAiDataProviderHubActions.ProjectCreated.GetStringValue() + ); + } + } + + /// + /// Check Default Conversation Exists or not ... + /// + /// + /// + /// + private async Task HasDefaultConversation( + string ownerId, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var result = !ownerId.IsNullOrEmpty(); + if (!result) + { + return result; + } + + // + // Check Default Owner Exists or not ... + result = !(await stringProvider.FindOne( + cancellationToken: cancellationToken, + predicate: x => + x.ResourceTitle + .ToNormalString() + .Contains(ownerId.ToNormalString()) && + x.ResourceTitle + .ToNormalString() + .Contains(XAiModelsConstants.XAiConversationTitleIdentifier + .ToNormalString()) && + x.TranslatedValue + .ToNormalString() + .Contains(XAiDataProviderConstants.XDefaultAiConversationSuffix) + )) + .IsNullOrDefault(); + return result; + } + + /// + /// Check Default Conversation Exists or not ... + /// + /// + /// + /// + /// + private async Task CreateDefaultConversation( + string ownerId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !ownerId.IsNullOrEmpty(); + if (!isValid) + { + return; + } + + // + var defaultLang = appConfiguration.DefaultLanguage; + + // + // Handle Default Project ... + var project = await GetDefaultProject( + ownerId: ownerId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + // Check Default Owner Exists or not ... + isValid = await HasDefaultConversation( + ownerId: ownerId, + cancellationToken: cancellationToken + ); + if (isValid) + { + return; + } + + // + // Create Default Conversation ... + var item = new XAiConversationDto + { + OwnerId = ownerId, + ProjectId = project.Id, + CreatedOn = DateTime.UtcNow, + Title = Guid.NewGuid().ToString() + }; + item = await conversationRepositoryService.AddAsync( + item: item, + saveChanges: true, + cancellationToken: cancellationToken + ); + + // + // Prepare Title Resource Ids ... + var titleResource = await conversationTitleResourceProvider.AddOrUpdateLocale( + owner: ownerId, + identifier: item.Id, + locale: new XLocaleResourceDto + { + Language = defaultLang, + Value = XAiDataProviderConstants.XDefaultAiConversationSuffix + }, + cancellationToken: cancellationToken + ); + if (titleResource.IsNullOrDefault()) + { + return; + } + + // + // Update Conversation Resources ... + item.Title = titleResource.Resource; + item = await conversationRepositoryService.UpdateAsync( + item: item, + id: item.Id, + saveChanges: true, + cancellationToken: cancellationToken + ); + + // + // Sending Push ... + if (!connectionId.IsNullOrEmpty()) + { + // + await SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: item.ToJSON(camelCase: true), + action: XAiDataProviderHubActions.ConversationCreated.GetStringValue() + ); + } + } + + /// + /// Retrieve Default Conversation of Specified Owner ... + /// Create it if not Handled ... + /// + /// + /// + /// + /// + private async Task GetDefaultConversation( + string ownerId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Check Default Conversation Exists or noit ... + var isExists = await HasDefaultConversation( + ownerId: ownerId, + cancellationToken: cancellationToken + ); + if (!isExists) + { + // + await CreateDefaultConversation( + ownerId: ownerId, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + } + + // + var resource = await stringProvider.FindOne( + cancellationToken: cancellationToken, + predicate: x => + x.ResourceTitle + .ToNormalString() + .Contains(ownerId.ToNormalString()) && + x.ResourceTitle + .ToNormalString() + .Contains(XAiModelsConstants.XAiProjectTitleIdentifier + .ToNormalString()) && + x.TranslatedValue + .ToNormalString() + .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix) + ); + if (resource.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + var result = await conversationRepositoryService.FindOneAsync( + cancellationToken: cancellationToken, + predicate: x => x.Title + .Equals( + resource.ResourceTitle, + StringComparison.InvariantCultureIgnoreCase + ) + ); + if (result.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + return result; + } + + /// + /// Send Custom Push Message ... + /// + /// + /// + /// + /// + /// + public async Task SendPush( + string action, + string payLoad, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Collect Allowed Actions ... + var actions = new List + { + XAiDataProviderHubActions.ProjectCreated.GetStringValue(), + XAiDataProviderHubActions.ProjectUpdated.GetStringValue(), + XAiDataProviderHubActions.ProjectRemoved.GetStringValue(), + XAiDataProviderHubActions.MessageCreated.GetStringValue(), + XAiDataProviderHubActions.MessageUpdated.GetStringValue(), + XAiDataProviderHubActions.MessageRemoved.GetStringValue(), + XAiDataProviderHubActions.ConversationCreated.GetStringValue(), + XAiDataProviderHubActions.ConversationUpdated.GetStringValue(), + XAiDataProviderHubActions.ConversationRemoved.GetStringValue(), + }; + + // + // Validate Args ... + var isValid = + !action.IsNullOrEmpty() && + actions.Contains(action); + if (!isValid) + { + return; + } + + // + // Retrieve Connection Id ... + var clients = hub.Clients.All; + if (!connectionId.IsNullOrEmpty()) + { + clients = hub.Clients.AllExcept(connectionId); + } + + // + await clients.SendAsync( + action, + payLoad, + connectionId, + cancellationToken + ); + } + catch { } + } #endregion } } \ No newline at end of file