diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ea588b..a72377c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,16 @@ name: "CI: Build and Test" on: + push: + branches: [main] + paths: + - "**.cs" + - "**.tsx" + - "**.js" + - "**.csproj" + - "**.props" + - "**.targets" + - "**.sln" pull_request: branches: [main] paths: @@ -15,9 +25,20 @@ on: - "**.sln" jobs: + dotnet-format: + name: dotnet-format + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Run dotnet format + run: dotnet format --exclude ./examples/** --verify-no-changes + build_and_test: name: Build and Test runs-on: ubuntu-latest + needs: dotnet-format defaults: run: shell: pwsh @@ -29,10 +50,10 @@ jobs: DOTNET_NOLOGO: 1 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: global-json-file: global.json diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemAddParams.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemAddParams.cs index 40407da..df71987 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemAddParams.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemAddParams.cs @@ -1,13 +1,13 @@ -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Interface for synchronized content items. -/// -public class ContentItemAddParams -{ - public required ContentItemSynchronizationBase ContentItem { get; set; } - - public required string LanguageName { get; set; } - - public int UserID { get; set; } -} +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Interface for synchronized content items. +/// +public class ContentItemAddParams +{ + public required ContentItemSynchronizationBase ContentItem { get; set; } + + public required string LanguageName { get; set; } + + public int UserID { get; set; } +} diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemServiceBase.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemServiceBase.cs index 8b849b9..4beba1d 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemServiceBase.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemServiceBase.cs @@ -1,130 +1,130 @@ -using CMS.ContentEngine; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Service for content items management -/// -public class ContentItemServiceBase : IContentItemService -{ - protected readonly IContentItemManagerFactory ContentItemManagerFactory; - - protected readonly IContentQueryExecutor ContentQueryExecutor; - - protected readonly IContentQueryResultMapper ContentQueryResultMapper; - - - public ContentItemServiceBase( - IContentItemManagerFactory contentItemManagerFactory, - IContentQueryExecutor contentQueryExecutor, - IContentQueryResultMapper contentQueryResultMapper) - { - ContentItemManagerFactory = contentItemManagerFactory; - ContentQueryExecutor = contentQueryExecutor; - ContentQueryResultMapper = contentQueryResultMapper; - } - - - /// - public async Task AddContentItem(ContentItemAddParams addParams) - { - ArgumentNullException.ThrowIfNull(addParams); - - var createParams = new CreateContentItemParameters( - addParams.ContentItem.ContentTypeName, - addParams.ContentItem.GenerateCodeName(), - addParams.ContentItem.DisplayName, - addParams.LanguageName); - var itemData = new ContentItemData(addParams.ContentItem.ToDict()); - - var contentItemManager = ContentItemManagerFactory.Create(addParams.UserID); - int itemID = await contentItemManager.Create(createParams, itemData); - await contentItemManager.TryPublish(itemID, addParams.LanguageName); - - return itemID; - } - - - /// - public async Task UpdateContentItem(ContentItemUpdateParams updateParams) - { - var contentItemManager = ContentItemManagerFactory.Create(updateParams.UserID); - var versionStatus = updateParams.VersionStatus; - - // If content item version is not draft, create draft in order to edit it - if (versionStatus != VersionStatus.Draft && - versionStatus != VersionStatus.InitialDraft && - !await contentItemManager.TryCreateDraft(updateParams.ContentItemID, updateParams.LanguageName)) - { - return false; - } - - var itemData = new ContentItemData(updateParams.ContentItemParams); - if (!await contentItemManager.TryUpdateDraft(updateParams.ContentItemID, updateParams.LanguageName, itemData)) - { - return false; - } - - return versionStatus switch - { - VersionStatus.Published => await contentItemManager.TryPublish(updateParams.ContentItemID, updateParams.LanguageName), - VersionStatus.Archived => await contentItemManager.TryArchive(updateParams.ContentItemID, updateParams.LanguageName), - VersionStatus.InitialDraft => true, - VersionStatus.Draft => true, - _ => true, - }; - } - - - /// - public async Task> GetContentItems(string contentType, Action queryParams) - where T : IContentItemFieldsSource, new() - { - var builder = new ContentItemQueryBuilder() - .ForContentType(contentType, queryParams); - - return await ContentQueryExecutor.GetResult(builder, ContentQueryResultMapper.Map); - } - - - /// - public async Task> GetContentItems(string contentType) - where T : IContentItemFieldsSource, new() => await GetContentItems(contentType, 0); - - - /// - public async Task> GetContentItems(string contentType, int linkedItemsLevel) - where T : IContentItemFieldsSource, new() - { - var builder = new ContentItemQueryBuilder() - .ForContentType(contentType, config => config.WithLinkedItems(linkedItemsLevel)); - - return await ContentQueryExecutor.GetResult(builder, ContentQueryResultMapper.Map); - } - - - /// - public async Task DeleteContentItem(int contentItemID, string languageName, int userID) - { - var contentItemManager = ContentItemManagerFactory.Create(userID); - await contentItemManager.Delete(contentItemID, languageName); - } - - - /// - public async Task DeleteContentItems(IEnumerable contentItemIDs, string languageName, int userID) - { - if (!contentItemIDs.Any()) - { - return; - } - - var contentItemManager = ContentItemManagerFactory.Create(userID); - - foreach (int contentItemID in contentItemIDs) - { - await contentItemManager.Delete(contentItemID, languageName); - } - } -} - +using CMS.ContentEngine; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Service for content items management +/// +public class ContentItemServiceBase : IContentItemService +{ + protected readonly IContentItemManagerFactory ContentItemManagerFactory; + + protected readonly IContentQueryExecutor ContentQueryExecutor; + + protected readonly IContentQueryResultMapper ContentQueryResultMapper; + + + public ContentItemServiceBase( + IContentItemManagerFactory contentItemManagerFactory, + IContentQueryExecutor contentQueryExecutor, + IContentQueryResultMapper contentQueryResultMapper) + { + ContentItemManagerFactory = contentItemManagerFactory; + ContentQueryExecutor = contentQueryExecutor; + ContentQueryResultMapper = contentQueryResultMapper; + } + + + /// + public async Task AddContentItem(ContentItemAddParams addParams) + { + ArgumentNullException.ThrowIfNull(addParams); + + var createParams = new CreateContentItemParameters( + addParams.ContentItem.ContentTypeName, + addParams.ContentItem.GenerateCodeName(), + addParams.ContentItem.DisplayName, + addParams.LanguageName); + var itemData = new ContentItemData(addParams.ContentItem.ToDict()); + + var contentItemManager = ContentItemManagerFactory.Create(addParams.UserID); + int itemID = await contentItemManager.Create(createParams, itemData); + await contentItemManager.TryPublish(itemID, addParams.LanguageName); + + return itemID; + } + + + /// + public async Task UpdateContentItem(ContentItemUpdateParams updateParams) + { + var contentItemManager = ContentItemManagerFactory.Create(updateParams.UserID); + var versionStatus = updateParams.VersionStatus; + + // If content item version is not draft, create draft in order to edit it + if (versionStatus != VersionStatus.Draft && + versionStatus != VersionStatus.InitialDraft && + !await contentItemManager.TryCreateDraft(updateParams.ContentItemID, updateParams.LanguageName)) + { + return false; + } + + var itemData = new ContentItemData(updateParams.ContentItemParams); + if (!await contentItemManager.TryUpdateDraft(updateParams.ContentItemID, updateParams.LanguageName, itemData)) + { + return false; + } + + return versionStatus switch + { + VersionStatus.Published => await contentItemManager.TryPublish(updateParams.ContentItemID, updateParams.LanguageName), + VersionStatus.Archived => await contentItemManager.TryArchive(updateParams.ContentItemID, updateParams.LanguageName), + VersionStatus.InitialDraft => true, + VersionStatus.Draft => true, + _ => true, + }; + } + + + /// + public async Task> GetContentItems(string contentType, Action queryParams) + where T : IContentItemFieldsSource, new() + { + var builder = new ContentItemQueryBuilder() + .ForContentType(contentType, queryParams); + + return await ContentQueryExecutor.GetResult(builder, ContentQueryResultMapper.Map); + } + + + /// + public async Task> GetContentItems(string contentType) + where T : IContentItemFieldsSource, new() => await GetContentItems(contentType, 0); + + + /// + public async Task> GetContentItems(string contentType, int linkedItemsLevel) + where T : IContentItemFieldsSource, new() + { + var builder = new ContentItemQueryBuilder() + .ForContentType(contentType, config => config.WithLinkedItems(linkedItemsLevel)); + + return await ContentQueryExecutor.GetResult(builder, ContentQueryResultMapper.Map); + } + + + /// + public async Task DeleteContentItem(int contentItemID, string languageName, int userID) + { + var contentItemManager = ContentItemManagerFactory.Create(userID); + await contentItemManager.Delete(contentItemID, languageName); + } + + + /// + public async Task DeleteContentItems(IEnumerable contentItemIDs, string languageName, int userID) + { + if (!contentItemIDs.Any()) + { + return; + } + + var contentItemManager = ContentItemManagerFactory.Create(userID); + + foreach (int contentItemID in contentItemIDs) + { + await contentItemManager.Delete(contentItemID, languageName); + } + } +} + diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemSynchronizationBase.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemSynchronizationBase.cs index 8fd2c1d..36b704b 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemSynchronizationBase.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemSynchronizationBase.cs @@ -1,123 +1,123 @@ -using System.Reflection; -using System.Text; - -using CMS.ContentEngine; -using CMS.Helpers; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Synchronization base for content items. -/// -public abstract class ContentItemSynchronizationBase -{ - private const string CODE_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"; - private const int CODE_LENGTH = 8; - private const int NAME_LENGTH = 100; - - protected abstract string DisplayNameInternal { get; } - - - public abstract string ContentTypeName { get; } - - - /// - /// Content item display name with max length of . - /// - public string DisplayName => - DisplayNameInternal.Length > NAME_LENGTH ? (DisplayNameInternal?[..NAME_LENGTH] ?? string.Empty) : DisplayNameInternal; - - - /// - /// Generate dictionary where keys are property names and values are property values. - /// properties are excluded. - /// - /// Dictionary where key = property name and value = property value. - public virtual Dictionary ToDict() - { - var baseProperties = typeof(ContentItemSynchronizationBase) - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name); - - var type = GetType(); - - // Get only properties that are not from IContentItemBase - var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy) - .Where(p => !baseProperties.Contains(p.Name)); - - Dictionary result = []; - - foreach (var property in properties) - { - string propertyName = property.Name; - object? propertyValue = property.GetValue(this); - - result.Add(propertyName, propertyValue); - } - - return result; - } - - -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved - /// - /// Get content item code name or generate if does not exist. - /// Generated code name consists of that is transformed to code name using - /// and random 8 characters long alphanumeric string. Maximum length will be - /// - /// - /// Generated code name - /// -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved - public string GenerateCodeName() - { - var random = Random.Shared; - - string? codeName = ValidationHelper.GetCodeName(DisplayName); - - if (codeName.Length + CODE_LENGTH >= NAME_LENGTH) - { - codeName = codeName[..(NAME_LENGTH - CODE_LENGTH - 1)]; - } - - var sb = new StringBuilder(codeName); - sb.Append('-'); - for (int i = 0; i < CODE_LENGTH; i++) - { - sb.Append(CODE_CHARS[random.Next(CODE_CHARS.Length)]); - } - - return sb.ToString(); - } - - - protected bool ReferenceModified(IEnumerable contentItems, IEnumerable contentItemReferences) - { - if (contentItems.Count() != contentItemReferences.Count()) - { - return true; - } - - for (int i = 0; i < contentItemReferences.Count(); i++) - { - var referenceObject = contentItemReferences.ElementAtOrDefault(i); - var contentItem = contentItems.ElementAtOrDefault(i); - - if (referenceObject?.Identifier != contentItem?.SystemFields?.ContentItemGUID) - { - return true; - } - } - - return false; - } - - - protected void SetPropsIfDiff(T source, T dest, string key, Dictionary props) - { - if ((source is null && dest is not null) || (source is not null && !source.Equals(dest))) - { - props.TryAdd(key, dest); - } - } -} +using System.Reflection; +using System.Text; + +using CMS.ContentEngine; +using CMS.Helpers; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Synchronization base for content items. +/// +public abstract class ContentItemSynchronizationBase +{ + private const string CODE_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"; + private const int CODE_LENGTH = 8; + private const int NAME_LENGTH = 100; + + protected abstract string DisplayNameInternal { get; } + + + public abstract string ContentTypeName { get; } + + + /// + /// Content item display name with max length of . + /// + public string DisplayName => + DisplayNameInternal.Length > NAME_LENGTH ? (DisplayNameInternal?[..NAME_LENGTH] ?? string.Empty) : DisplayNameInternal; + + + /// + /// Generate dictionary where keys are property names and values are property values. + /// properties are excluded. + /// + /// Dictionary where key = property name and value = property value. + public virtual Dictionary ToDict() + { + var baseProperties = typeof(ContentItemSynchronizationBase) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(p => p.Name); + + var type = GetType(); + + // Get only properties that are not from IContentItemBase + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy) + .Where(p => !baseProperties.Contains(p.Name)); + + Dictionary result = []; + + foreach (var property in properties) + { + string propertyName = property.Name; + object? propertyValue = property.GetValue(this); + + result.Add(propertyName, propertyValue); + } + + return result; + } + + +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved + /// + /// Get content item code name or generate if does not exist. + /// Generated code name consists of that is transformed to code name using + /// and random 8 characters long alphanumeric string. Maximum length will be + /// + /// + /// Generated code name + /// +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved + public string GenerateCodeName() + { + var random = Random.Shared; + + string? codeName = ValidationHelper.GetCodeName(DisplayName); + + if (codeName.Length + CODE_LENGTH >= NAME_LENGTH) + { + codeName = codeName[..(NAME_LENGTH - CODE_LENGTH - 1)]; + } + + var sb = new StringBuilder(codeName); + sb.Append('-'); + for (int i = 0; i < CODE_LENGTH; i++) + { + sb.Append(CODE_CHARS[random.Next(CODE_CHARS.Length)]); + } + + return sb.ToString(); + } + + + protected bool ReferenceModified(IEnumerable contentItems, IEnumerable contentItemReferences) + { + if (contentItems.Count() != contentItemReferences.Count()) + { + return true; + } + + for (int i = 0; i < contentItemReferences.Count(); i++) + { + var referenceObject = contentItemReferences.ElementAtOrDefault(i); + var contentItem = contentItems.ElementAtOrDefault(i); + + if (referenceObject?.Identifier != contentItem?.SystemFields?.ContentItemGUID) + { + return true; + } + } + + return false; + } + + + protected void SetPropsIfDiff(T source, T dest, string key, Dictionary props) + { + if ((source is null && dest is not null) || (source is not null && !source.Equals(dest))) + { + props.TryAdd(key, dest); + } + } +} diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemUpdateParams.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemUpdateParams.cs index 2e94f52..cb5b66e 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemUpdateParams.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ContentItemUpdateParams.cs @@ -1,19 +1,19 @@ -using CMS.ContentEngine; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Parameters for content item update in synchronization. -/// -public class ContentItemUpdateParams -{ - public required Dictionary ContentItemParams { get; set; } - - public required int ContentItemID { get; set; } - - public required string LanguageName { get; set; } - - public required int UserID { get; set; } - - public required VersionStatus VersionStatus { get; set; } -} +using CMS.ContentEngine; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Parameters for content item update in synchronization. +/// +public class ContentItemUpdateParams +{ + public required Dictionary ContentItemParams { get; set; } + + public required int ContentItemID { get; set; } + + public required string LanguageName { get; set; } + + public required int UserID { get; set; } + + public required VersionStatus VersionStatus { get; set; } +} diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemBase.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemBase.cs index 2b11a8d..56854d6 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemBase.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemBase.cs @@ -1,17 +1,17 @@ -using CMS.ContentEngine; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Interface for synchronized content items. -/// -public interface IContentItemBase : IContentItemFieldsSource -{ - string ContentTypeName { get; } - - string DisplayName { get; } - - string ShopifyObjectID { get; } - - int ContentItemIdentifier { get; } -} +using CMS.ContentEngine; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Interface for synchronized content items. +/// +public interface IContentItemBase : IContentItemFieldsSource +{ + string ContentTypeName { get; } + + string DisplayName { get; } + + string ShopifyObjectID { get; } + + int ContentItemIdentifier { get; } +} diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemService.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemService.cs index d62f550..fe96f9e 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemService.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IContentItemService.cs @@ -1,77 +1,77 @@ -using CMS.ContentEngine; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Interface for content items management -/// -public interface IContentItemService -{ - /// - /// Add and publish content item. - /// - /// - /// ID of created content item. - Task AddContentItem(ContentItemAddParams addParams); - - - /// - /// Get content items of given content type and query params. - /// - /// - /// - /// - /// Collection of content items. - Task> GetContentItems(string contentType, Action queryParams) - where T : IContentItemFieldsSource, new(); - - - /// - /// Get content items of given content type. - /// - /// - /// - /// Collection of content items. - Task> GetContentItems(string contentType) - where T : IContentItemFieldsSource, new(); - - - /// - /// Get content items of given content type. - /// - /// - /// - /// >Max. level for linked items - /// Collection of content items. - Task> GetContentItems(string contentType, int linkedItemsLevel) - where T : IContentItemFieldsSource, new(); - - - /// - /// Updates content item. - /// - /// - /// True when update succeeds, else False. - Task UpdateContentItem(ContentItemUpdateParams updateParams); - - - /// - /// Deletes content item. - /// - /// - /// - /// - /// - Task DeleteContentItem(int contentItemID, string languageName, int userID); - - - /// - /// Delete content items. - /// - /// Content type IDs - /// - /// - /// - Task DeleteContentItems(IEnumerable contentItemIDs, string languageName, int userID); -} - +using CMS.ContentEngine; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Interface for content items management +/// +public interface IContentItemService +{ + /// + /// Add and publish content item. + /// + /// + /// ID of created content item. + Task AddContentItem(ContentItemAddParams addParams); + + + /// + /// Get content items of given content type and query params. + /// + /// + /// + /// + /// Collection of content items. + Task> GetContentItems(string contentType, Action queryParams) + where T : IContentItemFieldsSource, new(); + + + /// + /// Get content items of given content type. + /// + /// + /// + /// Collection of content items. + Task> GetContentItems(string contentType) + where T : IContentItemFieldsSource, new(); + + + /// + /// Get content items of given content type. + /// + /// + /// + /// >Max. level for linked items + /// Collection of content items. + Task> GetContentItems(string contentType, int linkedItemsLevel) + where T : IContentItemFieldsSource, new(); + + + /// + /// Updates content item. + /// + /// + /// True when update succeeds, else False. + Task UpdateContentItem(ContentItemUpdateParams updateParams); + + + /// + /// Deletes content item. + /// + /// + /// + /// + /// + Task DeleteContentItem(int contentItemID, string languageName, int userID); + + + /// + /// Delete content items. + /// + /// Content type IDs + /// + /// + /// + Task DeleteContentItems(IEnumerable contentItemIDs, string languageName, int userID); +} + diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IItemIdentifier.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IItemIdentifier.cs index 1a1aeb5..9f114e2 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IItemIdentifier.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/IItemIdentifier.cs @@ -1,13 +1,13 @@ -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Interface to set external identifier for api objects. -/// -/// External ID type. -public interface IItemIdentifier -{ - /// - /// External identifier. - /// - public TType ExternalId { get; } -} +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Interface to set external identifier for api objects. +/// +/// External ID type. +public interface IItemIdentifier +{ + /// + /// External identifier. + /// + public TType ExternalId { get; } +} diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ISynchronizationItem.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ISynchronizationItem.cs index e0b9400..0324544 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ISynchronizationItem.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/ISynchronizationItem.cs @@ -1,40 +1,40 @@ -using CMS.ContentEngine; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -public interface ISynchronizationItem - where TContentItem : IContentItemBase, new() -{ - /// - /// Fills modifiedProps with modified properties compared to contentItem. - /// - /// - /// - /// True if any property was modified. Otherwise False. - public bool GetModifiedProperties(TContentItem contentItem, out Dictionary modifiedProps); -} - - -/// -/// Interface for synchronization between data in Dto and content items. -/// -/// -/// -public interface ISynchronizationItem - where TDto : class - where TContentItem : IContentItemFieldsSource -{ - /// - /// Dto item, typically object from Ecommerce platform API - /// - public TDto Item { get; set; } - - - /// - /// Fills modifiedProps with modified properties compared to contentItem. - /// - /// - /// - /// True if any property was modified. Otherwise False. - bool GetModifiedProperties(TContentItem contentItem, out Dictionary modifiedProps); -} +using CMS.ContentEngine; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +public interface ISynchronizationItem + where TContentItem : IContentItemBase, new() +{ + /// + /// Fills modifiedProps with modified properties compared to contentItem. + /// + /// + /// + /// True if any property was modified. Otherwise False. + public bool GetModifiedProperties(TContentItem contentItem, out Dictionary modifiedProps); +} + + +/// +/// Interface for synchronization between data in Dto and content items. +/// +/// +/// +public interface ISynchronizationItem + where TDto : class + where TContentItem : IContentItemFieldsSource +{ + /// + /// Dto item, typically object from Ecommerce platform API + /// + public TDto Item { get; set; } + + + /// + /// Fills modifiedProps with modified properties compared to contentItem. + /// + /// + /// + /// True if any property was modified. Otherwise False. + bool GetModifiedProperties(TContentItem contentItem, out Dictionary modifiedProps); +} diff --git a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/SynchronizationServiceCommon.cs b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/SynchronizationServiceCommon.cs index 8d871ed..83af7c9 100644 --- a/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/SynchronizationServiceCommon.cs +++ b/src/Kentico.Xperience.Ecommerce.Common/ContentItemSynchronization/SynchronizationServiceCommon.cs @@ -1,82 +1,82 @@ -using System.Net.Mime; - -using CMS.ContentEngine; -using CMS.Core; - -using Path = CMS.IO.Path; - -namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; - -/// -/// Common service base functionality for synchronization of content items. -/// -/// -public abstract class SynchronizationServiceCommon(IHttpClientFactory httpClientFactory) -{ - protected (IEnumerable ToCreate, IEnumerable<(TStoreItem StoreItem, TContentItem ContentItem)> ToUpdate, - IEnumerable ToDelete) - ClassifyItems(IEnumerable storeItems, - IEnumerable existingItems) - where TStoreItem : IItemIdentifier - where TContentItem : IItemIdentifier - { - var existingLookup = existingItems.ToLookup(item => item.ExternalId); - var storeLookup = storeItems.ToLookup(item => item.ExternalId); - - var toCreate = storeItems.Where(storeItem => !existingLookup.Contains(storeItem.ExternalId)) - .ToList(); - - var toUpdate = storeItems.SelectMany(storeItem => existingLookup[storeItem.ExternalId], - (storeItem, existingItem) => (storeItem, existingItem)) - .ToList(); - - var toDelete = existingItems.Where(p => !storeLookup.Contains(p.ExternalId)).ToList(); - - return (toCreate, toUpdate, toDelete); - } - - - protected async Task CreateAssetMetadata(string url, string name) - { - byte[] bytes; - string? contentType; - using (var client = httpClientFactory.CreateClient()) - { - var response = await client.GetAsync(url); - response.EnsureSuccessStatusCode(); - bytes = await response.Content.ReadAsByteArrayAsync(); - contentType = response.Content.Headers.ContentType?.MediaType; - } - - long length = bytes.LongLength; - var dataWrapper = new BinaryDataWrapper(bytes); - var fileSource = new ContentItemAssetStreamSource(cancellationToken => Task.FromResult(dataWrapper.Stream)); - string extension = Path.GetExtension(name); - if (string.IsNullOrWhiteSpace(extension)) - { - extension = GetExtension(contentType); - name += extension; - } - - var assetMetadata = new ContentItemAssetMetadata() - { - Extension = extension, - Identifier = Guid.NewGuid(), - LastModified = DateTime.Now, - Name = name, - Size = length - }; - - return new ContentItemAssetMetadataWithSource(fileSource, assetMetadata); - } - - private static string GetExtension(string? contentType) => - contentType switch - { - MediaTypeNames.Image.Jpeg => ".jpg", - MediaTypeNames.Image.Png => ".png", - MediaTypeNames.Image.Webp => ".webp", - MediaTypeNames.Image.Avif => ".avif", - _ => string.Empty - }; -} +using System.Net.Mime; + +using CMS.ContentEngine; +using CMS.Core; + +using Path = CMS.IO.Path; + +namespace Kentico.Xperience.Ecommerce.Common.ContentItemSynchronization; + +/// +/// Common service base functionality for synchronization of content items. +/// +/// +public abstract class SynchronizationServiceCommon(IHttpClientFactory httpClientFactory) +{ + protected (IEnumerable ToCreate, IEnumerable<(TStoreItem StoreItem, TContentItem ContentItem)> ToUpdate, + IEnumerable ToDelete) + ClassifyItems(IEnumerable storeItems, + IEnumerable existingItems) + where TStoreItem : IItemIdentifier + where TContentItem : IItemIdentifier + { + var existingLookup = existingItems.ToLookup(item => item.ExternalId); + var storeLookup = storeItems.ToLookup(item => item.ExternalId); + + var toCreate = storeItems.Where(storeItem => !existingLookup.Contains(storeItem.ExternalId)) + .ToList(); + + var toUpdate = storeItems.SelectMany(storeItem => existingLookup[storeItem.ExternalId], + (storeItem, existingItem) => (storeItem, existingItem)) + .ToList(); + + var toDelete = existingItems.Where(p => !storeLookup.Contains(p.ExternalId)).ToList(); + + return (toCreate, toUpdate, toDelete); + } + + + protected async Task CreateAssetMetadata(string url, string name) + { + byte[] bytes; + string? contentType; + using (var client = httpClientFactory.CreateClient()) + { + var response = await client.GetAsync(url); + response.EnsureSuccessStatusCode(); + bytes = await response.Content.ReadAsByteArrayAsync(); + contentType = response.Content.Headers.ContentType?.MediaType; + } + + long length = bytes.LongLength; + var dataWrapper = new BinaryDataWrapper(bytes); + var fileSource = new ContentItemAssetStreamSource(cancellationToken => Task.FromResult(dataWrapper.Stream)); + string extension = Path.GetExtension(name); + if (string.IsNullOrWhiteSpace(extension)) + { + extension = GetExtension(contentType); + name += extension; + } + + var assetMetadata = new ContentItemAssetMetadata() + { + Extension = extension, + Identifier = Guid.NewGuid(), + LastModified = DateTime.Now, + Name = name, + Size = length + }; + + return new ContentItemAssetMetadataWithSource(fileSource, assetMetadata); + } + + private static string GetExtension(string? contentType) => + contentType switch + { + MediaTypeNames.Image.Jpeg => ".jpg", + MediaTypeNames.Image.Png => ".png", + MediaTypeNames.Image.Webp => ".webp", + MediaTypeNames.Image.Avif => ".avif", + _ => string.Empty + }; +}