Skip to content

Commit

Permalink
Merge pull request #744 from EdiWang/feature/close-comment-days
Browse files Browse the repository at this point in the history
Auto close comment after x days
  • Loading branch information
EdiWang authored Aug 7, 2023
2 parents feaa22a + b4cf75c commit 534e185
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 99 deletions.
31 changes: 22 additions & 9 deletions src/Moonglade.Comments/CreateCommentCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Moonglade.Comments;

public class CreateCommentCommand : IRequest<CommentDetailedItem>
public class CreateCommentCommand : IRequest<(int Status, CommentDetailedItem Item)>
{
public CreateCommentCommand(Guid postId, CommentRequest payload, string ipAddress)
{
Expand All @@ -23,7 +23,7 @@ public CreateCommentCommand(Guid postId, CommentRequest payload, string ipAddres
public string IpAddress { get; set; }
}

public class CreateCommentCommandHandler : IRequestHandler<CreateCommentCommand, CommentDetailedItem>
public class CreateCommentCommandHandler : IRequestHandler<CreateCommentCommand, (int Status, CommentDetailedItem Item)>
{
private readonly IBlogConfig _blogConfig;
private readonly IRepository<PostEntity> _postRepo;
Expand All @@ -39,7 +39,7 @@ public CreateCommentCommandHandler(
_commentRepo = commentRepo;
}

public async Task<CommentDetailedItem> Handle(CreateCommentCommand request, CancellationToken ct)
public async Task<(int Status, CommentDetailedItem Item)> Handle(CreateCommentCommand request, CancellationToken ct)
{
if (_blogConfig.ContentSettings.EnableWordFilter)
{
Expand All @@ -53,12 +53,28 @@ public async Task<CommentDetailedItem> Handle(CreateCommentCommand request, Canc
if (await _moderator.HasBadWord(request.Payload.Username, request.Payload.Content))
{
await Task.CompletedTask;
return null;
return (-1, null);
}
break;
}
}

var spec = new PostSpec(request.PostId, false);
var postInfo = await _postRepo.FirstOrDefaultAsync(spec, p => new
{
p.Title,
p.PubDateUtc
});

if (_blogConfig.ContentSettings.CloseCommentAfterDays > 0)
{
var days = DateTime.UtcNow.Date.Subtract(postInfo.PubDateUtc.GetValueOrDefault()).Days;
if (days > _blogConfig.ContentSettings.CloseCommentAfterDays)
{
return (-2, null);
}
}

var model = new CommentEntity
{
Id = Guid.NewGuid(),
Expand All @@ -73,9 +89,6 @@ public async Task<CommentDetailedItem> Handle(CreateCommentCommand request, Canc

await _commentRepo.AddAsync(model, ct);

var spec = new PostSpec(request.PostId, false);
var postTitle = await _postRepo.FirstOrDefaultAsync(spec, p => p.Title);

var item = new CommentDetailedItem
{
Id = model.Id,
Expand All @@ -84,10 +97,10 @@ public async Task<CommentDetailedItem> Handle(CreateCommentCommand request, Canc
Email = model.Email,
IpAddress = model.IPAddress,
IsApproved = model.IsApproved,
PostTitle = postTitle,
PostTitle = postInfo.Title,
Username = model.Username
};

return item;
return (0, item);
}
}
4 changes: 4 additions & 0 deletions src/Moonglade.Configuration/ContentSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public class ContentSettings : IBlogSettings
[Display(Name = "Comments require review and approval")]
public bool RequireCommentReview { get; set; }

[Display(Name = "Automatically close comments on posts older than x days")]
[Range(0, 65536)]
public int CloseCommentAfterDays { get; set; }

[DataType(DataType.MultilineText)]
[Display(Name = "Blocked words")]
[MaxLength(2048)]
Expand Down
20 changes: 12 additions & 8 deletions src/Moonglade.Web/Controllers/CommentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,26 @@ public async Task<IActionResult> Create([NotEmpty] Guid postId, CommentRequest r
var ip = (bool)HttpContext.Items["DNT"]! ? "N/A" : Helper.GetClientIP(HttpContext);
var item = await _mediator.Send(new CreateCommentCommand(postId, request, ip));

if (item is null)
switch (item.Status)
{
ModelState.AddModelError(nameof(request.Content), "Your comment contains bad bad word.");
return Conflict(ModelState);
case -1:
ModelState.AddModelError(nameof(request.Content), "Your comment contains bad bad word.");
return Conflict(ModelState);
case -2:
ModelState.AddModelError(nameof(postId), "Comment is closed for this post.");
return Conflict(ModelState);
}

if (_blogConfig.NotificationSettings.SendEmailOnNewComment)
{
try
{
await _mediator.Publish(new CommentNotification(
item.Username,
item.Email,
item.IpAddress,
item.PostTitle,
item.CommentContent));
item.Item.Username,
item.Item.Email,
item.Item.IpAddress,
item.Item.PostTitle,
item.Item.CommentContent));
}
catch (Exception e)
{
Expand Down
165 changes: 87 additions & 78 deletions src/Moonglade.Web/Pages/Post.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -208,94 +208,103 @@
</article>
@if (BlogConfig.ContentSettings.EnableComments)
{
switch (BlogConfig.ContentSettings.CommentProvider)
var days = DateTime.UtcNow.Date.Subtract(Model.Post.PubDateUtc.GetValueOrDefault()).Days;

if (BlogConfig.ContentSettings.CloseCommentAfterDays > 0 && days > BlogConfig.ContentSettings.CloseCommentAfterDays)
{
case CommentProvider.BuiltIn:
<div class="d-print-none">
@if (Model.Post.CommentEnabled)
{
<div class="card bg-light comment-form-containter mb-4">
<div class="card-header">
<i class="bi-chat-left-text"></i>
@SharedLocalizer["Comments"]
</div>
<form id="comment-form">
<div class="card-body">
<div class="row g-1 mb-2">
<div class="col-md-4">
<input type="text"
class="form-control"
placeholder="Your name"
id="input-comment-name"
required/>
</div>
<div class="col-md-8">
<input type="email"
class="form-control"
placeholder="Email (Optional)"
id="input-comment-email"
data-bs-toggle="tooltip"
data-placement="top"
title="@SharedLocalizer["Providing your email address can enable blog admin to send notifications for replying your comment. Your email address will also be used to show Gravatar if it has one."]"/>
</div>
</div>
<div class="comment-md-content mb-2">
<textarea id="input-comment-content"
class="form-control"
cols="60"
rows="4"
placeholder="@SharedLocalizer["Your comments (Markdown supported)"]"
maxlength="1024"
required></textarea>
</div>
<div class="row">
<div class="col-9">
<div class="input-group">
<img id="img-captcha" onclick="viewpost.resetCaptchaImage()" src="~/captcha-image" data-bs-toggle="tooltip" data-placement="top" title="@SharedLocalizer["Can't read? Click to change another image."]" alt="Captcha image"/>
<div class="alert alert-warning">@SharedLocalizer["Comment is closed."]</div>
}
else
{
switch (BlogConfig.ContentSettings.CommentProvider)
{
case CommentProvider.BuiltIn:
<div class="d-print-none">
@if (Model.Post.CommentEnabled)
{
<div class="card bg-light comment-form-containter mb-4">
<div class="card-header">
<i class="bi-chat-left-text"></i>
@SharedLocalizer["Comments"]
</div>
<form id="comment-form">
<div class="card-body">
<div class="row g-1 mb-2">
<div class="col-md-4">
<input type="text"
id="input-comment-captcha"
class="form-control input-captcha"
placeholder="Captcha Code"
autocomplete="off"
minlength="4"
maxlength="4"
class="form-control"
placeholder="Your name"
id="input-comment-name"
required/>
</div>
<div class="col-md-8">
<input type="email"
class="form-control"
placeholder="Email (Optional)"
id="input-comment-email"
data-bs-toggle="tooltip"
data-placement="top"
title="@SharedLocalizer["Providing your email address can enable blog admin to send notifications for replying your comment. Your email address will also be used to show Gravatar if it has one."]"/>
</div>
</div>
<div class="col-3">
<button id="btn-submit-comment" type="submit" class="btn btn-success float-end">
<span id="loadingIndicator" class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
@SharedLocalizer["Submit"]
</button>
<div class="comment-md-content mb-2">
<textarea id="input-comment-content"
class="form-control"
cols="60"
rows="4"
placeholder="@SharedLocalizer["Your comments (Markdown supported)"]"
maxlength="1024"
required></textarea>
</div>
<div class="row">
<div class="col-9">
<div class="input-group">
<img id="img-captcha" onclick="viewpost.resetCaptchaImage()" src="~/captcha-image" data-bs-toggle="tooltip" data-placement="top" title="@SharedLocalizer["Can't read? Click to change another image."]" alt="Captcha image"/>
<input type="text"
id="input-comment-captcha"
class="form-control input-captcha"
placeholder="Captcha Code"
autocomplete="off"
minlength="4"
maxlength="4"
required/>
</div>
</div>
<div class="col-3">
<button id="btn-submit-comment" type="submit" class="btn btn-success float-end">
<span id="loadingIndicator" class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display: none;"></span>
@SharedLocalizer["Submit"]
</button>
</div>
</div>
</div>
</div>
</form>
</form>
</div>
}
else
{
<div class="alert alert-warning">@SharedLocalizer["Comment of this post is disabled."]</div>
}

<div id="thx-for-comment" class="alert alert-warning" style="display: none;">
@SharedLocalizer["Thanks, your comment is pending approval now."] <br/>
@SharedLocalizer["It will show up once blog administrator approved your comment."]
</div>
}
else
{
<div class="alert alert-warning">@SharedLocalizer["Comment of this post is disabled."]</div>
}

<div id="thx-for-comment" class="alert alert-warning" style="display: none;">
@SharedLocalizer["Thanks, your comment is pending approval now."] <br/>
@SharedLocalizer["It will show up once blog administrator approved your comment."]
</div>

<div id="thx-for-comment-non-review" class="alert alert-success" style="display: none;">
@SharedLocalizer["Thanks for your comment."] <br/>
@SharedLocalizer["Refresh the page to see your comment."]
</div>
<div id="thx-for-comment-non-review" class="alert alert-success" style="display: none;">
@SharedLocalizer["Thanks for your comment."] <br/>
@SharedLocalizer["Refresh the page to see your comment."]
</div>

<section id="comments-list">
@await Component.InvokeAsync("CommentList", new {postId = Model.Post.Id})
</section>
</div>
break;
case CommentProvider.ThirdParty:
@Html.Raw(BlogConfig.ContentSettings.ThirdPartyCommentHtmlPitch)
break;
<section id="comments-list">
@await Component.InvokeAsync("CommentList", new {postId = Model.Post.Id})
</section>
</div>
break;
case CommentProvider.ThirdParty:
@Html.Raw(BlogConfig.ContentSettings.ThirdPartyCommentHtmlPitch)
break;
}
}
}
else
Expand Down
22 changes: 18 additions & 4 deletions src/Moonglade.Web/Pages/Settings/Content.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,30 @@
</div>
<div class="col-md-5 text-end">
<div class="form-check form-check-inline">
@Html.RadioButtonFor(model => settings.CommentOrder, CommentOrder.OldToNew, new { id = CommentOrder.OldToNew.ToString(), @class = "form-check-input" })
<label class="form-check-label" for="@CommentOrder.OldToNew">@SharedLocalizer["Old to new"]</label>
@Html.RadioButtonFor(model => settings.CommentOrder, CommentOrder.OldToNew, new { id = CommentOrder.OldToNew.ToString(), @class = "form-check-input" })
<label class="form-check-label" for="@CommentOrder.OldToNew">@SharedLocalizer["Old to new"]</label>
</div>
<div class="form-check form-check-inline">
@Html.RadioButtonFor(model => settings.CommentOrder, CommentOrder.NewToOld, new { id = CommentOrder.NewToOld.ToString(), @class = "form-check-input" })
<label class="form-check-label" for="@CommentOrder.NewToOld">@SharedLocalizer["New to old"]</label>
@Html.RadioButtonFor(model => settings.CommentOrder, CommentOrder.NewToOld, new { id = CommentOrder.NewToOld.ToString(), @class = "form-check-input" })
<label class="form-check-label" for="@CommentOrder.NewToOld">@SharedLocalizer["New to old"]</label>
</div>
</div>
</div>


<div class="row g-3 align-items-center settings-entry mb-4">
<div class="col-auto">
<i class="settings-entry-icon bi-hourglass-split"></i>
</div>
<div class="col">
<label asp-for="@settings.CloseCommentAfterDays"></label>
<div class="form-text">@SharedLocalizer["Set to 0 to never close comments after any days"]</div>
</div>
<div class="col-md-5">
<input asp-for="@settings.CloseCommentAfterDays" class="form-control" required />
</div>
</div>

<div class="comment-settings-built-in">

<div class="row g-3 align-items-center settings-entry mb-4">
Expand Down

0 comments on commit 534e185

Please sign in to comment.