Implemented update and delete for posts

This commit is contained in:
Marcel Baumgartner
2023-10-28 19:33:59 +02:00
parent d98e8ef0f8
commit 6d83c31f42
3 changed files with 111 additions and 11 deletions

View File

@@ -45,6 +45,7 @@ public class PostService
{ {
post.Title = title; post.Title = title;
post.Content = content; post.Content = content;
post.UpdatedAt = DateTime.UtcNow;
PostRepository.Update(post); PostRepository.Update(post);
@@ -53,6 +54,30 @@ public class PostService
public async Task Delete(Post post) public async Task Delete(Post post)
{ {
var postWithData = PostRepository
.Get()
.Include(x => x.Comments)
.Include(x => x.Likes)
.First(x => x.Id == post.Id);
// Cache relational data to delete later on
var likes = postWithData.Likes.ToArray();
var comments = postWithData.Comments.ToArray();
// Clear relations
postWithData.Comments.Clear();
postWithData.Likes.Clear();
PostRepository.Update(postWithData);
// Delete relational data
foreach (var like in likes)
PostLikeRepository.Delete(like);
foreach (var comment in comments)
PostCommentRepository.Delete(comment);
// Now delete the post itself
PostRepository.Delete(post); PostRepository.Delete(post);
await Events.OnPostDeleted.InvokeAsync(post); await Events.OnPostDeleted.InvokeAsync(post);
} }
@@ -67,7 +92,7 @@ public class PostService
if (content.Length > 1024) if (content.Length > 1024)
throw new DisplayException("Comment content cannot be longer than 1024 characters"); throw new DisplayException("Comment content cannot be longer than 1024 characters");
if (!Regex.IsMatch(content, "^[a-zA-Z0-9äöüßÄÖÜẞ,.;_\\n\\t-]+$")) if (!Regex.IsMatch(content, "^[ a-zA-Z0-9äöüßÄÖÜẞ,.;_\\n\\t-]+$"))
throw new DisplayException("Illegal characters in comment content"); throw new DisplayException("Illegal characters in comment content");
//TODO: Swear word filter //TODO: Swear word filter

View File

@@ -1,6 +1,7 @@
@using Moonlight.App.Database.Entities.Community @using Moonlight.App.Database.Entities.Community
@using Ganss.Xss @using Ganss.Xss
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Moonlight.App.Models.Enums
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Services.Community @using Moonlight.App.Services.Community
@@ -8,6 +9,7 @@
@inject Repository<Post> PostRepository @inject Repository<Post> PostRepository
@inject IdentityService IdentityService @inject IdentityService IdentityService
@inject PostService PostService @inject PostService PostService
@inject ToastService ToastService
<div class="card card-flush"> <div class="card card-flush">
<div class="card-header pt-9"> <div class="card-header pt-9">
@@ -16,19 +18,29 @@
<img src="/api/bucket/avatars/@(Post.Author.Avatar)" class="" alt=""> <img src="/api/bucket/avatars/@(Post.Author.Avatar)" class="" alt="">
</div> </div>
<div class="flex-grow-1"> <div class="flex-grow-1">
<a href="#" class="text-gray-800 text-hover-primary fs-4 fw-bold">@(Post.Author.Username)</a> <span class="text-gray-800 text-hover-primary fs-4 fw-bold">@(Post.Author.Username)</span>
<span class="text-gray-500 fw-semibold d-block">@(Formatter.FormatAgoFromDateTime(Post.CreatedAt))</span> <span class="text-gray-500 fw-semibold d-block">@(Formatter.FormatAgoFromDateTime(Post.CreatedAt))</span>
</div> </div>
</div> </div>
<div class="card-toolbar">
</div> @if (Post.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
{
<div class="card-toolbar">
<a @onclick="DeletePost" @onclick:preventDefault href="#" class="text-danger fw-semibold d-block">Remove post</a>
</div>
}
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="fs-6 fw-normal text-gray-700"> <div class="fs-6 fw-normal text-gray-700">
@{ @if (IsEditing)
{
<TextEditor @bind-Value="EditContent" InitialContent="@Post.Content" />
}
else
{
var sanitizer = new HtmlSanitizer(); var sanitizer = new HtmlSanitizer();
var content = sanitizer.Sanitize(Post.Content); var content = sanitizer.Sanitize(Post.Content);
@((MarkupString)content) @((MarkupString)content)
} }
</div> </div>
@@ -49,6 +61,25 @@
@(LikesCount) Like(s) @(LikesCount) Like(s)
</a> </a>
</li> </li>
@if (Post.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
{
<li class="nav-item">
<div class="nav-link pt-0">
<a @onclick="() => ToggleEdit()" @onclick:preventDefault href="#" class="btn btn-sm btn-color-gray-600 btn-active-color-warning fw-bold px-4 pe-0 @(IsEditing ? "active" : "")">
<i class="bx bx-edit fs-2"></i>
@(IsEditing ? "Save" : "Edit")
</a>
@if (IsEditing)
{
<a @onclick="() => ToggleEdit(true)" @onclick:preventDefault href="#" class="btn btn-sm btn-color-gray-600 btn-active-color-warning fw-bold px-4 ps-2 @(IsEditing ? "active" : "")">
<i class="bx bx-x fs-2"></i>
Cancel
</a>
}
</div>
</li>
}
</ul> </ul>
<div class="separator separator-solid mb-1"></div> <div class="separator separator-solid mb-1"></div>
@if (ShowComments) @if (ShowComments)
@@ -66,6 +97,11 @@
<div class="d-flex align-items-center flex-wrap mb-0"> <div class="d-flex align-items-center flex-wrap mb-0">
<a href="#" class="text-gray-800 text-hover-primary fw-bold me-6">@(comment.Author.Username)</a> <a href="#" class="text-gray-800 text-hover-primary fw-bold me-6">@(comment.Author.Username)</a>
<span class="text-gray-500 fw-semibold fs-7 me-5">@(Formatter.FormatAgoFromDateTime(comment.CreatedAt))</span> <span class="text-gray-500 fw-semibold fs-7 me-5">@(Formatter.FormatAgoFromDateTime(comment.CreatedAt))</span>
@if (comment.Author.Id == IdentityService.CurrentUser.Id || IdentityService.Permissions[Permission.AdminCommunity])
{
<a @onclick="() => DeleteComment(comment)" @onclick:preventDefault href="#" class="text-danger fw-semibold fs-7">Remove comment</a>
}
</div> </div>
<span class="text-gray-800 fs-6 fw-normal pt-1"> <span class="text-gray-800 fs-6 fw-normal pt-1">
@(Formatter.FormatLineBreaks(comment.Content)) @(Formatter.FormatLineBreaks(comment.Content))
@@ -90,8 +126,8 @@
</div> </div>
<div class="position-relative w-100"> <div class="position-relative w-100">
<div class="input-group"> <div class="input-group">
<textarea @bind="Comment" type="text" class="form-control form-control-solid border ps-5" placeholder="Write your comment.."></textarea> <textarea @bind="Comment" type="text" class="form-control form-control-solid border ps-5" placeholder="Write your comment.." style="height: 1vh"></textarea>
<WButton OnClick="CreateComment" Text="Comment" CssClasses="btn btn-primary" /> <WButton OnClick="CreateComment" Text="Comment" CssClasses="btn btn-primary"/>
</div> </div>
</div> </div>
</div> </div>
@@ -103,6 +139,9 @@
[Parameter] [Parameter]
public Post Post { get; set; } public Post Post { get; set; }
[Parameter]
public Func<Task>? OnUpdate { get; set; }
private int CommentsCount = -1; private int CommentsCount = -1;
private int LikesCount = -1; private int LikesCount = -1;
private bool HasLiked = false; private bool HasLiked = false;
@@ -111,6 +150,10 @@
private PostComment[] Comments = Array.Empty<PostComment>(); private PostComment[] Comments = Array.Empty<PostComment>();
private string Comment = ""; private string Comment = "";
private bool IsEditing = false;
private string EditTitle = "";
private string EditContent = "";
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
@@ -161,6 +204,7 @@
Comment = ""; Comment = "";
ShowComments = true; ShowComments = true;
await LoadComments(null!);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await UpdateCounts(); await UpdateCounts();
} }
@@ -169,8 +213,11 @@
{ {
await PostService.DeleteComment(Post, comment); await PostService.DeleteComment(Post, comment);
await LoadComments(null!);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await UpdateCounts(); await UpdateCounts();
await ToastService.Success("Successfully deleted comment");
} }
private async Task ToggleComments() private async Task ToggleComments()
@@ -187,4 +234,31 @@
await PostService.ToggleLike(Post, IdentityService.CurrentUser); await PostService.ToggleLike(Post, IdentityService.CurrentUser);
await UpdateCounts(); await UpdateCounts();
} }
private async Task ToggleEdit(bool preventSaving = false)
{
IsEditing = !IsEditing;
if (IsEditing)
{
EditTitle = Post.Title;
EditContent = Post.Content;
}
else if (!preventSaving)
{
await PostService.Update(Post, EditTitle, EditContent);
await ToastService.Success("Successfully saved post");
}
await InvokeAsync(StateHasChanged);
}
private async Task DeletePost()
{
await PostService.Delete(Post);
await ToastService.Success("Successfully deleted post");
if (OnUpdate != null)
await OnUpdate.Invoke();
}
} }

View File

@@ -8,10 +8,10 @@
@inject Repository<Post> PostRepository @inject Repository<Post> PostRepository
<div class="row"> <div class="row">
<LazyLoader Load="Load"> <LazyLoader @ref="LazyLoader" Load="Load">
@foreach (var post in Posts) @foreach (var post in Posts)
{ {
<PostView Post="post" /> <PostView Post="post" OnUpdate="() => LazyLoader.Reload()" />
<div class="mb-10"></div> <div class="mb-10"></div>
} }
</LazyLoader> </LazyLoader>
@@ -19,6 +19,7 @@
@code @code
{ {
private LazyLoader LazyLoader;
private Post[] Posts; private Post[] Posts;
private Task Load(LazyLoader _) private Task Load(LazyLoader _)