Browse Source

CMS Kit: Prevent duplicate comment records

pull/17924/head
Engincan VESKE 3 years ago
parent
commit
cdd14dbf4e
  1. 2
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CommentConsts.cs
  2. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  3. 4
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/Comment.cs
  4. 2
      modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Comments/ICommentRepository.cs
  5. 5
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs
  6. 1
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/EntityFrameworkCore/CmsKitDbContextModelCreatingExtensions.cs
  7. 6
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs
  8. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs
  9. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParametersInput.cs
  10. 15
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  11. 1
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml
  12. 23
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js

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

@ -225,6 +225,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,

4
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,6 @@ public class CreateCommentInput: ExtensibleObject
public int CaptchaAnswer { get; set; }
public string Url { get; set; }
public string IdempotencyToken { get; set; }
}

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

@ -25,4 +25,6 @@ public class CreateCommentWithParametersInput
public int CaptchaAnswer { get; set; }
public string Url { get; set; }
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();
}
});
});

Loading…
Cancel
Save