From 5739b968aae8ca5b3891153a0e1fed895e3bee12 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Sat, 5 Sep 2026 10:24:19 +0330 Subject: [PATCH] add requirements in Content Service ... --- .../XContentRepositoryControllerBase.cs | 33 + ...ContentRepositoryProviderControllerBase.cs | 36 + Controllers/XContentServiceControllerBase.cs | 34 + .../XContentServiceProviderControllerBase.cs | 38 + .../XControllerProviderControllerBase.cs | 41 ++ Extensions/XModelExtensions.cs | 46 ++ .../IXContentProviderController.cs | 13 + Interfaces/IXContentProvider.cs | 28 +- Models/Entities/XContent.cs | 2 +- Providers/XContentProvider.cs | 687 +++++++++++++++++- 10 files changed, 953 insertions(+), 5 deletions(-) create mode 100644 Controllers/XContentRepositoryControllerBase.cs create mode 100644 Controllers/XContentRepositoryProviderControllerBase.cs create mode 100644 Controllers/XContentServiceControllerBase.cs create mode 100644 Controllers/XContentServiceProviderControllerBase.cs create mode 100644 Controllers/XControllerProviderControllerBase.cs create mode 100644 Extensions/XModelExtensions.cs create mode 100644 Interfaces/Controllers/IXContentProviderController.cs diff --git a/Controllers/XContentRepositoryControllerBase.cs b/Controllers/XContentRepositoryControllerBase.cs new file mode 100644 index 0000000..41fd8a7 --- /dev/null +++ b/Controllers/XContentRepositoryControllerBase.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using xCommons.Providers; +using xCommons.Configurations; +using xDataService.Interfaces; +using xDataService.Controllers; +using Microsoft.Extensions.Logging; +using xContentService.Models.Entities; +using Microsoft.EntityFrameworkCore.Query; +using xContentService.Interfaces.Entities; + +namespace xContentService.Controllers +{ + public abstract class XContentRepositoryControllerBase : XBaseRepositoryController, IXBaseRepositoryController + { + protected XContentRepositoryControllerBase( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider, + IXContentRepository repository, + Func, IOrderedQueryable> defaultOrderBuilder = null, + Func, IIncludableQueryable> defaultIncludeBuilder = null + ) : base( + logger, + appConfiguration, + validationProvider, + repository, + defaultOrderBuilder, + defaultIncludeBuilder + ) + { } + } +} \ No newline at end of file diff --git a/Controllers/XContentRepositoryProviderControllerBase.cs b/Controllers/XContentRepositoryProviderControllerBase.cs new file mode 100644 index 0000000..20bab65 --- /dev/null +++ b/Controllers/XContentRepositoryProviderControllerBase.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using xCommons.Providers; +using xPushService.Interfaces; +using xCommons.Configurations; +using xPushService.Controllers; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using xContentService.Models.Entities; +using xContentService.Providers.Entities; +using Microsoft.EntityFrameworkCore.Query; +using xContentService.Interfaces.Entities; + +namespace xContentService.Controllers +{ + public abstract class XContentRepositoryProviderControllerBase : XBaseRepositoryProviderController, IXBaseRepositoryProviderController + { + protected XContentRepositoryProviderControllerBase( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider, + IXContentRepositoryProvider provider, + Func, IOrderedQueryable> defaultOrderBuilder = null, Func, IIncludableQueryable> defaultIncludeBuilder = null + ) : base( + logger, + appConfiguration, + identityProvider, + validationProvider, + provider, + defaultOrderBuilder, + defaultIncludeBuilder + ) + { } + } +} \ No newline at end of file diff --git a/Controllers/XContentServiceControllerBase.cs b/Controllers/XContentServiceControllerBase.cs new file mode 100644 index 0000000..bcc866f --- /dev/null +++ b/Controllers/XContentServiceControllerBase.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using xCommons.Providers; +using xCommons.Configurations; +using xDataService.Interfaces; +using xDataService.Controllers; +using xContentService.Models.Dtos; +using Microsoft.Extensions.Logging; +using xContentService.Models.Entities; +using xContentService.Interfaces.Dtos; +using Microsoft.EntityFrameworkCore.Query; + +namespace xContentService.Controllers +{ + public abstract class XContentServiceControllerBase : XBaseServiceController, IXBaseServiceController + { + protected XContentServiceControllerBase( + ILogger logger, + XAppConfiguration appConfiguration, + XValidationProvider validationProvider, + IXContentRepositoryService repositoryService, + Func, IOrderedQueryable> defaultOrderBuilder = null, + Func, IIncludableQueryable> defaultIncludeBuilder = null + ) : base( + logger, + appConfiguration, + validationProvider, + repositoryService, + defaultOrderBuilder, + defaultIncludeBuilder + ) + { } + } +} \ No newline at end of file diff --git a/Controllers/XContentServiceProviderControllerBase.cs b/Controllers/XContentServiceProviderControllerBase.cs new file mode 100644 index 0000000..6319c40 --- /dev/null +++ b/Controllers/XContentServiceProviderControllerBase.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using xCommons.Providers; +using xCommons.Configurations; +using xPushService.Interfaces; +using xPushService.Controllers; +using xContentService.Models.Dtos; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using xContentService.Providers.Dtos; +using xContentService.Models.Entities; +using xContentService.Interfaces.Dtos; +using Microsoft.EntityFrameworkCore.Query; + +namespace xContentService.Controllers +{ + public abstract class XContentServiceProviderControllerBase : XBaseServiceProviderController, IXBaseServiceProviderController + { + protected XContentServiceProviderControllerBase( + ILogger logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider, + IXContentServiceProvider provider, + Func, IOrderedQueryable> defaultOrderBuilder = null, + Func, IIncludableQueryable> defaultIncludeBuilder = null + ) : base( + logger, + appConfiguration, + identityProvider, + validationProvider, + provider, + defaultOrderBuilder, + defaultIncludeBuilder + ) + { } + } +} \ No newline at end of file diff --git a/Controllers/XControllerProviderControllerBase.cs b/Controllers/XControllerProviderControllerBase.cs new file mode 100644 index 0000000..7efaae5 --- /dev/null +++ b/Controllers/XControllerProviderControllerBase.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using xCommons.Providers; +using xTagService.Interfaces; +using xCommons.Configurations; +using xPushService.Interfaces; +using xTagService.Controllers; +using xContentService.Models.Dtos; +using xIdentityService.Interfaces; +using Microsoft.Extensions.Logging; +using xContentService.Providers.Dtos; +using xContentService.Models.Entities; +using Microsoft.EntityFrameworkCore.Query; +using xContentService.Interfaces.Controllers; + +namespace xContentService.Controllers +{ + public abstract class XControllerProviderControllerBase : XBaseProvidedHasTagControllerBase, IXContentProviderController + { + protected XControllerProviderControllerBase( + ILogger> logger, + XAppConfiguration appConfiguration, + IXIdentityProvider identityProvider, + XValidationProvider validationProvider, + IXBaseProvided provider, + IXTagProvided tagProvided, + Func, IOrderedQueryable> defaultOrderBuilder = null, + Func, IIncludableQueryable> defaultIncludeBuilder = null + ) : base( + logger, + appConfiguration, + identityProvider, + validationProvider, + provider, + tagProvided, + defaultOrderBuilder, + defaultIncludeBuilder + ) + { } + } +} \ No newline at end of file diff --git a/Extensions/XModelExtensions.cs b/Extensions/XModelExtensions.cs new file mode 100644 index 0000000..042deb5 --- /dev/null +++ b/Extensions/XModelExtensions.cs @@ -0,0 +1,46 @@ +using xCommons.Extensions; +using xContentService.Models.Dtos; +using xContentService.Models.Entities; + +namespace xContentService.Extensions +{ + /// + /// All Service Model's Extensions ... + /// + public static class XModelExtensions + { + /// + /// Validate a Model ... + /// + /// + /// + public static bool IsValid(this XContent source) + { + // + var result = + !source.IsNullOrDefault() && + !source.Title.IsNullOrEmpty() && + !source.OwnerId.IsNullOrEmpty(); + + // + return result; + } + + /// + /// Validate a Model ... + /// + /// + /// + public static bool IsValid(this XContentDto source) + { + // + var result = + !source.IsNullOrDefault() && + !source.Title.IsNullOrEmpty() && + !source.OwnerId.IsNullOrEmpty(); + + // + return result; + } + } +} \ No newline at end of file diff --git a/Interfaces/Controllers/IXContentProviderController.cs b/Interfaces/Controllers/IXContentProviderController.cs new file mode 100644 index 0000000..3f7dc8d --- /dev/null +++ b/Interfaces/Controllers/IXContentProviderController.cs @@ -0,0 +1,13 @@ +using System; +using xTagService.Interfaces; +using xContentService.Models.Dtos; +using xContentService.Providers.Dtos; +using xContentService.Models.Entities; + +namespace xContentService.Interfaces.Controllers +{ + public interface IXContentProviderController : IXBaseProvidedHasTagController + { + + } +} \ No newline at end of file diff --git a/Interfaces/IXContentProvider.cs b/Interfaces/IXContentProvider.cs index 6d81cdd..69b8538 100644 --- a/Interfaces/IXContentProvider.cs +++ b/Interfaces/IXContentProvider.cs @@ -1,7 +1,31 @@ +using System; +using System.Threading; +using xTagService.Interfaces; +using System.Threading.Tasks; +using xIdentityModels.Models; +using xContentService.Models.Dtos; +using xContentService.Providers.Dtos; +using xContentService.Models.Entities; + namespace xContentService.Interfaces { - public interface IXContentProvider + public interface IXContentProvider : IXBaseProvidedHasTag { - + /// + /// Add an item values ... + /// + /// + /// + /// + /// + /// + /// + public Task Add( + XContentDto item, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + string language = null, + CancellationToken cancellationToken = default + ); } } \ No newline at end of file diff --git a/Models/Entities/XContent.cs b/Models/Entities/XContent.cs index 52cf9b4..36a7e93 100644 --- a/Models/Entities/XContent.cs +++ b/Models/Entities/XContent.cs @@ -1,6 +1,6 @@ using System; -using System.ComponentModel.DataAnnotations; using xModels.Base; +using System.ComponentModel.DataAnnotations; namespace xContentService.Models.Entities { diff --git a/Providers/XContentProvider.cs b/Providers/XContentProvider.cs index 020338c..e896cb6 100644 --- a/Providers/XContentProvider.cs +++ b/Providers/XContentProvider.cs @@ -1,9 +1,692 @@ +using System; +using System.Linq; +using xModels.Dtos; +using System.Threading; +using xCommons.Extensions; +using xTagService.Providers; +using xExceptions.Constants; +using xIdentityModels.Models; +using System.Threading.Tasks; +using xPushService.Constants; +using System.Linq.Expressions; +using xCommons.Configurations; +using xContentService.Constants; using xContentService.Interfaces; +using System.Collections.Generic; +using xContentService.Extensions; +using xContentService.Models.Dtos; +using xContentService.Providers.Dtos; +using xContentService.Models.Entities; +using xContentService.Interfaces.Dtos; +using Microsoft.EntityFrameworkCore.Query; +using xContentService.Interfaces.Enrichers; namespace xContentService.Providers { - public class XContentProvider : IXContentProvider + public class XContentProvider : XBaseProvidedHasTag, IXContentProvider { - + private readonly IXContentEnricher enricher; + private readonly XAppConfiguration configuration; + private readonly IXContentTagProvider tagProvider; + private readonly IXContentServiceProvider provider; + private readonly IXContentTitleResourceProvider titleResourceProvider; + private readonly IXContentDescriptionResourceProvider descriptionResourceProvider; + + public XContentProvider( + IXContentEnricher enricher, + XAppConfiguration configuration, + IXContentTagProvider tagProvider, + IXContentServiceProvider provider, + IXContentTitleResourceProvider titleResourceProvider, + IXContentDescriptionResourceProvider descriptionResourceProvider + ) : base( + provider: provider, + tagProvider: tagProvider, + permittedRoles: [XContentServiceConstants.XAdminRole] + ) + { + this.provider = provider; + this.enricher = enricher; + this.tagProvider = tagProvider; + this.configuration = configuration; + this.titleResourceProvider = titleResourceProvider; + this.descriptionResourceProvider = descriptionResourceProvider; + } + + public override bool IsOwned( + XContent item, + XUserClaimsInfoDto userInfo + ) + { + // + var result = + !item.IsNullOrDefault() && + !userInfo.IsNullOrDefault() && + !item.OwnerId.IsNullOrEmpty() && + userInfo.UserId == item.OwnerId; + + // + return result; + } + + public override bool IsOwned( + XContentDto item, + XUserClaimsInfoDto userInfo + ) + { + // + var result = + !item.IsNullOrDefault() && + !userInfo.IsNullOrDefault() && + !item.OwnerId.IsNullOrEmpty() && + userInfo.UserId == item.OwnerId; + + // + return result; + } + + // + #region Overrides ... + /// + /// Get Specified Item by ID ... + /// + /// + /// + /// + /// + public override async Task Get( + Guid id, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Retrieve Item ... + var result = await provider.GetAsync( + id: id, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + + // + // Handle Enrichment ... + if (!result.IsNullOrDefault()) + { + // + result = await enricher.Enrich( + source: result, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// Get All Exists Items ... + /// + /// + /// + /// + /// + public override async Task> GetAll( + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Retrieve Items ... + var result = await provider.GetAllAsync( + ignoreSoftDeleteds: true, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken + ); + + // + // Handle Enrichment ... + if (result.HasChild()) + { + // + result = await enricher.EnrichRange( + sources: result, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// find an item by providing a Conditional Expression ... + /// + /// + /// + /// + /// + public override async Task FindOne( + Expression> predicate, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Detect Item ... + var result = await provider.FindOneAsync( + predicate: predicate, + ignoreSoftDeleteds: true, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken + ); + + // + // Handle Enrichment ... + if (!result.IsNullOrDefault()) + { + // + result = await enricher.Enrich( + source: result, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// find a collection of Items by proving a Conditional Expression ... + /// + /// + /// + /// + /// + /// + public override async Task> FindMany( + Expression> predicate, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Detect Items ... + var result = await provider.FindManyAsync( + predicate: predicate, + ignoreSoftDeleteds: true, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken + ); + + // + // Handle Enrichment ... + if (result.HasChild()) + { + // + result = await enricher.EnrichRange( + sources: result, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve Items based on XQuery Pagination structure ... + /// + /// + /// + /// + /// + /// + /// + public override async Task> Query( + XQuery query, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Query Items ... + var result = await provider.QueryAsync( + query: query, + predicate: predicate, + ignoreSoftDeleteds: true, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken + ); + + // + // Handle Enrichment ... + if (!result.IsNullOrDefault() && result.Items.HasChild()) + { + // + result.Items = await enricher.EnrichRange( + sources: result.Items, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// retrieve Owned Items based on XQuery Pagination structure ... + /// if supported ... + /// + /// + /// + /// + /// + /// + /// + /// + public override async Task> QueryOwned( + XQuery query, + XUserClaimsInfoDto userInfo = null, + Expression> predicate = null, + Func, IOrderedQueryable> orderBuilder = null, + Func, IIncludableQueryable> includeBuilder = null, + CancellationToken cancellationToken = default + ) + { + // + // Prepare Predicator ... + Expression> predicateor = null; + if (!predicate.IsNull()) + { + // + var predicateFnc = predicate.Compile(); + predicateor = x => + predicateFnc(x) && + IsOwned(x, userInfo); + } + else + { + predicateor = x => IsOwned(x, userInfo); + } + + // + // Query Items ... + var result = await provider.QueryAsync( + query: query, + predicate: predicateor, + ignoreSoftDeleteds: true, + orderBuilder: orderBuilder, + includeBuilder: includeBuilder, + cancellationToken: cancellationToken + ); + + // + // Handle Enrichment ... + if (!result.IsNullOrDefault() && result.Items.HasChild()) + { + // + result.Items = await enricher.EnrichRange( + sources: result.Items, + cancellationToken: cancellationToken + ); + } + + // + return result; + } + + /// + /// Add an item values ... + /// + /// + /// + /// + /// + /// + public override async Task Add( + XContentDto item, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + var result = await Add( + item: item, + language: null, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Add an item values ... + /// + /// + /// + /// + /// + /// + /// + public async Task Add( + XContentDto item, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + string language = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = !item.IsNullOrDefault(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Normalize ... + + // + // Language ... + if (language.IsNullOrEmpty()) + { + language = configuration.DefaultLanguage; + } + + // + // Owner Id ... + if (item.OwnerId.IsNullOrEmpty()) + { + // + if (!userInfo.IsNullOrDefault()) + { + item.OwnerId = userInfo.UserId; + } + } + + // + // Created On ... + if (item.CreatedOn.IsNull()) + { + item.CreatedOn = DateTime.UtcNow; + } + + // + // Validate Normalized Items ... + isValid = item.IsValid(); + if (!isValid) + { + XException.InvalidData.Throw(); + } + + // + // Handle Insert Temp ... + var result = await provider.AddAsync( + item: item, + saveChanges: true, + connectionId: null, // Ignor Push Notifications ..., + cancellationToken: cancellationToken + ); + isValid = !result.IsNullOrDefault(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + // Handle Insert Title/Description ... + + // + var titleResource = await titleResourceProvider.AddOrUpdateLocale( + connectionId: null, + identifier: result.Id, + locale: new XLocaleResourceDto + { + Language = language, + Value = result.Title, + }, + cancellationToken: cancellationToken + ); + + // + XResourceDto descriptionResource = null; + if (!result.Description.IsNullOrEmpty()) + { + // + descriptionResource = await descriptionResourceProvider.AddOrUpdateLocale( + connectionId: null, + identifier: result.Id, + locale: new XLocaleResourceDto + { + Language = language, + Value = result.Description, + }, + cancellationToken: cancellationToken + ); + } + + // + // Fill Item Title and Description ... + if (titleResource.IsNullOrDefault()) + { + result.Title = titleResource.Resource; + } + if (descriptionResource.IsNullOrDefault()) + { + result.Description = descriptionResource.Resource; + } + + // + // Update Item ... + result = await Update( + item: result, + id: result.Id, + userInfo: userInfo, + connectionId: null, + cancellationToken: cancellationToken + ); + isValid = !result.IsNullOrDefault(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + // Handle Enrichment ... + if (isValid) + { + // + result = await enricher.Enrich( + source: result, + cancellationToken: cancellationToken + ); + } + + // + // Handle Push Notifications ... + if (isValid && !connectionId.IsNullOrEmpty()) + { + // + await provider.SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: result.ToJSON(camelCase: true), + action: XBaseEntityHubAction.Add.GetStringValue() + ); + } + + // + return result; + } + + /// + /// Update an item values ... + /// + /// + /// + /// + /// + /// + /// + public override async Task Update( + Guid id, + XContentDto item, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Check Permission ... + var hasPermission = + IsOwned(item, userInfo) || + HasPermision(userInfo); + if (!hasPermission) + { + XException.NotAllowed.Throw(); + } + + // + // Handle Resources ... + if (!item.TitleResource.IsNullOrDefault()) + { + item.TitleResource = await titleResourceProvider.AddOrUpdateResource( + identifier: item.Id, + connectionId: connectionId, + resource: item.TitleResource, + cancellationToken: cancellationToken + ); + } + + // + var result = await provider.UpdateAsync( + id: id, + item: item, + saveChanges: true, + connectionId: null, + cancellationToken: cancellationToken + ); + var isValid = !result.IsNullOrDefault(); + + // + // Handle Enrichment ... + if (isValid) + { + // + result = await enricher.Enrich( + source: result, + cancellationToken: cancellationToken + ); + } + + // + // Handle Notification ... + if (isValid && !connectionId.IsNullOrEmpty()) + { + // + await provider.SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: result.ToJSON(camelCase: true), + action: XBaseEntityHubAction.Update.GetStringValue() + ); + } + + // + return result; + } + + /// + /// remove an Item ... + /// + /// + /// + /// + /// + /// + public override async Task Remove( + Guid id, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Check Permission ... + var item = await Get( + id: id, + cancellationToken: cancellationToken + ); + var hasPermission = + IsOwned(item, userInfo) || + HasPermision(userInfo); + if (!hasPermission) + { + XException.NotAllowed.Throw(); + } + + // + // Handle Item Remove ... + var result = await provider.RemoveAsync( + id: id, + saveChanges: true, + softDelete: false, + connectionId: null, + cancellationToken: cancellationToken + ); + if (result.IsNullOrDefault()) + { + XException.ActionFailed.Throw(); + } + + // + // Remove Titles/Descriptions ... + + // + // Titles ... + await titleResourceProvider.RemoveLocales( + connectionId: null, + identifier: result.Id, + cancellationToken: cancellationToken + ); + + // + // Descriptions ... + await descriptionResourceProvider.RemoveLocales( + connectionId: null, + identifier: result.Id, + cancellationToken: cancellationToken + ); + + // + // Handle Notifications ... + if (!connectionId.IsNullOrEmpty()) + { + // + await provider.SendPush( + connectionId: connectionId, + cancellationToken: cancellationToken, + payLoad: result.ToJSON(camelCase: true), + action: XBaseEntityHubAction.Delete.GetStringValue() + ); + } + + // + return result; + } + #endregion } } \ No newline at end of file