Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update the posts encryption status synchronously after deleting the category #1815

Merged
merged 2 commits into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package run.halo.app.event.category;

import java.util.Set;
import org.springframework.context.ApplicationEvent;
import org.springframework.lang.Nullable;
import run.halo.app.model.entity.Category;
Expand All @@ -15,13 +16,15 @@ public class CategoryUpdatedEvent extends ApplicationEvent {
private final Category beforeUpdated;
private final Category category;
private final boolean beforeIsPrivate;
private final Set<Integer> postIds;

public CategoryUpdatedEvent(Object source, Category category,
Category beforeUpdated, boolean beforeIsPrivate) {
Category beforeUpdated, boolean beforeIsPrivate, Set<Integer> postIds) {
super(source);
this.category = category;
this.beforeUpdated = beforeUpdated;
this.beforeIsPrivate = beforeIsPrivate;
this.postIds = postIds;
}

@Nullable
Expand All @@ -37,4 +40,8 @@ public Category getBeforeUpdated() {
public boolean isBeforeIsPrivate() {
return beforeIsPrivate;
}

public Set<Integer> getPostIds() {
return postIds;
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package run.halo.app.listener.post;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.event.EventListener;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import run.halo.app.event.category.CategoryUpdatedEvent;
import run.halo.app.event.post.PostUpdatedEvent;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Post;
import run.halo.app.model.entity.PostCategory;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.service.CategoryService;
import run.halo.app.service.PostCategoryService;
import run.halo.app.service.PostService;
import run.halo.app.utils.ServiceUtils;

/**
* Post status management.
Expand Down Expand Up @@ -53,86 +48,62 @@ public void categoryUpdatedListener(CategoryUpdatedEvent event) {
Category beforeUpdated = event.getBeforeUpdated();
boolean beforeIsPrivate = event.isBeforeIsPrivate();
RecordState recordState = determineRecordState(beforeUpdated, category);

List<Post> posts = postService.listAllByIds(event.getPostIds());

// handle delete action
if (RecordState.DELETED.equals(recordState) || category == null) {
if (beforeUpdated == null || !beforeIsPrivate) {
return;
}
// Cancel the encryption status of the posts
changeStatusToPublishIfNecessary(posts, beforeUpdated.getId());
return;
}

// now
boolean isPrivate = categoryService.isPrivate(category.getId());
List<Post> posts = findPostsByCategoryIdRecursively(category.getId());
if (isPrivate) {
List<Post> postsToUpdate = new ArrayList<>();
posts.forEach(post -> {
if (post.getStatus() == PostStatus.PUBLISHED) {
post.setStatus(PostStatus.INTIMATE);
postsToUpdate.add(post);
}
});
} else {
if (RecordState.UPDATED.equals(recordState)) {
Set<Integer> encryptedCategories =
pickUpEncryptedFromUpdatedRecord(category.getId());
for (Post post : posts) {
boolean belongsToEncryptedCategory =
postBelongsToEncryptedCategory(post.getId(), encryptedCategories);
if (!belongsToEncryptedCategory && StringUtils.isBlank(post.getPassword())
&& beforeIsPrivate
&& post.getStatus() == PostStatus.INTIMATE) {
post.setStatus(PostStatus.PUBLISHED);
}
}
postService.updateInBatch(postsToUpdate);
} else if (beforeIsPrivate && RecordState.UPDATED.equals(recordState)) {
// Cancel the encryption status of the posts
changeStatusToPublishIfNecessary(posts, category.getId());
}
}

private void changeStatusToPublishIfNecessary(List<Post> posts, Integer categoryId) {
List<Post> postsToUpdate = new ArrayList<>();
for (Post post : posts) {
boolean belongsToEncryptedCategory = postBelongsToEncryptedCategory(post.getId());
if (!belongsToEncryptedCategory && StringUtils.isBlank(post.getPassword())
&& post.getStatus() == PostStatus.INTIMATE) {
post.setStatus(PostStatus.PUBLISHED);
postsToUpdate.add(post);
}
}
postService.updateInBatch(posts);
postService.updateInBatch(postsToUpdate);
}

private boolean postBelongsToEncryptedCategory(Integer postId,
Set<Integer> encryptedCategories) {
Set<Integer> categoryIds =
postCategoryService.listCategoryIdsByPostId(postId);
private boolean postBelongsToEncryptedCategory(Integer postId) {
Set<Integer> categoryIds = postCategoryService.listCategoryIdsByPostId(postId);

boolean encrypted = false;
for (Integer categoryId : categoryIds) {
if (encryptedCategories.contains(categoryId)) {
if (categoryService.isPrivate(categoryId)) {
encrypted = true;
break;
}
}
return encrypted;
}

private Set<Integer> pickUpEncryptedFromUpdatedRecord(Integer categoryId) {
Set<Integer> privateCategories = new HashSet<>();

List<Category> categories = categoryService.listAllByParentId(categoryId);
Map<Integer, Category> categoryMap =
ServiceUtils.convertToMap(categories, Category::getId);
categories.forEach(category -> {
boolean privateBy = isPrivateBy(category.getId(), categoryMap);
if (privateBy) {
privateCategories.add(category.getId());
}
});
return privateCategories;
}

private boolean isPrivateBy(Integer categoryId, Map<Integer, Category> categoryMap) {
return findFirstEncryptedCategoryBy(categoryMap, categoryId) != null;
}

private Category findFirstEncryptedCategoryBy(Map<Integer, Category> idToCategoryMap,
Integer categoryId) {
Category category = idToCategoryMap.get(categoryId);

if (categoryId == 0 || category == null) {
return null;
}

if (StringUtils.isNotBlank(category.getPassword())) {
return category;
}

return findFirstEncryptedCategoryBy(idToCategoryMap, category.getParentId());
}

private RecordState determineRecordState(Category before, Category updated) {
if (before == null) {
if (updated != null) {
Expand All @@ -153,27 +124,16 @@ private RecordState determineRecordState(Category before, Category updated) {
}
}

/**
* effective state for database record.
*/
enum RecordState {
/**
* effective state for database record.
*/
CREATED,
UPDATED,
DELETED,
UNCHANGED
}

@NonNull
private List<Post> findPostsByCategoryIdRecursively(Integer categoryId) {
Set<Integer> categoryIds =
ServiceUtils.fetchProperty(categoryService.listAllByParentId(categoryId),
Category::getId);
List<PostCategory> postCategories =
postCategoryService.listByCategoryIdList(new ArrayList<>(categoryIds));
Set<Integer> postIds = ServiceUtils.fetchProperty(postCategories, PostCategory::getPostId);
return postService.listAllByIds(postIds);
}

/**
* If the post belongs to any encryption category, set the status to INTIMATE.
*
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/run/halo/app/service/CategoryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.domain.Sort;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -133,4 +134,13 @@ public interface CategoryService extends CrudService<Category, Integer> {
* @return a tree of category.
*/
List<CategoryVO> listToTree(List<Category> categories);

/**
* Recursively query the associated post ids according to the category id.
*
* @param categoryId category id
* @return a collection of post ids
*/
@NonNull
Set<Integer> listPostIdsByCategoryIdRecursively(@NonNull Integer categoryId);
}
29 changes: 25 additions & 4 deletions src/main/java/run/halo/app/service/impl/CategoryServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
Expand All @@ -28,6 +29,7 @@
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.dto.CategoryDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.PostCategory;
import run.halo.app.model.vo.CategoryVO;
import run.halo.app.repository.CategoryRepository;
import run.halo.app.service.CategoryService;
Expand Down Expand Up @@ -111,8 +113,10 @@ public Category update(Category category) {
boolean beforeIsPrivate = isPrivate(category.getId());

Category updated = super.update(category);

Set<Integer> postIds = listPostIdsByCategoryIdRecursively(category.getId());
applicationContext.publishEvent(
new CategoryUpdatedEvent(this, category, beforeUpdated, beforeIsPrivate));
new CategoryUpdatedEvent(this, category, beforeUpdated, beforeIsPrivate, postIds));
return updated;
}

Expand Down Expand Up @@ -183,8 +187,10 @@ public Category getByName(String name) {
}

@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public void removeCategoryAndPostCategoryBy(Integer categoryId) {
final boolean beforeIsPrivate = isPrivate(categoryId);
final Set<Integer> postIds = listPostIdsByCategoryIdRecursively(categoryId);
List<Category> categories = listByParentId(categoryId);
if (null != categories && categories.size() > 0) {
categories.forEach(category -> {
Expand All @@ -198,7 +204,8 @@ public void removeCategoryAndPostCategoryBy(Integer categoryId) {
// Remove post categories
postCategoryService.removeByCategoryId(categoryId);

applicationContext.publishEvent(new CategoryUpdatedEvent(this, null, category, false));
applicationContext.publishEvent(
new CategoryUpdatedEvent(this, null, category, beforeIsPrivate, postIds));
}

@Override
Expand Down Expand Up @@ -371,12 +378,26 @@ public List<Category> updateInBatch(Collection<Category> categories) {
Category categoryParam = idCategoryParamMap.get(categoryToUpdate.getId());
BeanUtils.updateProperties(categoryParam, categoryToUpdate);
Category categoryUpdated = update(categoryToUpdate);

Set<Integer> postIds = listPostIdsByCategoryIdRecursively(categoryUpdated.getId());
applicationContext.publishEvent(new CategoryUpdatedEvent(this,
categoryUpdated, categoryBefore, beforeIsPrivate));
categoryUpdated, categoryBefore, beforeIsPrivate, postIds));
return categoryUpdated;
})
.collect(Collectors.toList());
}

@NonNull
@Override
public Set<Integer> listPostIdsByCategoryIdRecursively(@NonNull Integer categoryId) {
Set<Integer> categoryIds = ServiceUtils.fetchProperty(listAllByParentId(categoryId),
Category::getId);
if (CollectionUtils.isEmpty(categoryIds)) {
return Collections.emptySet();
}
List<PostCategory> postCategories =
postCategoryService.listByCategoryIdList(new ArrayList<>(categoryIds));
return ServiceUtils.fetchProperty(postCategories, PostCategory::getPostId);
}

}