diff --git a/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java b/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java new file mode 100644 index 000000000..175d1817f --- /dev/null +++ b/application/src/main/java/run/halo/app/content/comment/AbstractCommentService.java @@ -0,0 +1,77 @@ +package run.halo.app.content.comment; + +import java.util.Set; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.util.Assert; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.User; +import run.halo.app.core.extension.content.Comment; +import run.halo.app.core.extension.content.Reply; +import run.halo.app.core.extension.service.RoleService; +import run.halo.app.core.extension.service.UserService; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.metrics.CounterService; +import run.halo.app.metrics.MeterUtils; +import run.halo.app.security.authorization.AuthorityUtils; + +@RequiredArgsConstructor +public abstract class AbstractCommentService { + protected final RoleService roleService; + protected final ReactiveExtensionClient client; + protected final UserService userService; + protected final CounterService counterService; + + protected Mono fetchCurrentUser() { + return ReactiveSecurityContextHolder.getContext() + .map(securityContext -> securityContext.getAuthentication().getName()) + .flatMap(username -> client.fetch(User.class, username)); + } + + Mono hasCommentManagePermission() { + return ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + var authentication = securityContext.getAuthentication(); + var roles = AuthorityUtils.authoritiesToRoles(authentication.getAuthorities()); + return roleService.contains(roles, + Set.of(AuthorityUtils.COMMENT_MANAGEMENT_ROLE_NAME)); + }); + } + + protected Comment.CommentOwner toCommentOwner(User user) { + Comment.CommentOwner owner = new Comment.CommentOwner(); + owner.setKind(User.KIND); + owner.setName(user.getMetadata().getName()); + owner.setDisplayName(user.getSpec().getDisplayName()); + return owner; + } + + protected Mono getOwnerInfo(Comment.CommentOwner owner) { + if (User.KIND.equals(owner.getKind())) { + return userService.getUserOrGhost(owner.getName()) + .map(OwnerInfo::from); + } + if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { + return Mono.just(OwnerInfo.from(owner)); + } + return Mono.error(new IllegalStateException("Unsupported owner kind: " + owner.getKind())); + } + + protected Mono fetchCommentStats(String commentName) { + return this.fetchStats(MeterUtils.nameOf(Comment.class, commentName)); + } + + protected Mono fetchReplyStats(String replyName) { + return this.fetchStats(MeterUtils.nameOf(Reply.class, replyName)); + } + + private Mono fetchStats(String meterName) { + Assert.notNull(meterName, "The reply must not be null."); + return counterService.getByName(meterName) + .map(counter -> CommentStats.builder() + .upvote(counter.getUpvote()) + .build() + ) + .switchIfEmpty(Mono.fromSupplier(CommentStats::empty)); + } +} diff --git a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java index d32931c14..a7510bdab 100644 --- a/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/CommentServiceImpl.java @@ -6,19 +6,16 @@ import static run.halo.app.extension.index.query.QueryFactory.isNull; import java.time.Duration; import java.time.Instant; -import java.util.Set; import java.util.function.Function; import org.apache.commons.lang3.BooleanUtils; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Sort; import org.springframework.lang.NonNull; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; -import run.halo.app.core.extension.User; import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.service.RoleService; import run.halo.app.core.extension.service.UserService; @@ -33,9 +30,7 @@ import run.halo.app.extension.router.selector.FieldSelector; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.exception.AccessDeniedException; import run.halo.app.metrics.CounterService; -import run.halo.app.metrics.MeterUtils; import run.halo.app.plugin.extensionpoint.ExtensionGetter; -import run.halo.app.security.authorization.AuthorityUtils; /** * Comment service implementation. @@ -44,28 +39,17 @@ import run.halo.app.security.authorization.AuthorityUtils; * @since 2.0.0 */ @Component -public class CommentServiceImpl implements CommentService { +public class CommentServiceImpl extends AbstractCommentService implements CommentService { - private final ReactiveExtensionClient client; - private final UserService userService; - private final RoleService roleService; private final ExtensionGetter extensionGetter; - private final SystemConfigurableEnvironmentFetcher environmentFetcher; - private final CounterService counterService; - public CommentServiceImpl(ReactiveExtensionClient client, - UserService userService, - SystemConfigurableEnvironmentFetcher environmentFetcher, - CounterService counterService, RoleService roleService, - ExtensionGetter extensionGetter - ) { - this.client = client; - this.userService = userService; - this.environmentFetcher = environmentFetcher; - this.counterService = counterService; - this.roleService = roleService; + public CommentServiceImpl(RoleService roleService, ReactiveExtensionClient client, + UserService userService, CounterService counterService, ExtensionGetter extensionGetter, + SystemConfigurableEnvironmentFetcher environmentFetcher) { + super(roleService, client, userService, counterService); this.extensionGetter = extensionGetter; + this.environmentFetcher = environmentFetcher; } @Override @@ -116,38 +100,36 @@ public class CommentServiceImpl implements CommentService { } comment.getSpec().setHidden(false); - - // return if the comment owner is not null - if (comment.getSpec().getOwner() != null) { - return Mono.just(comment); - } - // populate owner from current user - return fetchCurrentUser() - .flatMap(currentUser -> ReactiveSecurityContextHolder.getContext() - .flatMap(securityContext -> { - var authentication = securityContext.getAuthentication(); - var roles = AuthorityUtils.authoritiesToRoles( - authentication.getAuthorities()); - return roleService.contains(roles, - Set.of(AuthorityUtils.COMMENT_MANAGEMENT_ROLE_NAME)) - .doOnNext(result -> { - if (result) { - comment.getSpec().setApproved(true); - comment.getSpec().setApprovedTime(Instant.now()); - } - }) - .thenReturn(toCommentOwner(currentUser)); - })) - .map(owner -> { - comment.getSpec().setOwner(owner); - return comment; - }) - .switchIfEmpty( - Mono.error(new IllegalStateException("The owner must not be null."))); + return Mono.just(comment); }) + .flatMap(populatedComment -> Mono.when(populateOwner(populatedComment), + populateApproveState(populatedComment)) + .thenReturn(populatedComment) + ) .flatMap(client::create); } + private Mono populateApproveState(Comment comment) { + return hasCommentManagePermission() + .filter(Boolean::booleanValue) + .doOnNext(hasPermission -> { + comment.getSpec().setApproved(true); + comment.getSpec().setApprovedTime(Instant.now()); + }) + .then(); + } + + Mono populateOwner(Comment comment) { + if (comment.getSpec().getOwner() != null) { + return Mono.empty(); + } + return fetchCurrentUser() + .switchIfEmpty(Mono.error(new IllegalStateException("The owner must not be null."))) + .map(this::toCommentOwner) + .doOnNext(owner -> comment.getSpec().setOwner(owner)) + .then(); + } + @Override public Mono removeBySubject(@NonNull Ref subjectRef) { Assert.notNull(subjectRef, "The subjectRef must not be null."); @@ -197,55 +179,19 @@ public class CommentServiceImpl implements CommentService { return false; } - private Comment.CommentOwner toCommentOwner(User user) { - Comment.CommentOwner owner = new Comment.CommentOwner(); - owner.setKind(User.KIND); - owner.setName(user.getMetadata().getName()); - owner.setDisplayName(user.getSpec().getDisplayName()); - return owner; - } - - private Mono fetchCurrentUser() { - return ReactiveSecurityContextHolder.getContext() - .map(securityContext -> securityContext.getAuthentication().getName()) - .flatMap(username -> client.fetch(User.class, username)); - } - private Mono toListedComment(Comment comment) { var builder = ListedComment.builder().comment(comment); // not empty - var ownerInfoMono = getCommentOwnerInfo(comment.getSpec().getOwner()) + var ownerInfoMono = getOwnerInfo(comment.getSpec().getOwner()) .doOnNext(builder::owner); var subjectMono = getCommentSubject(comment.getSpec().getSubjectRef()) .doOnNext(builder::subject); - var statsMono = fetchStats(comment.getMetadata().getName()) + var statsMono = fetchCommentStats(comment.getMetadata().getName()) .doOnNext(builder::stats); return Mono.when(ownerInfoMono, subjectMono, statsMono) .then(Mono.fromSupplier(builder::build)); } - Mono fetchStats(String commentName) { - Assert.notNull(commentName, "The commentName must not be null."); - return counterService.getByName(MeterUtils.nameOf(Comment.class, commentName)) - .map(counter -> CommentStats.builder() - .upvote(counter.getUpvote()) - .build() - ) - .defaultIfEmpty(CommentStats.empty()); - } - - private Mono getCommentOwnerInfo(Comment.CommentOwner owner) { - if (User.KIND.equals(owner.getKind())) { - return userService.getUserOrGhost(owner.getName()) - .map(OwnerInfo::from); - } - if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { - return Mono.just(OwnerInfo.from(owner)); - } - throw new IllegalStateException( - "Unsupported owner kind: " + owner.getKind()); - } - @SuppressWarnings("unchecked") Mono getCommentSubject(Ref ref) { return extensionGetter.getExtensions(CommentSubject.class) diff --git a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java index 5c32c966f..a59bbd347 100644 --- a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java @@ -3,29 +3,22 @@ package run.halo.app.content.comment; import static run.halo.app.extension.index.query.QueryFactory.and; import static run.halo.app.extension.index.query.QueryFactory.equal; import static run.halo.app.extension.index.query.QueryFactory.isNull; -import static run.halo.app.security.authorization.AuthorityUtils.COMMENT_MANAGEMENT_ROLE_NAME; -import static run.halo.app.security.authorization.AuthorityUtils.authoritiesToRoles; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; -import java.util.Set; import java.util.function.Function; import java.util.function.UnaryOperator; -import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.reactivestreams.Publisher; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Sort; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.retry.Retry; -import run.halo.app.core.extension.User; import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.content.Reply; import run.halo.app.core.extension.service.RoleService; @@ -37,7 +30,6 @@ import run.halo.app.extension.PageRequestImpl; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.router.selector.FieldSelector; import run.halo.app.metrics.CounterService; -import run.halo.app.metrics.MeterUtils; /** * A default implementation of {@link ReplyService}. @@ -46,13 +38,12 @@ import run.halo.app.metrics.MeterUtils; * @since 2.0.0 */ @Service -@RequiredArgsConstructor -public class ReplyServiceImpl implements ReplyService { +public class ReplyServiceImpl extends AbstractCommentService implements ReplyService { - private final ReactiveExtensionClient client; - private final UserService userService; - private final RoleService roleService; - private final CounterService counterService; + public ReplyServiceImpl(RoleService roleService, ReactiveExtensionClient client, + UserService userService, CounterService counterService) { + super(roleService, client, userService, counterService); + } @Override public Mono create(String commentName, Reply reply) { @@ -74,6 +65,12 @@ public class ReplyServiceImpl implements ReplyService { } private Mono approveComment(Comment comment) { + return hasCommentManagePermission() + .filter(Boolean::booleanValue) + .flatMap(hasPermission -> doApproveComment(comment)); + } + + private Mono doApproveComment(Comment comment) { UnaryOperator updateFunc = commentToUpdate -> { commentToUpdate.getSpec().setApproved(true); commentToUpdate.getSpec().setApprovedTime(Instant.now()); @@ -85,6 +82,12 @@ public class ReplyServiceImpl implements ReplyService { } private Mono approveReply(String replyName) { + return hasCommentManagePermission() + .filter(Boolean::booleanValue) + .flatMap(hasPermission -> doApproveReply(replyName)); + } + + private Mono doApproveReply(String replyName) { return Mono.defer(() -> client.fetch(Reply.class, replyName) .flatMap(reply -> { reply.getSpec().setApproved(true); @@ -144,15 +147,6 @@ public class ReplyServiceImpl implements ReplyService { return Mono.when(steps).thenReturn(reply); } - Mono hasCommentManagePermission() { - return ReactiveSecurityContextHolder.getContext() - .map(SecurityContext::getAuthentication) - .flatMap(authentication -> { - var roles = authoritiesToRoles(authentication.getAuthorities()); - return roleService.contains(roles, Set.of(COMMENT_MANAGEMENT_ROLE_NAME)); - }); - } - @Override public Mono> list(ReplyQuery query) { return client.listBy(Reply.class, query.toListOptions(), query.toPageRequest()) @@ -209,52 +203,14 @@ public class ReplyServiceImpl implements ReplyService { private Mono toListedReply(Reply reply) { ListedReply.ListedReplyBuilder builder = ListedReply.builder() .reply(reply); - return getOwnerInfo(reply) + return getOwnerInfo(reply.getSpec().getOwner()) .map(ownerInfo -> { builder.owner(ownerInfo); return builder; }) .map(ListedReply.ListedReplyBuilder::build) - .flatMap(listedReply -> fetchStats(reply) + .flatMap(listedReply -> fetchReplyStats(reply.getMetadata().getName()) .doOnNext(listedReply::setStats) .thenReturn(listedReply)); } - - Mono fetchStats(Reply reply) { - Assert.notNull(reply, "The reply must not be null."); - String name = reply.getMetadata().getName(); - return counterService.getByName(MeterUtils.nameOf(Reply.class, name)) - .map(counter -> CommentStats.builder() - .upvote(counter.getUpvote()) - .build() - ) - .defaultIfEmpty(CommentStats.empty()); - } - - private Mono getOwnerInfo(Reply reply) { - Comment.CommentOwner owner = reply.getSpec().getOwner(); - if (User.KIND.equals(owner.getKind())) { - return userService.getUserOrGhost(owner.getName()) - .map(OwnerInfo::from); - } - if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { - return Mono.just(OwnerInfo.from(owner)); - } - throw new IllegalStateException( - "Unsupported owner kind: " + owner.getKind()); - } - - private Comment.CommentOwner toCommentOwner(User user) { - Comment.CommentOwner owner = new Comment.CommentOwner(); - owner.setKind(User.KIND); - owner.setName(user.getMetadata().getName()); - owner.setDisplayName(user.getSpec().getDisplayName()); - return owner; - } - - private Mono fetchCurrentUser() { - return ReactiveSecurityContextHolder.getContext() - .map(securityContext -> securityContext.getAuthentication().getName()) - .flatMap(username -> client.fetch(User.class, username)); - } }