post reactions

This commit is contained in:
john 2025-05-28 22:05:20 +02:00
parent d1687f276b
commit 8e8e4e2258
10 changed files with 124 additions and 8 deletions

View file

@ -0,0 +1,6 @@
using Femto.Common;
using Femto.Common.Domain;
namespace Femto.Modules.Blog.Application.Commands.AddPostReaction;
public record AddPostReactionCommand(Guid PostId, string Emoji, Guid ReactorId) : ICommand;

View file

@ -0,0 +1,21 @@
using Femto.Common.Domain;
using Microsoft.EntityFrameworkCore;
namespace Femto.Modules.Blog.Application.Commands.AddPostReaction;
internal class AddPostReactionCommandHandler(BlogContext context)
: ICommandHandler<AddPostReactionCommand>
{
public async Task Handle(AddPostReactionCommand request, CancellationToken cancellationToken)
{
var post = await context.Posts.SingleOrDefaultAsync(
p => p.Id == request.PostId,
cancellationToken
);
if (post is null)
return;
post.AddReaction(request.ReactorId, request.Emoji);
}
}

View file

@ -0,0 +1,6 @@
using Femto.Common;
using Femto.Common.Domain;
namespace Femto.Modules.Blog.Application.Commands.ClearPostReaction;
public record ClearPostReactionCommand(Guid PostId, string Emoji, Guid ReactorId): ICommand;

View file

@ -0,0 +1,22 @@
using Femto.Common.Domain;
using Femto.Modules.Blog.Application.Commands.AddPostReaction;
using Microsoft.EntityFrameworkCore;
namespace Femto.Modules.Blog.Application.Commands.ClearPostReaction;
internal class ClearPostReactionCommandHandler(BlogContext context)
: ICommandHandler<ClearPostReactionCommand>
{
public async Task Handle(ClearPostReactionCommand request, CancellationToken cancellationToken)
{
var post = await context.Posts.SingleOrDefaultAsync(
p => p.Id == request.PostId,
cancellationToken
);
if (post is null)
return;
post.RemoveReaction(request.ReactorId, request.Emoji);
}
}

View file

@ -25,9 +25,10 @@ internal class CreatePostCommandHandler(BlogContext context)
media.Height
))
.ToList()
);
post.IsPublic = request.IsPublic is true;
)
{
IsPublic = request.IsPublic is true
};
await context.AddAsync(post, cancellationToken);

View file

@ -10,10 +10,21 @@ internal class PostConfiguration : IEntityTypeConfiguration<Post>
{
table.ToTable("post");
table.OwnsMany(post => post.Media).WithOwner();
table.OwnsMany(post => post.Reactions).WithOwner();
table.Property<string>("PossibleReactionsJson")
.HasColumnName("possible_reactions");
table.OwnsMany(
post => post.Reactions,
reactions =>
{
reactions.WithOwner().HasForeignKey(r => r.PostId);
reactions.HasKey(r => new
{
r.PostId,
r.AuthorId,
r.Emoji,
});
}
);
table.Property<string>("PossibleReactionsJson").HasColumnName("possible_reactions");
table.Ignore(e => e.PossibleReactions);
}

View file

@ -14,7 +14,7 @@ internal class Post : Entity
public IList<PostReaction> Reactions { get; private set; } = [];
public bool IsPublic { get; set; }
public DateTimeOffset PostedOn { get; private set; }
private string PossibleReactionsJson { get; set; } = null!;
@ -38,4 +38,22 @@ internal class Post : Entity
this.AddDomainEvent(new PostCreated(this));
}
public void AddReaction(Guid reactorId, string emoji)
{
if (!this.PossibleReactions.Contains(emoji))
return;
if (this.Reactions.Any(r => r.AuthorId == reactorId && r.Emoji == emoji))
return;
this.Reactions.Add(new PostReaction(reactorId, this.Id, emoji));
}
public void RemoveReaction(Guid reactorId, string emoji)
{
this.Reactions = this
.Reactions.Where(r => r.AuthorId != reactorId || r.Emoji != emoji)
.ToList();
}
}