diff --git a/Extensions/XModelExtensions.cs b/Extensions/XModelExtensions.cs new file mode 100644 index 0000000..17b00bf --- /dev/null +++ b/Extensions/XModelExtensions.cs @@ -0,0 +1,149 @@ +using System.Collections.Generic; +using System.Linq; +using xCategoryService.Models.Dtos; +using xCategoryService.Models.Entities; +using xCommons.Extensions; +using xDataService.Extensions; +using xDataService.Models; + +namespace xCategoryService.Extensions +{ + public static class XModelExtensions + { + /// + /// Extract References of a Model ... + /// + /// + /// + public static IList> GetReferences( + this XCategory source + ) + { + // + var result = new List>(); + + // + if ( + !source.IsNullOrDefault() && + !source.References.IsNullOrEmpty() + ) + { + // + result = source.References + .ParseXReferenceList() + .ToList(); + } + + // + return result; + } + + /// + /// Extract References of a Model ... + /// + /// + /// + public static IList> GetReferences( + this XCategoryDto source + ) + { + // + var result = new List>(); + + // + if ( + !source.IsNullOrDefault() && + !source.References.IsNullOrEmpty() + ) + { + // + result = source.References + .ParseXReferenceList() + .ToList(); + } + + // + return result; + } + + /// + /// Update a Model's References by Providing References List ... + /// + /// + /// + /// + /// + public static XCategory UpdateReferences( + this XCategory source, + IList> references, + bool forceClean = true + ) + { + // + XCategory result = source; + + // + if (!source.IsNullOrDefault()) + { + // + result = source; + + // + if (forceClean) + { + result.References = string.Empty; + } + + // + if (!references.IsNull() && + references.HasChild()) + { + result.References = references.ToXReferenceString(); + } + } + + // + return result; + } + + /// + /// Update a Model's References by Providing References List ... + /// + /// + /// + /// + /// + public static XCategoryDto UpdateReferences( + this XCategoryDto source, + IList> references, + bool forceClean = true + ) + { + // + XCategoryDto result = source; + + // + if (!source.IsNullOrDefault()) + { + // + result = source; + + // + if (forceClean) + { + result.References = string.Empty; + } + + // + if (!references.IsNull() && + references.HasChild()) + { + result.References = references.ToXReferenceString(); + } + } + + // + return result; + } + } +} \ No newline at end of file diff --git a/Interfaces/IXBaseCategoryProvider.cs b/Interfaces/IXBaseCategoryProvider.cs new file mode 100644 index 0000000..5fc9460 --- /dev/null +++ b/Interfaces/IXBaseCategoryProvider.cs @@ -0,0 +1,8 @@ +using xCategoryService.Models.Dtos; +using xDataService.Interfaces; + +namespace xCategoryService.Interfaces +{ + public interface IXBaseCategoryProvider : IXBaseReferencedProvider + {} +} \ No newline at end of file diff --git a/Providers/XBaseCategoryProvider.cs b/Providers/XBaseCategoryProvider.cs new file mode 100644 index 0000000..d75b648 --- /dev/null +++ b/Providers/XBaseCategoryProvider.cs @@ -0,0 +1,710 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using xCategoryService.Extensions; +using xCategoryService.Interfaces; +using xCategoryService.Interfaces.Dtos; +using xCategoryService.Models.Dtos; +using xCommons.Extensions; +using xDataService.Extensions; +using xDataService.Models; +using xExceptions.Constants; +using xModels.Dtos; + +namespace xCategoryService.Providers +{ + public abstract class XBaseCategoryProvider : IXBaseCategoryProvider + { + // + #region Props ... + /// + /// Provider Identifier ... + /// + public string Provider { get; } + + /// + /// Tag Provider for Maipulating Tags ... + /// + public IXCategoryServiceProvider CategoryProvider { get; } + #endregion + + // + #region Constructor ... + public XBaseCategoryProvider( + string providedFor, + IXCategoryServiceProvider categoryProvider + ) + { + // + Provider = providedFor; + CategoryProvider = categoryProvider; + } + #endregion + + // + #region Referenced Actions ... + /// + /// Get Specified Indexed Reference for Specific Provided ID ... + /// + /// + /// + /// + /// + public async Task GetReference( + TKey providedForId, + int forIndex = 0, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (providedForId.IsNull()) + { + XException.InvalidArgs.Throw(); + } + + // + // Normalize ... + if (forIndex < 0) + { + forIndex = 0; + } + + // + // Retrieve Last Index ... + var maxIndex = (await GetReferencingIndexes( + providedForId: providedForId, + cancellationToken: cancellationToken + )) + .Max(); + if (forIndex > maxIndex) + { + XException.NotFound.Throw(); + } + + // + var indexedIdentifier = GetIndexedProvidedID( + forIndex: forIndex, + providedForId: providedForId + ); + var result = await CategoryProvider.FindOneAsync( + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + predicate: x => x.References.Contains(indexedIdentifier) + ); + if (result.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + return result; + } + + /// + /// Retrieve all Exists References of Specific Provided ID ... + /// + /// + /// + /// + public async Task> GetAllReferences( + TKey providedForId, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (providedForId.IsNull()) + { + XException.InvalidArgs.Throw(); + } + + // + var identifier = GetProvidedID(providedForId); + var result = await CategoryProvider.FindManyAsync( + orderBuilder: null, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + predicate: x => x.References.Contains(identifier) + ); + + // + return result; + } + + /// + /// Extract all Referencing Models for Specified Key ... + /// + /// + /// + /// + public async Task>> GetAllReferencings( + TKey providedForId, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = + !providedForId.IsNull(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + var identifier = GetProvidedID(providedForId); + + // + var result = (await CategoryProvider.FindManyAsync( + orderBuilder: null, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + predicate: x => x.References.Contains(identifier) + )) + .Select(x => x.References) + .ToList() + .SelectMany(x => x.ParseXReferenceList()) + .Where(x => x.ProvidedFor == Provider && $"{x.ReferencedTo}" == $"{providedForId}") + .OrderBy(x => x.Index) + .AsEnumerable(); + + // + return result; + } + + /// + /// Count References ... + /// + /// + /// + /// + public async Task CountReferences( + TKey providedForId, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = !providedForId.IsNull(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + var identifier = GetProvidedID(providedForId); + var result = (await CategoryProvider.FindManyAsync( + orderBuilder: null, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + predicate: x => x.References.Contains(identifier) + )) + .Count(); + + // + return result; + } + + /// + /// Retrieve References of Specific Provided ID as Query ... + /// + /// + /// + /// + /// + public async Task> QueryReferences( + XQuery query, + TKey providedForId, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (query.IsNull() || providedForId.IsNull()) + { + XException.InvalidArgs.Throw(); + } + + // + var identifier = GetProvidedID(providedForId); + var result = await CategoryProvider.QueryAsync( + query: query, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken, + orderBuilder: x => x.OrderBy(y => y.Id), + predicate: x => x.References.Contains(identifier) + ); + + // + return result; + } + + /// + /// Add Reference a Model to Specific Provided ID ... + /// + /// + /// + /// + /// + /// + public async Task AddReference( + XCategoryDto model, + TKey providedForId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + var result = false; + + // + // Validate ... + result = !model.IsNullOrDefault() && + model.Id.IsValidIntId() && + !providedForId.IsNull(); + if (!result) + { + XException.InvalidArgs.Throw(); + } + + // + // Check Model Exists ... + result = await CategoryProvider.IsExistsAsync(model.Id); + if (!result) + { + XException.NotFound.Throw(); + } + + // + // Update Model by Retrieving ... + model = await CategoryProvider.GetAsync( + id: model.Id, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + + // + // Retrieve all References ... + var references = await GetAllReferences( + providedForId: providedForId, + cancellationToken: cancellationToken + ); + + // + // Check isReferenced or not ... + result = references + .Any(r => + r.Id == model.Id && + r.GetReferences() + .Any(rf => + rf.ProvidedFor == Provider && + rf.ReferencedTo == $"{providedForId}")); + if (result) + { + return result; + } + + // + // Here means there is not any reference to ProvidedForId ... + int forIndex = await CountReferences( + providedForId: providedForId, + cancellationToken: cancellationToken + ); + var modelReferences = model.GetReferences(); + modelReferences.Add(new XReference + { + Index = forIndex, + ProvidedFor = Provider, + ReferencedTo = $"{providedForId}" + }); + modelReferences = modelReferences + .OrderBy(r => r.Index) + .ToList(); + model = model.UpdateReferences(modelReferences); + + // + // Update Data Base ... + model = await CategoryProvider.UpdateAsync( + id: model.Id, + item: model, + saveChanges: true, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + result = !model.IsNullOrDefault(); + + // + return result; + } + + /// + /// Add Reference a Model to Specific XRefence ... + /// + /// + /// + /// + /// + /// + public async Task AddReference( + XCategoryDto model, + XReference reference, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + // Since other Validations Handled in Calling, we Ignore them here ... + var result = + !model.IsNull() && + !reference.IsNullOrDefault() && + reference.ProvidedFor == Provider && + !reference.ReferencedTo.IsNull(); + if (!result) + { + XException.InvalidArgs.Throw(); + } + + // + result = await AddReference( + model: model, + connectionId: connectionId, + cancellationToken: cancellationToken, + providedForId: reference.ReferencedTo + ); + + // + return result; + } + + /// + /// Remove Reference a Model for Specific Provided ID ... + /// + /// + /// + /// + /// + /// + public async Task RemoveReference( + XCategoryDto model, + TKey providedForId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var result = + !providedForId.IsNull() && + !model.IsNullOrDefault() && + model.Id.IsValidIntId(); + if (!result) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Model ... + model = await CategoryProvider.GetAsync( + id: model.Id, + includeBuilder: null, + ignoreSoftDeleteds: true, + cancellationToken: cancellationToken + ); + result = !model.IsNullOrDefault(); + if (!result) + { + XException.NotFound.Throw(); + } + + // + // Reading Referencing Indexes ... + var indexes = await GetReferencingIndexes( + providedForId: providedForId, + cancellationToken: cancellationToken + ); + + // + // Check Model Reference to ID ... + var referencesCount = await CountReferences( + providedForId: providedForId, + cancellationToken: cancellationToken + ); + result = referencesCount == 0; + if (result) + { + // + // There is not any Reference to Remove ... + return result; + } + + // + // Check Model is Trully Referenced to ProvidedForId or not ... + var reference = model + .GetReferences() + .FirstOrDefault(r => + r.ProvidedFor == Provider && + r.ReferencedTo == $"{providedForId}"); + result = !reference.IsNullOrDefault(); + if (!result) + { + XException.ActionFailed.Throw(); + } + + // + // Remove Referenced Item from Model ... + var modelReferences = model + .GetReferences() + .Where(r => + !(r.ProvidedFor == Provider && + r.ReferencedTo == $"{providedForId}")) + .ToList(); + model = model.UpdateReferences(modelReferences); + + // + // Update DataBase ... + model = await CategoryProvider.UpdateAsync( + item: model, + id: model.Id, + saveChanges: true, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + result = !model.IsNullOrDefault(); + if (!result) + { + XException.ActionFailed.Throw(); + } + + // + // Get Max Exists Indexes ... + var maxIndex = indexes.Max(); + + // + // Here We Have to Re Arrange Indexed ... + if (reference.Index < maxIndex) + { + // + var startIndex = reference.Index + 1; + for (int i = startIndex; i <= maxIndex; i++) + { + // + var dto = await GetReference( + forIndex: i, + providedForId: providedForId, + cancellationToken: cancellationToken + ); + if (!dto.IsNullOrDefault()) + { + // + var dtoReferences = dto.GetReferences(); + dtoReferences + .ToList() + .ForEach(iref => + { + // + if ( + iref.Index == i && + iref.ProvidedFor == Provider && + iref.ReferencedTo == reference.ReferencedTo + ) + { + iref.Index = i - 1; + } + }); + dto = dto.UpdateReferences(dtoReferences); + + // + dto = await CategoryProvider.UpdateAsync( + item: dto, + id: dto.Id, + saveChanges: true, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + } + } + } + + // + return result; + } + + /// + /// Remove Reference a Model for Specific XRefence ... + /// + /// + /// + /// + /// + /// + public async Task RemoveReference( + XCategoryDto model, + XReference reference, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + if (model.IsNullOrDefault() || + reference.IsNullOrDefault() || + reference.ReferencedTo.IsNull() || + reference.ProvidedFor != Provider) + { + XException.InvalidArgs.Throw(); + } + + // + var result = await RemoveReference( + model: model, + connectionId: connectionId, + cancellationToken: cancellationToken, + providedForId: reference.ReferencedTo + ); + + // + return result; + } + + /// + /// Remove All References to Specified Key ... + /// + /// + /// + /// + /// + public async Task RemoveReferences( + TKey providedForId, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (providedForId.IsNull()) + { + XException.InvalidArgs.Throw(); + } + + // + var result = false; + var references = await GetAllReferences( + providedForId: providedForId, + cancellationToken: cancellationToken + ); + if (!references.IsNull() && references.HasChild()) + { + // + foreach (var reference in references) + { + // + // Remove Reference ... + var itemReferences = reference.GetReferences() + .Where(r => + r.ProvidedFor != Provider || + (r.ProvidedFor == Provider && + r.ReferencedTo != $"{providedForId}")) + .ToList(); + var updatedModel = reference.UpdateReferences(itemReferences); + updatedModel = await CategoryProvider.UpdateAsync( + saveChanges: true, + item: updatedModel, + id: updatedModel.Id, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + if (!updatedModel.IsNullOrDefault()) + { + result = true; + } + } + } + + // + return result; + } + #endregion + + // + #region Private ... + private string GetProvidedID( + TKey providedForId, + char splitter = '_' + ) + { + // + // Validate ... + if (providedForId.IsNull()) + { + XException.InvalidArgs.Throw(); + } + + // + var result = $"{Provider}{splitter}{providedForId}"; + return result; + } + + private string GetIndexedProvidedID( + TKey providedForId, + int forIndex, + char splitter = '_' + ) + { + // + var result = $"{GetProvidedID(providedForId, splitter)}{splitter}{forIndex}"; + + // + return result; + } + + private async Task> GetReferencingIndexes( + TKey providedForId, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + var isValid = + !providedForId.IsNull(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + var result = new List(); + var referencings = await GetAllReferencings( + providedForId: providedForId, + cancellationToken: cancellationToken + ); + isValid = + !referencings.IsNull() && + referencings.HasChild(); + if (!isValid) + { + return result; + } + + // + result = + referencings + .OrderBy(r => r.Index) + .Select(r => r.Index) + .ToList(); + + // + return result; + } + #endregion + } +} \ No newline at end of file