Browse Source

Merge pull request #7500 from abpframework/cms-kit/reaction-definitions

CmsKit - Refactor Reactions Definitions Configuration
pull/8022/head
İlkay İlknur 5 years ago
committed by GitHub
parent
commit
d5f7d43fbe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs
  2. 5
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs
  3. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs
  4. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  5. 1
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json
  6. 60
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs
  7. 12
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs
  8. 11
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/CmsKitReactionOptions.cs
  9. 45
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/DefaultReactionDefinitionStore.cs
  10. 23
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/EntityCantHaveReactionException.cs
  11. 8
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IReactionDefinitionStore.cs
  12. 1
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinition.cs
  13. 14
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinitionDictionary.cs
  14. 26
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionEntityTypeDefinition.cs
  15. 11
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionManager.cs
  16. 3
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  17. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Reactions/ReactionPublicAppService.cs
  18. 4
      modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Reactions/ReactionManager_Tests.cs
  19. 35
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs

18
modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs

@ -39,6 +39,8 @@ using Volo.Abp.Threading;
using Volo.Abp.VirtualFileSystem;
using Volo.CmsKit.Admin.Web;
using Volo.CmsKit.Public.Web;
using System;
using Volo.CmsKit.Reactions;
namespace Volo.CmsKit
{
@ -81,6 +83,8 @@ namespace Volo.CmsKit
var hostingEnvironment = context.Services.GetHostingEnvironment();
var configuration = context.Services.GetConfiguration();
ConfigureCmsKit(context);
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer();
@ -131,6 +135,20 @@ namespace Volo.CmsKit
});
}
private void ConfigureCmsKit(ServiceConfigurationContext context)
{
Configure<CmsKitReactionOptions>(options =>
{
options.EntityTypes.Add(
new ReactionEntityTypeDefinition("quote",
reactions: new[]
{
new ReactionDefinition(StandardReactions.ThumbsUp),
new ReactionDefinition(StandardReactions.ThumbsDown),
}));
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();

5
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/CmsKitErrorCodes.cs

@ -13,6 +13,11 @@
public const string SlugAlreadyExist = "CmsKit:Page:0001";
}
public static class Reactions
{
public const string EntityCantHaveReaction = "CmsKit:Reaction:0001";
}
public static class Blogs
{
public const string SlugAlreadyExists = "CmsKit:Blog:0001";

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs

@ -4,6 +4,7 @@ namespace Volo.CmsKit.Comments
{
public static class CommentConsts
{
public const string EntityType = "Comment";
public static int MaxEntityTypeLength { get; set; } = CmsEntityConsts.MaxEntityTypeLength;
public static int MaxEntityIdLength { get; set; } = CmsEntityConsts.MaxEntityIdLength;

3
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -17,10 +17,11 @@
"CmsKit:0003": "The entity {0} is not taggable.",
"CmsKit:Blog:0001": "The given slug ({Slug}) already exists!",
"CmsKit:BlogPost:0001": "The given slug already exists!",
"CmsKit:Comments:0001": "The entity {0} is not commentable.",
"CmsKit:Comments:0001": "The entity {EntityType} is not commentable.",
"CmsKit:Media:0001": "'{Name}' is not a valid media name.",
"CmsKit:Media:0002": "The entity can't have media.",
"CmsKit:Page:0001": "The given url ({0}) already exists.",
"CmsKit:Reaction:0001": "The entity {EntityType} can't have reactions.",
"CmsKit:Tag:0002": "The entity is not taggable!",
"CommentAuthorizationExceptionMessage": "Those comments are not allowed for public display.",
"CommentDeletionConfirmationMessage": "This comment and all replies will be deleted!",

1
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/tr.json

@ -18,6 +18,7 @@
"CmsKit:0003": "{0} ögesi etiketlenebilir değil.",
"CmsKit:BlogPost:0001": "Aynı url etiketi zaten mevcut.",
"CmsKit:Media:0002": "Bu öge için medya eklenemez.",
"CmsKit:Reaction:0001": "Bu ögeye tepki verilemez.",
"CmsKit:Page:0001": "Girilen url ({0}) kullanımdadır.",
"CmsKit:Tag:0002": "Bu öge etiketlenebilir değil.",
"CommentAuthorizationExceptionMessage": "Bu yorumları görebilmek için yetki gerekir.",

60
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitDomainModule.cs

@ -1,5 +1,7 @@
using Volo.Abp.BlobStoring;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using Volo.Abp;
using Volo.Abp.Domain;
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Localization;
@ -10,6 +12,8 @@ using Volo.CmsKit.Localization;
using Volo.CmsKit.Pages;
using Volo.CmsKit.Reactions;
using Volo.CmsKit.Tags;
using Volo.CmsKit.Blogs;
using Volo.CmsKit.Comments;
namespace Volo.CmsKit
{
@ -23,22 +27,50 @@ namespace Volo.CmsKit
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<CmsKitOptions>(options =>
if (GlobalFeatureManager.Instance.IsEnabled<ReactionsFeature>())
{
options.Reactions.AddOrReplace(StandardReactions.ThumbsUp);
options.Reactions.AddOrReplace(StandardReactions.ThumbsDown);
options.Reactions.AddOrReplace(StandardReactions.Smile);
options.Reactions.AddOrReplace(StandardReactions.Wink);
options.Reactions.AddOrReplace(StandardReactions.Confused);
options.Reactions.AddOrReplace(StandardReactions.Victory);
options.Reactions.AddOrReplace(StandardReactions.Rock);
options.Reactions.AddOrReplace(StandardReactions.Eyes);
options.Reactions.AddOrReplace(StandardReactions.Heart);
options.Reactions.AddOrReplace(StandardReactions.HeartBroken);
options.Reactions.AddOrReplace(StandardReactions.Rocket);
options.Reactions.AddOrReplace(StandardReactions.Pray);
Configure<CmsKitReactionOptions>(options =>
{
if (GlobalFeatureManager.Instance.IsEnabled<BlogsFeature>())
{
options.EntityTypes.Add(
new ReactionEntityTypeDefinition(
BlogPostConsts.EntityType,
reactions: new[]
{
new ReactionDefinition(StandardReactions.Smile),
new ReactionDefinition(StandardReactions.ThumbsUp),
new ReactionDefinition(StandardReactions.ThumbsDown),
new ReactionDefinition(StandardReactions.Confused),
new ReactionDefinition(StandardReactions.Eyes),
new ReactionDefinition(StandardReactions.Heart),
new ReactionDefinition(StandardReactions.HeartBroken),
new ReactionDefinition(StandardReactions.Wink),
new ReactionDefinition(StandardReactions.Pray),
new ReactionDefinition(StandardReactions.Rocket),
new ReactionDefinition(StandardReactions.Victory),
new ReactionDefinition(StandardReactions.Rock),
}));
}
});
if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>())
{
options.EntityTypes.Add(
new ReactionEntityTypeDefinition(
CommentConsts.EntityType,
reactions: new[]
{
new ReactionDefinition(StandardReactions.ThumbsUp),
new ReactionDefinition(StandardReactions.ThumbsDown),
}));
}
});
}
if (GlobalFeatureManager.Instance.IsEnabled<TagsFeature>())
{
// TODO: Configure TagEntityTypes here...
}
}
private static LocalizableString L(string name)

12
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/CmsKitOptions.cs

@ -1,12 +0,0 @@
using JetBrains.Annotations;
using Volo.CmsKit.Reactions;
using Volo.CmsKit.Tags;
namespace Volo.CmsKit
{
public class CmsKitOptions
{
[NotNull]
public ReactionDefinitionDictionary Reactions { get; } = new ReactionDefinitionDictionary();
}
}

11
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/CmsKitReactionOptions.cs

@ -0,0 +1,11 @@
using JetBrains.Annotations;
using System.Collections.Generic;
namespace Volo.CmsKit.Reactions
{
public class CmsKitReactionOptions
{
[NotNull]
public List<ReactionEntityTypeDefinition> EntityTypes { get; } = new ();
}
}

45
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/DefaultReactionDefinitionStore.cs

@ -1,28 +1,59 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
namespace Volo.CmsKit.Reactions
{
public class DefaultReactionDefinitionStore : IReactionDefinitionStore, ITransientDependency
{
protected CmsKitOptions Options { get; }
protected CmsKitReactionOptions Options { get; }
public DefaultReactionDefinitionStore(IOptions<CmsKitOptions> options)
public DefaultReactionDefinitionStore(IOptions<CmsKitReactionOptions> options)
{
Options = options.Value;
}
public virtual Task<List<ReactionDefinition>> GetReactionsAsync(string entityType = null)
public virtual async Task<List<ReactionDefinition>> GetReactionsAsync([NotNull] string entityType)
{
return Task.FromResult(Options.Reactions.Values.ToList());
Check.NotNullOrEmpty(entityType, nameof(entityType));
var definition = await GetAsync(entityType);
return definition.Reactions;
}
public virtual async Task<ReactionDefinition> GetReactionOrNullAsync([NotNull] string reactionName, [NotNull] string entityType)
{
Check.NotNullOrEmpty(entityType, nameof(entityType));
Check.NotNullOrEmpty(reactionName, nameof(reactionName));
var definition = await GetAsync(entityType);
return definition.Reactions.SingleOrDefault(x => x.Name == reactionName);
}
public virtual Task<ReactionDefinition> GetReactionOrNullAsync(string reactionName, string entityType = null)
public virtual Task<bool> IsDefinedAsync([NotNull] string entityType)
{
return Task.FromResult(Options.Reactions.GetOrDefault(reactionName));
Check.NotNullOrWhiteSpace(entityType, nameof(entityType));
var isDefined = Options.EntityTypes.Any(x => x.EntityType.Equals(entityType, StringComparison.InvariantCultureIgnoreCase));
return Task.FromResult(isDefined);
}
public virtual Task<ReactionEntityTypeDefinition> GetAsync([NotNull] string entityType)
{
Check.NotNullOrWhiteSpace(entityType, nameof(entityType));
var definition = Options.EntityTypes.SingleOrDefault(x => x.EntityType.Equals(entityType, StringComparison.InvariantCultureIgnoreCase)) ??
throw new EntityCantHaveReactionException(entityType);
return Task.FromResult(definition);
}
}
}

23
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/EntityCantHaveReactionException.cs

@ -0,0 +1,23 @@

using JetBrains.Annotations;
using System.Runtime.Serialization;
using Volo.Abp;
namespace Volo.CmsKit.Reactions
{
public class EntityCantHaveReactionException : BusinessException
{
public EntityCantHaveReactionException(SerializationInfo serializationInfo, StreamingContext context) : base(serializationInfo, context)
{
}
public EntityCantHaveReactionException([NotNull] string entityType)
{
EntityType = Check.NotNullOrEmpty(entityType, nameof(entityType));
Code = CmsKitErrorCodes.Reactions.EntityCantHaveReaction;
WithData(nameof(EntityType), EntityType);
}
public string EntityType { get; }
}
}

8
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/IReactionDefinitionStore.cs

@ -6,8 +6,12 @@ namespace Volo.CmsKit.Reactions
{
public interface IReactionDefinitionStore
{
Task<List<ReactionDefinition>> GetReactionsAsync([CanBeNull] string entityType = null);
Task<bool> IsDefinedAsync([NotNull]string entityType);
Task<ReactionDefinition> GetReactionOrNullAsync([NotNull] string reactionName, [CanBeNull] string entityType = null);
Task<ReactionEntityTypeDefinition> GetAsync([NotNull] string entityType);
Task<List<ReactionDefinition>> GetReactionsAsync([NotNull] string entityType);
Task<ReactionDefinition> GetReactionOrNullAsync([NotNull] string reactionName, [NotNull] string entityType);
}
}

1
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinition.cs

@ -1,4 +1,5 @@
using JetBrains.Annotations;
using System;
using Volo.Abp;
using Volo.Abp.Localization;

14
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionDefinitionDictionary.cs

@ -1,14 +0,0 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Localization;
namespace Volo.CmsKit.Reactions
{
public class ReactionDefinitionDictionary : Dictionary<string, ReactionDefinition>
{
public void AddOrReplace([NotNull] string name, ILocalizableString displayName = null)
{
this[name] = new ReactionDefinition(name, displayName);
}
}
}

26
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionEntityTypeDefinition.cs

@ -0,0 +1,26 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
namespace Volo.CmsKit.Reactions
{
public class ReactionEntityTypeDefinition : PolicySpecifiedDefinition
{
[NotNull]
public List<ReactionDefinition> Reactions { get; } = new();
public ReactionEntityTypeDefinition(
[NotNull] string entityType,
[NotNull] IEnumerable<ReactionDefinition> reactions,
IEnumerable<string> createPolicies = null,
IEnumerable<string> updatePolicies = null,
IEnumerable<string> deletePolicies = null) : base(entityType, createPolicies, updatePolicies, deletePolicies)
{
Reactions = Check.NotNull(reactions, nameof(reactions)).ToList();
}
}
}

11
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Reactions/ReactionManager.cs

@ -22,8 +22,10 @@ namespace Volo.CmsKit.Reactions
}
public virtual async Task<List<ReactionDefinition>> GetReactionsAsync(
[CanBeNull] string entityType = null)
[NotNull] string entityType)
{
Check.NotNullOrEmpty(entityType, nameof(entityType));
return await ReactionDefinitionStore.GetReactionsAsync(entityType);
}
@ -51,7 +53,7 @@ namespace Volo.CmsKit.Reactions
.ToList();
}
public virtual async Task<UserReaction> CreateAsync(
public virtual async Task<UserReaction> GetOrCreateAsync(
Guid creatorId,
[NotNull] string entityType,
[NotNull] string entityId,
@ -67,6 +69,11 @@ namespace Volo.CmsKit.Reactions
return existingReaction;
}
if (!await ReactionDefinitionStore.IsDefinedAsync(entityType))
{
throw new EntityCantHaveReactionException(entityType);
}
return await UserReactionRepository.InsertAsync(
new UserReaction(
GuidGenerator.Create(),

3
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs

@ -18,7 +18,6 @@ namespace Volo.CmsKit.Public.Comments
{
public class CommentPublicAppService : ApplicationService, ICommentPublicAppService
{
protected CmsKitOptions CmsKitOptions { get; }
protected ICommentRepository CommentRepository { get; }
protected ICmsUserLookupService CmsUserLookupService { get; }
public IDistributedEventBus DistributedEventBus { get; }
@ -28,10 +27,8 @@ namespace Volo.CmsKit.Public.Comments
ICommentRepository commentRepository,
ICmsUserLookupService cmsUserLookupService,
IDistributedEventBus distributedEventBus,
IOptions<CmsKitOptions> cmsKitOptions,
CommentManager commentManager)
{
CmsKitOptions = cmsKitOptions.Value;
CommentRepository = commentRepository;
CmsUserLookupService = cmsUserLookupService;
DistributedEventBus = distributedEventBus;

2
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Reactions/ReactionPublicAppService.cs

@ -59,7 +59,7 @@ namespace Volo.CmsKit.Public.Reactions
[Authorize]
public virtual async Task CreateAsync(string entityType, string entityId, string reaction)
{
await ReactionManager.CreateAsync(
await ReactionManager.GetOrCreateAsync(
CurrentUser.GetId(),
entityType,
entityId,

4
modules/cms-kit/test/Volo.CmsKit.Domain.Tests/Reactions/ReactionManager_Tests.cs

@ -19,7 +19,7 @@ namespace Volo.CmsKit.Reactions
[Fact]
public async Task GetReactionsAsync()
{
var reactions = await _reactionManager.GetReactionsAsync();
var reactions = await _reactionManager.GetReactionsAsync(_cmsKitTestData.EntityType1);
reactions.Count.ShouldBe(12);
@ -44,7 +44,7 @@ namespace Volo.CmsKit.Reactions
[Fact]
public async Task CreateAsync()
{
var reaction = await _reactionManager.CreateAsync(
var reaction = await _reactionManager.GetOrCreateAsync(
_cmsKitTestData.User2Id,
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId2,

35
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs

@ -39,7 +39,7 @@ namespace Volo.CmsKit
private readonly IBlogFeatureRepository _blogFeatureRepository;
private readonly IBlogPostRepository _blogPostRepository;
private readonly BlogPostManager _blogPostManager;
private readonly IOptions<CmsKitOptions> _options;
private readonly IOptions<CmsKitReactionOptions> _reactionOptions;
private readonly IOptions<CmsKitTagOptions> _tagOptions;
private readonly IMediaDescriptorRepository _mediaDescriptorRepository;
private readonly IBlobContainer<MediaContainer> _mediaBlobContainer;
@ -64,7 +64,7 @@ namespace Volo.CmsKit
BlogPostManager blogPostmanager,
IBlogFeatureRepository blogFeatureRepository,
EntityTagManager entityTagManager,
IOptions<CmsKitOptions> options,
IOptions<CmsKitReactionOptions> reactionOptions,
IOptions<CmsKitTagOptions> tagOptions,
IMediaDescriptorRepository mediaDescriptorRepository,
IBlobContainer<MediaContainer> mediaBlobContainer,
@ -88,7 +88,7 @@ namespace Volo.CmsKit
_blogPostRepository = blogPostRepository;
_blogPostManager = blogPostmanager;
_blogFeatureRepository = blogFeatureRepository;
_options = options;
_reactionOptions = reactionOptions;
_tagOptions = tagOptions;
_mediaDescriptorRepository = mediaDescriptorRepository;
_mediaBlobContainer = mediaBlobContainer;
@ -140,6 +140,25 @@ namespace Volo.CmsKit
_commentsOptions.Value.EntityTypes.Add(
new CommentEntityTypeDefinition(_cmsKitTestData.EntityType1));
List<ReactionDefinition> reactions = new()
{
new ReactionDefinition(StandardReactions.Smile),
new ReactionDefinition(StandardReactions.ThumbsUp),
new ReactionDefinition(StandardReactions.ThumbsDown),
new ReactionDefinition(StandardReactions.Confused),
new ReactionDefinition(StandardReactions.Eyes),
new ReactionDefinition(StandardReactions.Heart),
new ReactionDefinition(StandardReactions.HeartBroken),
new ReactionDefinition(StandardReactions.Wink),
new ReactionDefinition(StandardReactions.Pray),
new ReactionDefinition(StandardReactions.Rocket),
new ReactionDefinition(StandardReactions.Victory),
new ReactionDefinition(StandardReactions.Rock),
};
_reactionOptions.Value.EntityTypes.Add(new ReactionEntityTypeDefinition(_cmsKitTestData.EntityType1, reactions));
_reactionOptions.Value.EntityTypes.Add(new ReactionEntityTypeDefinition(_cmsKitTestData.EntityType2, reactions));
return Task.CompletedTask;
}
@ -209,31 +228,31 @@ namespace Volo.CmsKit
private async Task SeedReactionsAsync()
{
await _reactionManager.CreateAsync(
await _reactionManager.GetOrCreateAsync(
_cmsKitTestData.User1Id,
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId1,
StandardReactions.Confused);
await _reactionManager.CreateAsync(
await _reactionManager.GetOrCreateAsync(
_cmsKitTestData.User1Id,
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId1,
StandardReactions.ThumbsUp);
await _reactionManager.CreateAsync(
await _reactionManager.GetOrCreateAsync(
_cmsKitTestData.User1Id,
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId2,
StandardReactions.Heart);
await _reactionManager.CreateAsync(
await _reactionManager.GetOrCreateAsync(
_cmsKitTestData.User1Id,
_cmsKitTestData.EntityType2,
_cmsKitTestData.EntityId1,
StandardReactions.Rocket);
await _reactionManager.CreateAsync(
await _reactionManager.GetOrCreateAsync(
_cmsKitTestData.User2Id,
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId1,

Loading…
Cancel
Save