diff --git a/Configurations/Entities/XAiConversationMappingAction.cs b/Configurations/Entities/XAiConversationMappingAction.cs deleted file mode 100644 index 61a9243..0000000 --- a/Configurations/Entities/XAiConversationMappingAction.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using AutoMapper; -using xAiModels.Interfaces; -using xAiModels.Models.Dtos; -using xAiModels.Models.Entities; -using xIdentityService.Extensions; -using xIdentityService.Interfaces; - -namespace xAiModels.Configurations.Entities -{ - public class XAiConversationMappingAction : IMappingAction - { - private readonly IXIdentityProvider identityProvider; - private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider; - private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider; - private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider; - - public XAiConversationMappingAction( - IXIdentityProvider identityProvider, - IXAiProjectTitleResourceProvider projectTitleResourceProvider, - IXAiConversationTitleResourceProvider conversationTitleResourceProvider, - IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider - ) - { - this.identityProvider = identityProvider; - this.projectTitleResourceProvider = projectTitleResourceProvider; - this.conversationTitleResourceProvider = conversationTitleResourceProvider; - this.projectDescriptionResourceProvider = projectDescriptionResourceProvider; - } - - public void Process( - XAiConversation source, - XAiConversationDto destination, - ResolutionContext context - ) - { - // - // Retrieve Owner ... - var device = identityProvider.GetDevice(); - var ownerUserInfo = identityProvider.GetUserInfo( - device: device, - userSelectByParam: source.OwnerId - ) - .GetAwaiter() - .GetResult(); - var owner = ownerUserInfo.ToXPersonDto(); - - // - // Setting Owner ... - destination.Owner = owner; - - // Retrieve Project Info ... - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Configurations/Entities/XAiMessageMappingAction.cs b/Configurations/Entities/XAiMessageMappingAction.cs deleted file mode 100644 index e14ff74..0000000 --- a/Configurations/Entities/XAiMessageMappingAction.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using AutoMapper; -using xAiModels.Models.Dtos; -using xAiModels.Models.Entities; -using xIdentityService.Extensions; -using xIdentityService.Interfaces; - -namespace xAiModels.Configurations.Entities -{ - public class XAiMessageMappingAction : IMappingAction - { - private readonly IXIdentityProvider identityProvider; - - public XAiMessageMappingAction( - IXIdentityProvider identityProvider - ) - { - this.identityProvider = identityProvider; - } - - public void Process( - XAiMessage source, - XAiMessageDto destination, - ResolutionContext context - ) - { - // - // Retrieve Owner ... - var device = identityProvider.GetDevice(); - var ownerUserInfo = identityProvider.GetUserInfo( - device: device, - userSelectByParam: source.OwnerId - ) - .GetAwaiter() - .GetResult(); - var owner = ownerUserInfo.ToXPersonDto(); - - // - // Setting Owner ... - destination.Owner = owner; - - // - // Retrieve Owner ... - // Retrieve Conversation ... - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Configurations/Entities/XAiProjectMappingAction.cs b/Configurations/Entities/XAiProjectMappingAction.cs deleted file mode 100644 index a15af2c..0000000 --- a/Configurations/Entities/XAiProjectMappingAction.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using AutoMapper; -using xAiModels.Models.Dtos; -using xAiModels.Models.Entities; -using xIdentityService.Extensions; -using xIdentityService.Interfaces; - -namespace xAiModels.Configurations.Entities -{ - public class XAiProjectMappingAction : IMappingAction - { - private readonly IXIdentityProvider identityProvider; - - public XAiProjectMappingAction( - IXIdentityProvider identityProvider - ) - { - this.identityProvider = identityProvider; - } - - public void Process( - XAiProject source, - XAiProjectDto destination, - ResolutionContext context - ) - { - // - // Retrieve Owner ... - var device = identityProvider.GetDevice(); - var ownerUserInfo = identityProvider.GetUserInfo( - device: device, - userSelectByParam: source.OwnerId - ) - .GetAwaiter() - .GetResult(); - var owner = ownerUserInfo.ToXPersonDto(); - - // - // Setting Owner ... - destination.Owner = owner; - - // - // Retrieve Owner ... - // Retrieve Title Resources ... - // Retrieve Description Resources ... - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/Configurations/XAiModelMappersProfile.cs b/Configurations/XAiModelMappersProfile.cs index 70322a4..27862b7 100644 --- a/Configurations/XAiModelMappersProfile.cs +++ b/Configurations/XAiModelMappersProfile.cs @@ -1,5 +1,4 @@ using AutoMapper; -using xAiModels.Configurations.Entities; using xAiModels.Models.Dtos; using xAiModels.Models.Entities; @@ -11,18 +10,15 @@ namespace xAiModels.Configurations { // // XAiMessage ... - CreateMap() - .AfterMap(); + CreateMap(); // // XAiProject ... - CreateMap() - .AfterMap(); + CreateMap(); // // XAiConversation ... - CreateMap() - .AfterMap(); + CreateMap(); } } } \ No newline at end of file diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs index 6919e24..bd37bd2 100644 --- a/DI/XDIHelperExtension.cs +++ b/DI/XDIHelperExtension.cs @@ -18,6 +18,8 @@ using xAiModels.Interfaces.Entities; using xAiModels.Interfaces; using xAiModels.Providers; using xAiModels.Constants; +using xAiModels.Interfaces.Enrichers; +using xAiModels.Providers.Enrichers; namespace xAiModels.DI { @@ -420,7 +422,6 @@ namespace xAiModels.DI // #region AiMessage ... // - // Models ... services.Add( new ServiceDescriptor(typeof(IXAiMessageServiceProvider), typeof(XAiMessageServiceProvider), lifeTime) ); @@ -435,7 +436,6 @@ namespace xAiModels.DI // #region AiProject ... // - // Models ... services.Add( new ServiceDescriptor(typeof(IXAiProjectServiceProvider), typeof(XAiProjectServiceProvider), lifeTime) ); @@ -459,7 +459,6 @@ namespace xAiModels.DI // #region AiConversation ... // - // Models ... services.Add( new ServiceDescriptor(typeof(IXAiConversationServiceProvider), typeof(XAiConversationServiceProvider), lifeTime) ); @@ -475,6 +474,12 @@ namespace xAiModels.DI services.Add( new ServiceDescriptor(typeof(IXAiConversationTitleResourceProvider), typeof(XAiConversationTitleResourceProvider), lifeTime) ); + + // + // Enrichers ... + services.Add( + new ServiceDescriptor(typeof(IXAiConversationEnricher), typeof(XAiConversationEnricher), lifeTime) + ); #endregion #endregion diff --git a/Interfaces/Enrichers/IXAiConversationEnricher.cs b/Interfaces/Enrichers/IXAiConversationEnricher.cs new file mode 100644 index 0000000..26b5836 --- /dev/null +++ b/Interfaces/Enrichers/IXAiConversationEnricher.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using xAiModels.Models.Dtos; + +namespace xAiModels.Interfaces.Enrichers +{ + public interface IXAiConversationEnricher + { + Task EnrichAsync( + XAiConversationDto source, + XAiConversationDto destination, + CancellationToken cancellationToken = default + ); + + Task> EnrichRangeAsync( + IEnumerable sources, + IEnumerable destinations, + CancellationToken cancellationToken = default + ); + } +} \ No newline at end of file diff --git a/Models/Dtos/XAiProjectDto.cs b/Models/Dtos/XAiProjectDto.cs index 8e0b697..6e14d98 100644 --- a/Models/Dtos/XAiProjectDto.cs +++ b/Models/Dtos/XAiProjectDto.cs @@ -26,11 +26,21 @@ namespace xAiModels.Models.Dtos /// public string Title { get; set; } + /// + /// All Exists Title Locale Resources ... + /// + public virtual XLocaleResourceDto[] TitleLocales { get; set; } + /// /// Description of Project ... /// public string Description { get; set; } + /// + /// All Exists Description Locale Resources ... + /// + public virtual XLocaleResourceDto[] DescriptionLocales { get; set; } + /// /// a Prompt for Project Start ... /// diff --git a/Providers/Dtos/XAiConversationRepositoryService.cs b/Providers/Dtos/XAiConversationRepositoryService.cs index 6b9e78a..face815 100644 --- a/Providers/Dtos/XAiConversationRepositoryService.cs +++ b/Providers/Dtos/XAiConversationRepositoryService.cs @@ -1,23 +1,485 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using AutoMapper; +using Microsoft.EntityFrameworkCore.Query; using xAiModels.Interfaces.Dtos; +using xAiModels.Interfaces.Enrichers; using xAiModels.Interfaces.Entities; using xAiModels.Models.Dtos; using xAiModels.Models.Entities; +using xCommons.Extensions; using xDataService.Providers; +using xModels.Dtos; namespace xAiModels.Providers.Dtos { public class XAiConversationRepositoryService : XBaseRepositoryService, IXAiConversationRepositoryService { + private readonly IXAiConversationEnricher enricher; + public XAiConversationRepositoryService( + IXAiConversationEnricher enricher, IXAiConversationRepository repository, IEnumerable mapperProfiles = null ) : base( repository, mapperProfiles ) - { } + { + this.enricher = enricher; + } + + // + #region Actions ... + // + #region Add ... + /// + /// add a new Dto ... + /// + /// + /// + /// + /// + public override async Task AddAsync( + XAiConversationDto item, + bool saveChanges = 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// add or update a Dto (add if not exists/update if exists) ... + /// + /// + /// + /// + /// + public override async Task AddOrUpdateAsync( + XAiConversationDto item, + bool saveChanges = 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + + // + #region Update ... + /// + /// Update an Dto values ... + /// + /// + /// + /// + /// + /// + public override async Task UpdateAsync( + Guid id, + XAiConversationDto item, + bool saveChanges = 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + + // + #region Remove ... + /// + /// remove an Dto by it's Id ... + /// + /// + /// + /// + /// + /// + public override async Task RemoveAsync( + Guid id, + bool softDelete = true, + bool saveChanges = 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// remove an Dto ... + /// + /// + /// + /// + /// + /// + public override async Task RemoveAsync( + XAiConversationDto item, + bool softDelete = true, + bool saveChanges = 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + + // + #region Retrieve ... + /// + /// retrieve an Dto by it's Id ... + /// + /// + /// + /// + /// + /// + public override async Task GetAsync( + Guid id, + bool ignoreSoftDeleteds = true, + Func, IIncludableQueryable> includeBuilder = null, + 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve all exists Dtos ... + /// + /// + /// + /// + /// + /// + public override async Task> GetAllAsync( + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve all exists Dtos + /// as Async Enumerable ... + /// + /// + /// + /// + /// + /// + public override async IAsyncEnumerable GetAllAsAsyncEnumerable( + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + [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, + cancellationToken: cancellationToken + ); + yield return dto; + } + } + + /// + /// find an Dto by providing a Conditional Expression ... + /// + /// + /// + /// + /// + /// + public override async Task FindOneAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + Func, IIncludableQueryable> includeBuilder = null, + 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// find a collection of Dtos by proving a Conditional Expression ... + /// + /// + /// + /// + /// + /// + /// + public override async Task> FindManyAsync( + Expression> predicate, + bool ignoreSoftDeleteds = true, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + 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, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve Dtos based on XQuery Pagination structure ... + /// + /// + /// + /// + /// + /// + /// + /// + public override async Task> QueryAsync( + XQuery query, + bool ignoreSoftDeleteds = true, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + var result = await base.QueryAsync( + query: query, + predicate: predicate, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken, + ignoreSoftDeleteds: ignoreSoftDeleteds + ); + + // + // Do Enrichment if result Exists ... + if (!result.IsNullOrDefault()) + { + // + await enricher.EnrichRangeAsync( + sources: result.Items, + destinations: result.Items, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + #endregion + #endregion } } \ No newline at end of file diff --git a/Providers/Enrichers/XAiConversationEnricher.cs b/Providers/Enrichers/XAiConversationEnricher.cs new file mode 100644 index 0000000..c3c7f66 --- /dev/null +++ b/Providers/Enrichers/XAiConversationEnricher.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using xAiModels.Interfaces; +using xAiModels.Interfaces.Dtos; +using xAiModels.Interfaces.Enrichers; +using xAiModels.Models.Dtos; +using xAiModels.Models.Entities; +using xCommons.Extensions; +using xIdentityService.Interfaces; +using xIdentityService.Extensions; +using xModels.Dtos; +using System.Linq; + +namespace xAiModels.Providers.Enrichers +{ + public class XAiConversationEnricher : IXAiConversationEnricher + { + private readonly IXIdentityProvider identityProvider; + private readonly IXAiProjectRepositoryService projectRepositoryService; + private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider; + + public XAiConversationEnricher( + IXIdentityProvider identityProvider, + IXAiProjectRepositoryService projectRepositoryService, + IXAiConversationTitleResourceProvider conversationTitleResourceProvider + ) + { + this.identityProvider = identityProvider; + this.projectRepositoryService = projectRepositoryService; + this.conversationTitleResourceProvider = conversationTitleResourceProvider; + } + + /// + /// Converts a Signle Entity to Dto ... + /// + /// + /// + /// + /// + public async Task EnrichAsync( + XAiConversationDto source, + XAiConversationDto destination, + 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); + + // + // wait until All Tasks Resolved ... + await Task.WhenAll(ownerTask, titleLocalesTask, projectTask); + + // + // Attached Resolved Info to Destination ... + destination.Owner = ownerTask.Result; + destination.TitleLocales = titleLocalesTask.Result; + var project = projectTask.Result; + if (!project.IsNullOrDefault()) + { + destination.ProjectTitle = project.Title; + destination.ProjectDescription = project.Description; + destination.ProjectTitleLocales = project.TitleLocales; + destination.ProjectDescriptionLocales = project.DescriptionLocales; + } + } + + /// + /// Converts a Collection of Entities to Dtos ... + /// + /// + /// + /// + /// + public async Task> EnrichRangeAsync( + IEnumerable sources, + IEnumerable destinations, + CancellationToken cancellationToken = default + ) + { + // + // Make Tasks Ready ... + var tasks = sources.Zip(destinations, (src, dst) => + EnrichAsync(src, dst, cancellationToken) + ); + + // + await Task.WhenAll(tasks); + + // + return destinations; + } + + // + #region Private ... + /// + /// Reading Owner User Info ... + /// + /// + /// + /// + private async Task ResolveOwnerAsync( + string ownerId, + CancellationToken cancellationToken = default + ) + { + // + XPersonDto result = null; + + // + // Validate ... + if (ownerId.IsNullOrEmpty()) + { + return result; + } + + // + // Try to Retrieve User Info ... + var device = identityProvider.GetDevice(); + var userInfo = await identityProvider.GetUserInfo( + device: device, + userSelectByParam: ownerId + ); + result = userInfo.ToXPersonDto(); + + // + return result; + } + + /// + /// Reading Title Locales ... + /// + /// + /// + /// + private async Task ResolveTitleLocalesAsync( + string title, + CancellationToken cancellationToken = default + ) + { + // + XLocaleResourceDto[] result = []; + if (title.IsNullOrEmpty()) + { + return result; + } + + // + var locales = await conversationTitleResourceProvider.GetLocales( + identifier: title, + cancellationToken: cancellationToken + ); + result = [.. locales]; + + // + return result; + } + + /// + /// Retrieve Project ... + /// + /// + /// + /// + private async Task ResolveProjectAsync( + Guid projectId, + CancellationToken cancellationToken = default + ) + { + // + XAiProjectDto result = null; + if (projectId.IsNull() || projectId.IsDefaultGuid()) + { + return result; + } + + // + result = await projectRepositoryService.GetAsync( + id: projectId, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + + // + return result; + } + #endregion + } +} \ No newline at end of file