From f1dbbc8f1d931bcaed4f0ca509c74bf38013ed37 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Fri, 12 Jun 2026 02:18:44 +0330 Subject: [PATCH] Update Lang Version to 12 ... Fix and Complete XCategory and XSubCategory Services and Service Implementations ... --- Interfaces/IXCategoryProvider.cs | 76 +- Interfaces/IXSubCategoryProvider.cs | 141 +++- Providers/Dtos/XSubCategoryResourceDto.cs | 13 + Providers/XCategoryProvider.cs | 513 +++++++++++- Providers/XSubCategoryProvider.cs | 925 +++++++++++++++++++++- xCategoryService.csproj | 6 +- 6 files changed, 1649 insertions(+), 25 deletions(-) create mode 100644 Providers/Dtos/XSubCategoryResourceDto.cs diff --git a/Interfaces/IXCategoryProvider.cs b/Interfaces/IXCategoryProvider.cs index 6d61a12..d7658fc 100644 --- a/Interfaces/IXCategoryProvider.cs +++ b/Interfaces/IXCategoryProvider.cs @@ -1,15 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using xCategoryService.Models.Dtos; using xCategoryService.Models.Entities; using xCategoryService.Providers.Dtos; using xIdentityModels.Models; +using xModels.Dtos; using xPushService.Interfaces; namespace xCategoryService.Interfaces { public interface IXCategoryProvider : IXBaseProvided { + /// + /// Check Has Specified Locale or not ... + /// + /// + /// + /// + /// + Task HasLocale( + int id, + string language, + CancellationToken cancellationToken = default + ); + /// /// Load an Item by all of it's Related Translations ... /// @@ -65,11 +83,67 @@ namespace xCategoryService.Interfaces /// /// /// - Task RefereshResource( + Task Referesh( XCategoryResourceDto resource, string connectionId = null, XUserClaimsInfoDto userInfo = null, CancellationToken cancellationToken = default ); + + /// + /// Get All Resources ... + /// + /// + /// + Task> GetAllResources( + CancellationToken cancellationToken = default + ); + + /// + /// Find One Resource ... + /// + /// + /// + /// + Task FindOne( + Expression> predicate = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find Many Resource ... + /// + /// + /// + /// + Task> FindMany( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Resources by Query Pattern ... + /// + /// + /// + /// + /// + /// + Task> QueryResources( + XQuery query = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Get All Resources as Async Enumerable ... + /// + /// + /// + IAsyncEnumerable GetAllResourcesAsAsyncEnumerable( + CancellationToken cancellationToken = default + ); } } \ No newline at end of file diff --git a/Interfaces/IXSubCategoryProvider.cs b/Interfaces/IXSubCategoryProvider.cs index 21206ba..efe8ef6 100644 --- a/Interfaces/IXSubCategoryProvider.cs +++ b/Interfaces/IXSubCategoryProvider.cs @@ -1,10 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; using xCategoryService.Models.Dtos; using xCategoryService.Models.Entities; using xCategoryService.Providers.Dtos; +using xIdentityModels.Models; +using xModels.Dtos; using xPushService.Interfaces; namespace xCategoryService.Interfaces { public interface IXSubCategoryProvider : IXBaseProvided - { } + { + /// + /// Check Has Specified Locale or not ... + /// + /// + /// + /// + /// + Task HasLocale( + int id, + string language, + CancellationToken cancellationToken = default + ); + + /// + /// Load an Item by all of it's Related Translations ... + /// + /// + /// + /// + Task GetResource( + int id, + CancellationToken cancellationToken = default + ); + + /// + /// Add Category Item for Specified Language ... + /// + /// + /// + /// + /// + /// + /// + Task Add( + XSubCategoryDto item, + string langauge = null, + string connectionId = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Update a Category Item for Specified Language ... + /// + /// + /// + /// + /// + /// + /// + /// + Task Update( + int id, + XSubCategoryDto item, + string language = null, + string connectionId = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Referesh a Resource ... + /// + /// + /// + /// + /// + /// + Task Referesh( + XSubCategoryResourceDto resource, + string connectionId = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ); + + /// + /// Get All Resources ... + /// + /// + /// + Task> GetAllResources( + CancellationToken cancellationToken = default + ); + + /// + /// Find One Resource ... + /// + /// + /// + /// + Task FindOne( + Expression> predicate = null, + CancellationToken cancellationToken = default + ); + + /// + /// Find Many Resource ... + /// + /// + /// + /// + Task> FindMany( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Resources by Query Pattern ... + /// + /// + /// + /// + /// + /// + Task> QueryResources( + XQuery query = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ); + + /// + /// Get All Resources as Async Enumerable ... + /// + /// + /// + IAsyncEnumerable GetAllResourcesAsAsyncEnumerable( + CancellationToken cancellationToken = default + ); + } } \ No newline at end of file diff --git a/Providers/Dtos/XSubCategoryResourceDto.cs b/Providers/Dtos/XSubCategoryResourceDto.cs new file mode 100644 index 0000000..2ce230c --- /dev/null +++ b/Providers/Dtos/XSubCategoryResourceDto.cs @@ -0,0 +1,13 @@ +using xCategoryService.Models.Dtos; +using xModels.Base; +using xModels.Dtos; + +namespace xCategoryService.Providers.Dtos +{ + public class XSubCategoryResourceDto : XBaseDto + { + public XSubCategoryDto Item { get; set; } + public XResourceDto Titles { get; set; } + public XResourceDto Descriptions { get; set; } + } +} \ No newline at end of file diff --git a/Providers/XCategoryProvider.cs b/Providers/XCategoryProvider.cs index c2d14e5..eae1695 100644 --- a/Providers/XCategoryProvider.cs +++ b/Providers/XCategoryProvider.cs @@ -1,4 +1,8 @@ +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 xCategoryService.Interfaces; @@ -11,8 +15,10 @@ using xCommons.Extensions; using xExceptions.Constants; using xIdentityModels.Models; using xModels.Dtos; +using xDataService.Extensions; using xPushService.Constants; using xPushService.Providers; +using xDataService.Configuration; namespace xCategoryService.Providers { @@ -20,12 +26,14 @@ namespace xCategoryService.Providers { private readonly IXCategoryServiceProvider provider; private readonly XAppConfiguration appConfiguration; + private readonly XDataServiceConfiguration dataServiceConfiguration; private readonly IXCategoryTitleResourceProvider titleResourceProvider; private readonly IXCategoryDescriptionResourceProvider descriptionResourceProvider; public XCategoryProvider( XAppConfiguration appConfiguration, IXCategoryServiceProvider provider, + XDataServiceConfiguration dataServiceConfiguration, IXCategoryTitleResourceProvider titleResourceProvider, IXCategoryDescriptionResourceProvider descriptionResourceProvider ) : base( @@ -37,9 +45,12 @@ namespace xCategoryService.Providers this.provider = provider; this.appConfiguration = appConfiguration; this.titleResourceProvider = titleResourceProvider; + this.dataServiceConfiguration = dataServiceConfiguration; this.descriptionResourceProvider = descriptionResourceProvider; } + // + #region Overrides ... public override bool IsOwned( XCategoryDto item, XUserClaimsInfoDto userInfo @@ -55,9 +66,12 @@ namespace xCategoryService.Providers { return !item.IsNullOrDefault(); } + #endregion + // + #region Actions ... /// - /// Check a Category Has Specified Locale or not ... + /// Check Has Specified Locale or not ... /// /// /// @@ -91,9 +105,6 @@ namespace xCategoryService.Providers var result = !resource.IsNullOrDefault() && resource.Titles.Locales - .Any(x => x.Language - .Equals(language, System.StringComparison.InvariantCultureIgnoreCase)) && - resource.Descriptions.Locales .Any(x => x.Language .Equals(language, System.StringComparison.InvariantCultureIgnoreCase)); @@ -113,7 +124,7 @@ namespace xCategoryService.Providers ) { // - var category = await Get( + var item = await Get( id: id, includeBuilder: null, cancellationToken: cancellationToken @@ -130,7 +141,7 @@ namespace xCategoryService.Providers // var result = new XCategoryResourceDto { - Item = category, + Item = item, Titles = titleResource, Descriptions = descriptionResource }; @@ -140,7 +151,7 @@ namespace xCategoryService.Providers } /// - /// Add Category Item for Specified Language ... + /// Add an Item for Specified Language ... /// /// /// @@ -171,6 +182,14 @@ namespace xCategoryService.Providers XException.InvalidArgs.Throw(); } + // + // Check Permission ... + isValid = HasPermision(userInfo); + if (!isValid) + { + XException.NotAllowed.Throw(); + } + // var id = -1; try @@ -271,7 +290,7 @@ namespace xCategoryService.Providers } /// - /// Update a Category Item for Specified Language ... + /// Update an Item for Specified Language ... /// /// /// @@ -337,10 +356,10 @@ namespace xCategoryService.Providers isValid = !dto.IsNullOrDefault() && !userInfo.IsNullOrDefault() && - IsOwned( + (IsOwned( item: dto, userInfo: userInfo - ); + ) || HasPermision(userInfo)); if (!isValid) { XException.NotAllowed.Throw(); @@ -364,7 +383,7 @@ namespace xCategoryService.Providers .ForEach(tl => { // - if (tl.Language.Equals(language, System.StringComparison.InvariantCultureIgnoreCase)) + if (tl.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase)) { // titleUpdated = true; @@ -379,7 +398,7 @@ namespace xCategoryService.Providers .ForEach(dl => { // - if (dl.Language.Equals(language, System.StringComparison.InvariantCultureIgnoreCase)) + if (dl.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase)) { // descriptionUpdated = true; @@ -396,7 +415,7 @@ namespace xCategoryService.Providers } // - resource = await RefereshResource( + resource = await Referesh( resource: resource, userInfo: userInfo, connectionId: connectionId, @@ -415,7 +434,7 @@ namespace xCategoryService.Providers /// /// /// - public async Task RefereshResource( + public async Task Referesh( XCategoryResourceDto resource, string connectionId = null, XUserClaimsInfoDto userInfo = null, @@ -425,20 +444,29 @@ namespace xCategoryService.Providers // // Validate Args ... var isValid = - !resource.IsNull() && - !resource.Item.IsNullOrDefault(); + !resource.IsNull(); if (!isValid) { XException.InvalidArgs.Throw(); } + // + // Check Permission ... + isValid = HasPermision(userInfo); + if (!isValid) + { + XException.NotAllowed.Throw(); + } + // var id = -1; + XCategoryResourceDto result = null; // // Check Item Exists ... XCategoryDto dto = - !resource.Item.Id.IsValidIntId() + (resource.Item.IsNullOrDefault() || + !resource.Item.Id.IsValidIntId()) ? null : await Get( id: resource.Item.Id, @@ -451,20 +479,215 @@ namespace xCategoryService.Providers // Do Referesh Based on Existance ... var isNew = !isExists && - !resource.Item.Id.IsValidIntId(); + (resource.Item.IsNullOrDefault() || + !resource.Item.Id.IsValidIntId()); if (isNew) { // // Add ... + // In this Senario, for Add a Category by Providing Resource + // the item must clean, either Title and Description, and tge Locales Provided by Resource + // ust Contains Values ... + // the Add Starts by Titles and only Items Add Which has Titles, since Title is Mandatory + // and Description is Optional ... + isValid = + // + // Resource ... + (resource.Item.IsNullOrDefault() || + (!resource.Item.Id.IsValidIntId() && + resource.Item.Title.IsNullOrEmpty() && + resource.Item.Description.IsNullOrEmpty())) && + // + // Titles ... + !resource.Titles.IsNullOrDefault() && + resource.Titles.Locales.HasChild(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Add Temp Category ... + var unique = Guid.NewGuid(); + var temp = new XCategoryDto + { + Title = $"TmpT_{unique}", + Description = $"TmpD_{unique}", + }; + temp = await provider.AddAsync( + item: temp, + saveChanges: true, + connectionId: null, + cancellationToken: cancellationToken + ); + isValid = + !temp.IsNullOrDefault() && + temp.Id.IsValidIntId(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + id = temp.Id; + + // + // Update Title and Description ... + var titleResource = titleResourceProvider.GetResourceTitle(id); + var descriptionResource = descriptionResourceProvider.GetResourceTitle(id); + temp.Title = titleResource; + temp.Description = descriptionResource; + temp = await provider.UpdateAsync( + id: id, + item: temp, + saveChanges: true, + connectionId: null, + cancellationToken: cancellationToken + ); + + // + // Loop through Titles ... + foreach (var locale in resource.Titles.Locales) + { + // + // Add Or Update Locale ... + await titleResourceProvider.AddOrUpdateLocale( + locale: locale, + identifier: id, + connectionId: null, + cancellationToken: cancellationToken + ); + var desLocale = resource.Descriptions.Locales.FirstOrDefault( + dl => + dl.Language == locale.Language); + if (!desLocale.IsNullOrDefault()) + { + // + // Add Descriptions ... + await descriptionResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + } } else if (isExists) { // // Re State ... id = resource.Item.Id; + + // + // Try to Refresh Resource State ... + + // + #region Remove ... + // + // Titles ... + var removeTitles = result.Titles.Locales.Where( + l => !resource.Titles.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in removeTitles) + { + // + await titleResourceProvider.RemoveLocale( + identifier: id, + connectionId: null, + language: locale.Language, + cancellationToken: cancellationToken + ); + } + + // + // Descriptions ... + var removeDescriptions = result.Descriptions.Locales.Where( + l => !resource.Descriptions.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in removeDescriptions) + { + // + await descriptionResourceProvider.RemoveLocale( + identifier: id, + connectionId: null, + language: locale.Language, + cancellationToken: cancellationToken + ); + } + #endregion + + // + #region Update ... + // + // Titles ... + var updateTitles = result.Titles.Locales.Where( + l => resource.Titles.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in updateTitles) + { + // + await titleResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + + // + // Descriptions ... + var updateDescriptions = result.Descriptions.Locales.Where( + l => resource.Descriptions.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in updateDescriptions) + { + // + await descriptionResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + #endregion + + // + #region New ... + // + // Titles ... + var newTitles = resource.Titles.Locales.Where( + l => !result.Titles.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in newTitles) + { + // + await titleResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + + // + // Descriptions ... + var newDescriptions = resource.Descriptions.Locales.Where( + l => !result.Descriptions.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in newDescriptions) + { + // + await descriptionResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + #endregion } // + // Validate Action ... isValid = id.IsValidIntId(); if (!isValid) { @@ -473,13 +696,265 @@ namespace xCategoryService.Providers // // Get Result ... - var result = await GetResource( + result = await GetResource( id: id, cancellationToken: cancellationToken ); + isValid = !result.IsNullOrDefault(); + + // + // Send Notification ... + if (isValid) + { + // + await provider.SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: result.ToJSON(camelCase: true), + action: isNew + ? XBaseEntityHubAction.Add.GetStringValue() + : XBaseEntityHubAction.Update.GetStringValue() + ); + } // return result; } + + /// + /// Get All Resources as Async Enumerable ... + /// + /// + /// + public async IAsyncEnumerable GetAllResourcesAsAsyncEnumerable( + [EnumeratorCancellation] CancellationToken cancellationToken = default + ) + { + // + var enumerable = provider.GetAllAsAsyncEnumerable( + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.Id) + ); + + // + await foreach (var dto in enumerable) + { + // + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + var resource = await GetResource( + id: dto.Id, + cancellationToken: cancellationToken + ); + yield return resource; + } + } + + /// + /// Get All Resources ... + /// + /// + /// + public async Task> GetAllResources( + CancellationToken cancellationToken = default + ) + { + // + var result = new List(); + + // + var enumerable = GetAllResourcesAsAsyncEnumerable(cancellationToken); + await foreach (var item in enumerable) + { + // + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // + result.Add(item); + } + + // + return result; + } + + /// + /// Find One Resource ... + /// + /// + /// + /// + public async Task FindOne( + Expression> predicate = null, + CancellationToken cancellationToken = default + ) + { + // + var isApproved = false; + XCategoryResourceDto result = null; + var enumerable = GetAllResourcesAsAsyncEnumerable(cancellationToken); + await foreach (var item in enumerable) + { + // + // Check Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // + // Detect Predicate Condition ... + isApproved = predicate.Compile()(item); + if (isApproved) + { + // + result = item; + break; + } + } + + // + return result; + } + + /// + /// Find Many Resource ... + /// + /// + /// + /// + public async Task> FindMany( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + var isApproved = false; + var result = new List(); + var enumerable = GetAllResourcesAsAsyncEnumerable(cancellationToken); + await foreach (var item in enumerable) + { + // + // Check Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // + // Detect Predicate Condition ... + isApproved = predicate.Compile()(item); + if (isApproved) + { + result.Add(item); + } + } + + // + // Apply Order ... + if (!orderBuilder.IsNull()) + { + // + result = + orderBuilder(result.AsQueryable()) + .ToList(); + } + + // + return result; + } + + /// + /// Retrieve Resources by Query Pattern ... + /// + /// + /// + /// + /// + /// + public async Task> QueryResources( + XQuery query = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Normalize Query ... + query = query.NormalizeQuery(dataServiceConfiguration); + + // + var items = await FindMany( + predicate: predicate, + orderBuilder: orderBuilder, + cancellationToken: cancellationToken + ); + var totalItemsCount = items.Count(); + + // + // Apply Filter ... + if (!query.Filter.IsNullOrEmpty()) + { + // + items = items + .ApplyFilter(query.Filter); + } + int filteredItemsCount = items.Count(); + + // + // Count Pages ... + var totalPagesCount = query.CountPages(totalItemsCount); + var filteredPagesCount = query.CountPages(filteredItemsCount); + + // + // Apply Paging and Sorting ... + if (totalItemsCount > 0 && + filteredItemsCount > 0) + { + // + // Apply Sorting ... + items = items + .ToList() + .ApplySorting( + query.SortBy, + query.IsAscending + ); + + // + // Apply Paging ... + items = items + .ToList() + .ApplyPaging( + query.Page, + query.PageSize + ); + } + + // + // Prepare Result ... + var result = new XQueryResult + { + Page = query.Page, + Items = [.. items], + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + #endregion } } \ No newline at end of file diff --git a/Providers/XSubCategoryProvider.cs b/Providers/XSubCategoryProvider.cs index 6d11dcf..51a3fb5 100644 --- a/Providers/XSubCategoryProvider.cs +++ b/Providers/XSubCategoryProvider.cs @@ -1,24 +1,55 @@ +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 xCategoryService.Interfaces; using xCategoryService.Interfaces.Dtos; using xCategoryService.Models.Dtos; using xCategoryService.Models.Entities; using xCategoryService.Providers.Dtos; +using xCommons.Configurations; using xCommons.Extensions; +using xDataService.Configuration; +using xExceptions.Constants; using xIdentityModels.Models; +using xModels.Dtos; +using xDataService.Extensions; +using xPushService.Constants; using xPushService.Providers; namespace xCategoryService.Providers { public class XSubCategoryProvider : XBaseProvided, IXSubCategoryProvider { + private readonly XAppConfiguration appConfiguration; + private readonly IXSubCategoryServiceProvider provider; + private readonly XDataServiceConfiguration dataServiceConfiguration; + private readonly IXSubCategoryTitleResourceProvider titleResourceProvider; + private readonly IXSubCategoryDescriptionResourceProvider descriptionResourceProvider; + public XSubCategoryProvider( - IXSubCategoryServiceProvider provider + XAppConfiguration appConfiguration, + IXSubCategoryServiceProvider provider, + XDataServiceConfiguration dataServiceConfiguration, + IXSubCategoryTitleResourceProvider titleResourceProvider, + IXSubCategoryDescriptionResourceProvider descriptionResourceProvider ) : base( provider, permittedRoles: new string[] { } ) - { } + { + this.appConfiguration = appConfiguration; + this.provider = provider; + this.dataServiceConfiguration = dataServiceConfiguration; + this.titleResourceProvider = titleResourceProvider; + this.descriptionResourceProvider = descriptionResourceProvider; + } + // + #region Overrides ... public override bool IsOwned( XSubCategoryDto item, XUserClaimsInfoDto userInfo @@ -34,5 +65,895 @@ namespace xCategoryService.Providers { return !item.IsNullOrDefault(); } + #endregion + + // + #region Actions ... + /// + /// Check Has Specified Locale or not ... + /// + /// + /// + /// + /// + public async Task HasLocale( + int id, + string language, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = + id.IsValidIntId() && + !language.IsNullOrEmpty(); + if (isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Resource ... + var resource = await GetResource( + id: id, + cancellationToken: cancellationToken + ); + + // + // Check Result ... + var result = + !resource.IsNullOrDefault() && + resource.Titles.Locales + .Any(x => x.Language + .Equals(language, System.StringComparison.InvariantCultureIgnoreCase)); + + // + return result; + } + + /// + /// Load an Item by all of it's Related Translations ... + /// + /// + /// + /// + public async Task GetResource( + int id, + CancellationToken cancellationToken = default + ) + { + // + var item = await Get( + id: id, + includeBuilder: null, + cancellationToken: cancellationToken + ); + var titleResource = await titleResourceProvider.GetResource( + identifier: id, + cancellationToken: cancellationToken + ); + var descriptionResource = await titleResourceProvider.GetResource( + identifier: id, + cancellationToken: cancellationToken + ); + + // + var result = new XSubCategoryResourceDto + { + Item = item, + Titles = titleResource, + Descriptions = descriptionResource + }; + + // + return result; + } + + /// + /// Add an Item for Specified Language ... + /// + /// + /// + /// + /// + /// + /// + public async Task Add( + XSubCategoryDto item, + string langauge = null, + string connectionId = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Normalize ... + langauge = + !langauge.IsNullOrEmpty() + ? langauge + : appConfiguration.DefaultLanguage; + + // + // Validate ... + var isValid = !item.IsNullOrDefault(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Permission ... + isValid = HasPermision(userInfo); + if (!isValid) + { + XException.NotAllowed.Throw(); + } + + // + var id = -1; + try + { + // + var title = item.Title; + var description = item.Description; + + // + // Add Item Pure for ID Generating ... + item = await base.Add( + item: item, + userInfo: userInfo, + connectionId: null, // Since here We dont want notification ... + cancellationToken: cancellationToken + ); + isValid = !item.IsNullOrDefault(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + id = item.Id; + var titleResource = await titleResourceProvider + .AddOrUpdateLocale( + identifier: id, + locale: new XLocaleResourceDto + { + Value = title, + Language = langauge + }, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + var descriptionResource = await descriptionResourceProvider + .AddOrUpdateLocale( + identifier: id, + locale: new XLocaleResourceDto + { + Value = description, + Language = langauge + }, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + // Update Category Dto ... + item.Title = titleResource.Resource; + item.Description = descriptionResource.Resource; + item = await base.Update( + id: id, + item: item, + userInfo: userInfo, + connectionId: null, + cancellationToken: cancellationToken + ); + } + catch + { + XException.InvalidArgs.Throw(); + } + + // + // Validate Id ... + isValid = id.IsValidIntId(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + // Retrieve Category Resourv=ce ... + var result = await GetResource( + id: id, + cancellationToken: cancellationToken + ); + + // + // Sending Push Notification ... + isValid = + isValid && + !result.IsNullOrDefault(); + if (isValid) + { + // + await provider.SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: result.ToJSON(camelCase: true), + action: XBaseEntityHubAction.Add.GetStringValue() + ); + } + + // + return result; + } + + /// + /// Update an Item for Specified Language ... + /// + /// + /// + /// + /// + /// + /// + /// + public async Task Update( + int id, + XSubCategoryDto item, + string language = null, + string connectionId = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = + id.IsValidIntId() && + !item.IsNullOrDefault() && + !language.IsNullOrEmpty() && + !item.Title.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Category Exists ... + isValid = await IsExists( + id: id, + cancellationToken: cancellationToken + ); + if (!isValid) + { + XException.NotFound.Throw(); + } + + // + // Check Locale Exists ... + isValid = await HasLocale( + id: id, + language: language, + cancellationToken: cancellationToken + ); + if (!isValid) + { + XException.NotFound.Throw(); + } + + // + // Retrieve Category Dto for Validate Owining ... + var dto = await Get( + id: id, + includeBuilder: null, + cancellationToken: cancellationToken + ); + + // + // Check Permission ... + isValid = + !dto.IsNullOrDefault() && + !userInfo.IsNullOrDefault() && + (IsOwned( + item: dto, + userInfo: userInfo + ) || HasPermision(userInfo)); + if (!isValid) + { + XException.NotAllowed.Throw(); + } + + // + // Retrieve Exists Item Resource ... + var resource = await GetResource( + id: id, + cancellationToken: cancellationToken + ); + + // + var titleUpdated = false; + var descriptionUpdated = false; + + // + // Update Title ... + resource.Titles.Locales + .ToList() + .ForEach(tl => + { + // + if (tl.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase)) + { + // + titleUpdated = true; + tl.Value = item.Title; + } + }); + + // + // Update Description ... + resource.Descriptions.Locales + .ToList() + .ForEach(dl => + { + // + if (dl.Language.Equals(language, StringComparison.InvariantCultureIgnoreCase)) + { + // + descriptionUpdated = true; + dl.Value = item.Description; + } + }); + + // + // Validate Title and Description Updated ... + isValid = titleUpdated && descriptionUpdated; + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + resource = await Referesh( + resource: resource, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + return resource; + } + + /// + /// Referesh a Resource ... + /// + /// + /// + /// + /// + /// + public async Task Referesh( + XSubCategoryResourceDto resource, + string connectionId = null, + XUserClaimsInfoDto userInfo = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = + !resource.IsNull(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Permission ... + isValid = HasPermision(userInfo); + if (!isValid) + { + XException.NotAllowed.Throw(); + } + + // + var id = -1; + XSubCategoryResourceDto result = null; + + // + // Check Item Exists ... + XSubCategoryDto dto = + (resource.Item.IsNullOrDefault() || + !resource.Item.Id.IsValidIntId()) + ? null + : await Get( + id: resource.Item.Id, + includeBuilder: null, + cancellationToken: cancellationToken + ); + var isExists = !dto.IsNullOrDefault(); + + // + // Do Referesh Based on Existance ... + var isNew = + !isExists && + (resource.Item.IsNullOrDefault() || + !resource.Item.Id.IsValidIntId()); + if (isNew) + { + // + // Add ... + // In this Senario, for Add a Category by Providing Resource + // the item must clean, either Title and Description, and tge Locales Provided by Resource + // ust Contains Values ... + // the Add Starts by Titles and only Items Add Which has Titles, since Title is Mandatory + // and Description is Optional ... + isValid = + // + // Resource ... + (resource.Item.IsNullOrDefault() || + (!resource.Item.Id.IsValidIntId() && + resource.Item.Title.IsNullOrEmpty() && + resource.Item.Description.IsNullOrEmpty())) && + // + // Titles ... + !resource.Titles.IsNullOrDefault() && + resource.Titles.Locales.HasChild(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Add Temp Category ... + var unique = Guid.NewGuid(); + var temp = new XSubCategoryDto + { + Title = $"TmpT_{unique}", + Description = $"TmpD_{unique}", + }; + temp = await provider.AddAsync( + item: temp, + saveChanges: true, + connectionId: null, + cancellationToken: cancellationToken + ); + isValid = + !temp.IsNullOrDefault() && + temp.Id.IsValidIntId(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + id = temp.Id; + + // + // Update Title and Description ... + var titleResource = titleResourceProvider.GetResourceTitle(id); + var descriptionResource = descriptionResourceProvider.GetResourceTitle(id); + temp.Title = titleResource; + temp.Description = descriptionResource; + temp = await provider.UpdateAsync( + id: id, + item: temp, + saveChanges: true, + connectionId: null, + cancellationToken: cancellationToken + ); + + // + // Loop through Titles ... + foreach (var locale in resource.Titles.Locales) + { + // + // Add Or Update Locale ... + await titleResourceProvider.AddOrUpdateLocale( + locale: locale, + identifier: id, + connectionId: null, + cancellationToken: cancellationToken + ); + var desLocale = resource.Descriptions.Locales.FirstOrDefault( + dl => + dl.Language == locale.Language); + if (!desLocale.IsNullOrDefault()) + { + // + // Add Descriptions ... + await descriptionResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + } + } + else if (isExists) + { + // + // Re State ... + id = resource.Item.Id; + + // + // Try to Refresh Resource State ... + + // + #region Remove ... + // + // Titles ... + var removeTitles = result.Titles.Locales.Where( + l => !resource.Titles.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in removeTitles) + { + // + await titleResourceProvider.RemoveLocale( + identifier: id, + connectionId: null, + language: locale.Language, + cancellationToken: cancellationToken + ); + } + + // + // Descriptions ... + var removeDescriptions = result.Descriptions.Locales.Where( + l => !resource.Descriptions.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in removeDescriptions) + { + // + await descriptionResourceProvider.RemoveLocale( + identifier: id, + connectionId: null, + language: locale.Language, + cancellationToken: cancellationToken + ); + } + #endregion + + // + #region Update ... + // + // Titles ... + var updateTitles = result.Titles.Locales.Where( + l => resource.Titles.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in updateTitles) + { + // + await titleResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + + // + // Descriptions ... + var updateDescriptions = result.Descriptions.Locales.Where( + l => resource.Descriptions.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in updateDescriptions) + { + // + await descriptionResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + #endregion + + // + #region New ... + // + // Titles ... + var newTitles = resource.Titles.Locales.Where( + l => !result.Titles.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in newTitles) + { + // + await titleResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + + // + // Descriptions ... + var newDescriptions = resource.Descriptions.Locales.Where( + l => !result.Descriptions.Locales.Any(r => r.Language.Equals(l.Language, System.StringComparison.InvariantCultureIgnoreCase)) + ); + foreach (var locale in newDescriptions) + { + // + await descriptionResourceProvider.AddOrUpdateLocale( + identifier: id, + locale: locale, + connectionId: null, + cancellationToken: cancellationToken + ); + } + #endregion + } + + // + // Validate Action ... + isValid = id.IsValidIntId(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + // Get Result ... + result = await GetResource( + id: id, + cancellationToken: cancellationToken + ); + isValid = !result.IsNullOrDefault(); + + // + // Send Notification ... + if (isValid) + { + // + await provider.SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: result.ToJSON(camelCase: true), + action: isNew + ? XBaseEntityHubAction.Add.GetStringValue() + : XBaseEntityHubAction.Update.GetStringValue() + ); + } + + // + return result; + } + + /// + /// Get All Resources as Async Enumerable ... + /// + /// + /// + public async IAsyncEnumerable GetAllResourcesAsAsyncEnumerable( + [EnumeratorCancellation] CancellationToken cancellationToken = default + ) + { + // + var enumerable = provider.GetAllAsAsyncEnumerable( + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.Id) + ); + + // + await foreach (var dto in enumerable) + { + // + if (cancellationToken.IsCancellationRequested) + { + yield break; + } + + // + var resource = await GetResource( + id: dto.Id, + cancellationToken: cancellationToken + ); + yield return resource; + } + } + + /// + /// Get All Resources ... + /// + /// + /// + public async Task> GetAllResources( + CancellationToken cancellationToken = default + ) + { + // + var result = new List(); + + // + var enumerable = GetAllResourcesAsAsyncEnumerable(cancellationToken); + await foreach (var item in enumerable) + { + // + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // + result.Add(item); + } + + // + return result; + } + + /// + /// Find One Resource ... + /// + /// + /// + /// + public async Task FindOne( + Expression> predicate = null, + CancellationToken cancellationToken = default + ) + { + // + var isApproved = false; + XSubCategoryResourceDto result = null; + var enumerable = GetAllResourcesAsAsyncEnumerable(cancellationToken); + await foreach (var item in enumerable) + { + // + // Check Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // + // Detect Predicate Condition ... + isApproved = predicate.Compile()(item); + if (isApproved) + { + // + result = item; + break; + } + } + + // + return result; + } + + /// + /// Find Many Resource ... + /// + /// + /// + /// + public async Task> FindMany( + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + var isApproved = false; + var result = new List(); + var enumerable = GetAllResourcesAsAsyncEnumerable(cancellationToken); + await foreach (var item in enumerable) + { + // + // Check Cancellation Token ... + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // + // Detect Predicate Condition ... + isApproved = predicate.Compile()(item); + if (isApproved) + { + result.Add(item); + } + } + + // + // Apply Order ... + if (!orderBuilder.IsNull()) + { + // + result = + orderBuilder(result.AsQueryable()) + .ToList(); + } + + // + return result; + } + + /// + /// Retrieve Resources by Query Pattern ... + /// + /// + /// + /// + /// + /// + public async Task> QueryResources( + XQuery query = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Normalize Query ... + query = query.NormalizeQuery(dataServiceConfiguration); + + // + var items = await FindMany( + predicate: predicate, + orderBuilder: orderBuilder, + cancellationToken: cancellationToken + ); + var totalItemsCount = items.Count(); + + // + // Apply Filter ... + if (!query.Filter.IsNullOrEmpty()) + { + // + items = items + .ApplyFilter(query.Filter); + } + int filteredItemsCount = items.Count(); + + // + // Count Pages ... + var totalPagesCount = query.CountPages(totalItemsCount); + var filteredPagesCount = query.CountPages(filteredItemsCount); + + // + // Apply Paging and Sorting ... + if (totalItemsCount > 0 && + filteredItemsCount > 0) + { + // + // Apply Sorting ... + items = items + .ToList() + .ApplySorting( + query.SortBy, + query.IsAscending + ); + + // + // Apply Paging ... + items = items + .ToList() + .ApplyPaging( + query.Page, + query.PageSize + ); + } + + // + // Prepare Result ... + var result = new XQueryResult + { + Page = query.Page, + Items = [.. items], + PageSize = query.PageSize, + TotalPages = totalPagesCount, + TotalItems = totalItemsCount, + TotalFilteredPages = filteredPagesCount, + TotalFilteredItems = filteredItemsCount + }; + + // + return result; + } + #endregion } } \ No newline at end of file diff --git a/xCategoryService.csproj b/xCategoryService.csproj index a3d8dc1..d2fdf07 100644 --- a/xCategoryService.csproj +++ b/xCategoryService.csproj @@ -2,11 +2,13 @@ - netstandard2.0 - xDashboard.xCategoryService 1.0.0 + 12.0 Hadi Khazaee Asl SaherElm IT Center + netstandard2.0 + xDashboard.xCategoryService + it is a Part of xDashboard on SaherElm IT Center which provides Category Futures ...