Browse Source

Merge branch 'dev' into cms-kit-enhancements

pull/17925/head
Enis Necipoglu 3 years ago
parent
commit
e3bd32a9f4
No known key found for this signature in database GPG Key ID: 1EC55E13241E1680
  1. 2
      Directory.Packages.props
  2. 4
      abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json
  3. 9
      abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json
  4. 2
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs
  5. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  6. 4
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs
  7. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentRepository.cs
  8. 5
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs
  9. 1
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs
  10. 6
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs
  11. 5
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs
  12. 3
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParametersInput.cs
  13. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  14. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml
  15. 23
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js
  16. 20
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs
  17. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj
  18. 28
      modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs
  19. 2
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitDataSeedContributor.cs
  20. 2
      modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs

2
Directory.Packages.props

@ -139,7 +139,7 @@
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Shouldly" Version="4.0.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.6" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta13" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
<PackageVersion Include="Slugify.Core" Version="4.0.1" />
<PackageVersion Include="Spectre.Console" Version="0.47.0" />
<PackageVersion Include="StackExchange.Redis" Version="2.6.122" />

4
abp_io/AbpIoLocalization/AbpIoLocalization/Base/Localization/Resources/en.json

@ -221,6 +221,8 @@
"More": "More",
"WhyABPIOPlatform": "Why ABP.IO Platform?",
"AbpStudio": "ABP Studio",
"ExtraMonths": "{0}<span>EXTRA MONTHS</span>"
"ExtraMonths": "{0}<span>EXTRA MONTHS</span>",
"RSS": "RSS",
"RSSFeed": "RSS Feed"
}
}

9
abp_io/AbpIoLocalization/AbpIoLocalization/Community/Localization/Resources/en.json

@ -235,6 +235,13 @@
"SponsoredConferencesDescription": "We are honoring to support .NET communities and events for software developers.",
"UpcomingEvents": "<span class=\"text-info\">Upcoming</span> Events",
"UpcomingCommunityTalkEventDescription": "The live shows, hosted by the ABP Team, are casual sessions full of community content, demos, Q&A, and discussions around what's happening in ABP.",
"UpcomingConferenceEventDescription": "ABP .NET Conference is a virtual event for the .NET Developer community to come together and listen to talks about the .NET world, common software development practices and the open source ABP Framework."
"UpcomingConferenceEventDescription": "ABP .NET Conference is a virtual event for the .NET Developer community to come together and listen to talks about the .NET world, common software development practices and the open source ABP Framework.",
"LastOneYear": "Last 1 Year",
"AllTimes": "All Times",
"TopContributors": "Top Contributors",
"{0}Posts": "{0} Posts",
"LATESTPOSTS": "LATEST POSTS",
"NoContributorsFound": "No contributors found!",
"LatestPost": "Latest post"
}
}

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

@ -12,4 +12,6 @@ public static class CommentConsts
public static int MaxTextLength { get; set; } = 512;
public static int MaxUrlLength { get; set; } = 512;
public static int MaxIdempotencyTokenLength { get; set; } = 32;
}

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

@ -226,6 +226,7 @@
"RemoveCoverImage": "Remove cover image",
"CssClass": "CSS Class",
"TagsHelpText": "Tags should be comma-separated (e.g.: tag1, tag2, tag3)",
"ThisPartOfContentCouldntBeLoaded": "This part of content couldn't be loaded."
"ThisPartOfContentCouldntBeLoaded": "This part of content couldn't be loaded.",
"DuplicateCommentAttemptMessage": "Duplicate comment post attempt detected. Your comment has already been submitted."
}
}

4
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs

@ -25,9 +25,11 @@ public class Comment : AggregateRoot<Guid>, IHasCreationTime, IMustHaveCreator,
public virtual string Url { get; set; }
public virtual string IdempotencyToken { get; set; }
protected Comment()
{
}
internal Comment(

2
modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentRepository.cs

@ -44,4 +44,6 @@ public interface ICommentRepository : IBasicRepository<Comment, Guid>
Comment comment,
CancellationToken cancellationToken = default
);
Task<bool> ExistsAsync(string idempotencyToken, CancellationToken cancellationToken = default);
}

5
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs

@ -141,6 +141,11 @@ public class EfCoreCommentRepository : EfCoreRepository<ICmsKitDbContext, Commen
await DeleteAsync(comment, cancellationToken: GetCancellationToken(cancellationToken));
}
public virtual async Task<bool> ExistsAsync(string idempotencyToken, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).AnyAsync(x => x.IdempotencyToken == idempotencyToken, GetCancellationToken(cancellationToken));
}
protected virtual async Task<IQueryable<CommentWithAuthorQueryResultItem>> GetListQueryAsync(
string filter = null,
string entityType = null,

1
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs

@ -80,6 +80,7 @@ public static class CmsKitDbContextModelCreatingExtensions
b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength);
b.Property(x => x.RepliedCommentId);
b.Property(x => x.Url).HasMaxLength(CommentConsts.MaxUrlLength);
b.Property(x => x.IdempotencyToken).HasMaxLength(CommentConsts.MaxIdempotencyTokenLength);
b.HasIndex(x => new { x.TenantId, x.EntityType, x.EntityId });
b.HasIndex(x => new { x.TenantId, x.RepliedCommentId });

6
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs

@ -159,6 +159,12 @@ public class MongoCommentRepository : MongoDbRepository<ICmsKitMongoDbContext, C
}
}
public virtual async Task<bool> ExistsAsync(string idempotencyToken, CancellationToken cancellationToken = default)
{
return await (await GetMongoQueryableAsync(cancellationToken))
.AnyAsync(x => x.IdempotencyToken == idempotencyToken, GetCancellationToken(cancellationToken));
}
protected virtual async Task<IQueryable<Comment>> GetListQueryAsync(
string filter = null,
string entityType = null,

5
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs

@ -7,7 +7,7 @@ using Volo.CmsKit.Comments;
namespace Volo.CmsKit.Public.Comments;
[Serializable]
public class CreateCommentInput: ExtensibleObject
public class CreateCommentInput : ExtensibleObject
{
[Required]
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))]
@ -20,4 +20,7 @@ public class CreateCommentInput: ExtensibleObject
public int CaptchaAnswer { get; set; }
public string Url { get; set; }
[Required]
public string IdempotencyToken { get; set; }
}

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

@ -25,4 +25,7 @@ public class CreateCommentWithParametersInput
public int CaptchaAnswer { get; set; }
public string Url { get; set; }
[Required]
public string IdempotencyToken { get; set; }
}

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

@ -63,14 +63,15 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
public virtual async Task<CommentDto> CreateAsync(string entityType, string entityId, CreateCommentInput input)
{
CheckExternalUrls(entityType, input.Text);
var user = await CmsUserLookupService.GetByIdAsync(CurrentUser.GetId());
if (input.RepliedCommentId.HasValue)
{
await CommentRepository.GetAsync(input.RepliedCommentId.Value);
}
await CheckIdempotencyTokenUniquenessAsync(input.IdempotencyToken);
var user = await CmsUserLookupService.GetByIdAsync(CurrentUser.GetId());
var comment = await CommentRepository.InsertAsync(
await CommentManager.CreateAsync(
user,
@ -192,4 +193,14 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
{
return ObjectMapper.Map<CmsUser, CmsUserDto>(comments.Single(c => c.Comment.Id == commentId).Author);
}
private async Task CheckIdempotencyTokenUniquenessAsync(string idempotencyToken)
{
if(!await CommentRepository.ExistsAsync(idempotencyToken))
{
return;
}
throw new UserFriendlyException(L["DuplicateCommentAttemptMessage"]);
}
}

1
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml

@ -31,6 +31,7 @@
data-reply-id="@(repliedCommentId?.ToString() ?? "")"
style="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? "" : "display:none")">
<form class="cms-comment-form">
<input hidden value="@(Guid.NewGuid().ToString("N"))" name="idempotencyToken" />
<input hidden value="@(repliedCommentId?.ToString() ?? "")" name="repliedCommentId" />
<div class="row">
<div class="col">

23
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js

@ -107,10 +107,15 @@
function registerUpdateOfNewComment($container) {
$container.find('.cms-comment-update-form').each(function () {
let $form = $(this);
var $form = $(this);
$form.submit(function (e) {
e.preventDefault();
abp.ui.setBusy($form.find("button[type='submit']"));
let formAsObject = $form.serializeFormToObject();
$.ajax({
type: 'POST',
url: '/CmsKitPublicComments/Update/' + formAsObject.id,
@ -124,9 +129,11 @@
}),
success: function () {
widgetManager.refresh($widget);
abp.ui.clearBusy();
},
error: function (data) {
abp.message.error(data.responseJSON.error.message);
abp.ui.clearBusy();
}
});
});
@ -135,10 +142,14 @@
function registerSubmissionOfNewComment($container) {
$container.find('.cms-comment-form').each(function () {
let $form = $(this);
var $form = $(this);
$form.submit(function (e) {
e.preventDefault();
let formAsObject = $form.serializeFormToObject();
abp.ui.setBusy("button[type='submit']");
var formAsObject = $form.serializeFormToObject();
if (formAsObject.repliedCommentId == '') {
formAsObject.repliedCommentId = null;
@ -146,6 +157,7 @@
if (formAsObject.commentText == '') {
abp.message.error(l("CommentTextRequired"));
abp.ui.clearBusy();
return;
}
@ -161,13 +173,16 @@
text: formAsObject.commentText,
url: window.location.href,
captchaToken: formAsObject.captchaId,
captchaAnswer: formAsObject.input?.captcha
captchaAnswer: formAsObject.input?.captcha,
idempotencyToken: formAsObject.idempotencyToken
}),
success: function () {
widgetManager.refresh($widget);
abp.ui.clearBusy();
},
error: function (data) {
abp.message.error(data.responseJSON.error.message);
abp.ui.clearBusy();
}
});
});

20
modules/cms-kit/src/Volo.CmsKit.Public.Web/Security/Captcha/SimpleMathsCaptchaGenerator.cs

@ -116,7 +116,9 @@ public class SimpleMathsCaptchaGenerator : ITransientDependency
var random = new Random();
var startWith = (byte)random.Next(5, 10);
image.Mutate(ctx => ctx.BackgroundColor(Color.Transparent));
var fontFamily = SystemFonts.Families.FirstOrDefault(x => x.IsStyleAvailable(options.FontStyle))?.Name ?? SystemFonts.Families.First().Name;
var fontFamily = SystemFonts.Families
.FirstOrDefault(x => x.GetAvailableStyles().Contains(options.FontStyle), SystemFonts.Families.First())
.Name;
var font = SystemFonts.CreateFont(fontFamily, options.FontSize, options.FontStyle);
foreach (var character in stringText)
@ -125,7 +127,7 @@ public class SimpleMathsCaptchaGenerator : ITransientDependency
var color = options.TextColor[random.Next(0, options.TextColor.Length)];
var location = new PointF(startWith + position, random.Next(6, 13));
image.Mutate(ctx => ctx.DrawText(text, font, color, location));
position += TextMeasurer.Measure(character.ToString(), new RendererOptions(font, location)).Width;
position += TextMeasurer.MeasureSize(character.ToString(), new TextOptions(font)).Width;
}
//add rotation
@ -133,7 +135,7 @@ public class SimpleMathsCaptchaGenerator : ITransientDependency
image.Mutate(ctx => ctx.Transform(rotation));
// add the dynamic image to original image
var size = (ushort)TextMeasurer.Measure(stringText, new RendererOptions(font)).Width;
var size = (ushort)TextMeasurer.MeasureSize(stringText, new TextOptions(font)).Width;
var img = new Image<Rgba32>(size + 15, options.Height);
img.Mutate(ctx => ctx.BackgroundColor(Color.White));
@ -146,7 +148,7 @@ public class SimpleMathsCaptchaGenerator : ITransientDependency
var y1 = random.Next(0, img.Height);
img.Mutate(ctx =>
ctx.DrawLines(options.TextColor[random.Next(0, options.TextColor.Length)],
ctx.DrawLine(options.TextColor[random.Next(0, options.TextColor.Length)],
RandomTextGenerator.GenerateNextFloat(options.MinLineThickness, options.MaxLineThickness),
new PointF[] { new PointF(x0, y0), new PointF(x1, y1) })
);
@ -154,14 +156,14 @@ public class SimpleMathsCaptchaGenerator : ITransientDependency
img.Mutate(ctx => ctx.DrawImage(image, 0.80f));
Parallel.For(0, options.NoiseRate, i =>
Parallel.For(0, options.NoiseRate, _ =>
{
var x0 = random.Next(0, img.Width);
var y0 = random.Next(0, img.Height);
var x0 = random.Next(0, img.Width - 1);
var y0 = random.Next(0, img.Height - 1);
img.Mutate(
ctx => ctx
.DrawLines(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)],
RandomTextGenerator.GenerateNextFloat(0.5, 1.5), new PointF[] { new Vector2(x0, y0), new Vector2(x0, y0) })
.DrawLine(options.NoiseRateColor[random.Next(0, options.NoiseRateColor.Length)],
RandomTextGenerator.GenerateNextFloat(0.5, 1.5), new (x0, y0), new (x0 + 0.01f, y0 + 0.01f))
);
});

2
modules/cms-kit/src/Volo.CmsKit.Public.Web/Volo.CmsKit.Public.Web.csproj

@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="HtmlSanitizer" />
<PackageReference Include="SixLabors.ImageSharp" VersionOverride="1.0.4"/>
<PackageReference Include="SixLabors.ImageSharp"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
</ItemGroup>

28
modules/cms-kit/test/Volo.CmsKit.Application.Tests/Comments/CommentPublicAppService_Tests.cs

@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
@ -48,7 +49,8 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
new CreateCommentInput
{
RepliedCommentId = null,
Text = "newComment"
Text = "newComment",
IdempotencyToken = Guid.NewGuid().ToString("N")
}
);
@ -75,7 +77,8 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
new CreateCommentInput
{
RepliedCommentId = null,
Text = text
Text = text,
IdempotencyToken = Guid.NewGuid().ToString("N")
}
);
}
@ -95,6 +98,25 @@ public class CommentPublicAppService_Tests : CmsKitApplicationTestBase
{
RepliedCommentId = null,
Text = text, //not allowed URL
IdempotencyToken = Guid.NewGuid().ToString("N")
}
));
}
[Fact]
public async Task CreateAsync_ShouldThrowUserFriendlyException_If_IdempotencyToken_Not_Unique()
{
_currentUser.Id.Returns(_cmsKitTestData.User2Id);
await Should.ThrowAsync<UserFriendlyException>(async () =>
await _commentAppService.CreateAsync(
_cmsKitTestData.EntityType1,
_cmsKitTestData.EntityId1,
new CreateCommentInput
{
RepliedCommentId = null,
Text = "<text>",
IdempotencyToken = _cmsKitTestData.IdempotencyToken_1
}
));
}

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

@ -194,7 +194,7 @@ public class CmsKitDataSeedContributor : IDataSeedContributor, ITransientDepende
"comment",
null,
_cmsKitTestData.User1Id
));
){ IdempotencyToken = _cmsKitTestData.IdempotencyToken_1 });
await _commentRepository.InsertAsync(new Comment(_guidGenerator.Create(),
_cmsKitTestData.EntityType1,

2
modules/cms-kit/test/Volo.CmsKit.TestBase/CmsKitTestData.cs

@ -131,4 +131,6 @@ public class CmsKitTestData : ISingletonDependency
public string PollName { get; } = "Poll";
public string WidgetName { get; } = "CmsPollByCode";
public string IdempotencyToken_1 { get; } = Guid.NewGuid().ToString("N");
}

Loading…
Cancel
Save