#16 board: hashtag service implementation

This commit is contained in:
haerong22
2023-01-21 01:36:44 +09:00
parent 4a0a3489eb
commit 5e90ac5983
3 changed files with 86 additions and 13 deletions

View File

@@ -1,11 +1,13 @@
package com.example.board.service;
import com.example.board.domain.Article;
import com.example.board.domain.Hashtag;
import com.example.board.domain.UserAccount;
import com.example.board.domain.constant.SearchType;
import com.example.board.dto.ArticleDto;
import com.example.board.dto.ArticleWithCommentsDto;
import com.example.board.repository.ArticleRepository;
import com.example.board.repository.HashtagRepository;
import com.example.board.repository.UserAccountRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -17,6 +19,8 @@ import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
@@ -24,6 +28,8 @@ import java.util.List;
@Service
public class ArticleService {
private final HashtagService hashtagService;
private final HashtagRepository hashtagRepository;
private final ArticleRepository articleRepository;
private final UserAccountRepository userAccountRepository;
@@ -62,7 +68,28 @@ public class ArticleService {
public void saveArticle(ArticleDto dto) {
UserAccount userAccount = userAccountRepository.getReferenceById(dto.userAccountDto().userId());
articleRepository.save(dto.toEntity(userAccount));
Set<Hashtag> hashtags = renewHashtagsFromContent(dto.content());
Article article = dto.toEntity(userAccount);
article.addHashtags(hashtags);
articleRepository.save(article);
}
private Set<Hashtag> renewHashtagsFromContent(String content) {
Set<String> hashtagNamesInContent = hashtagService.parseHashtagNames(content);
Set<Hashtag> hashtags = hashtagService.findHashtagsByNames(hashtagNamesInContent);
Set<String> existingHashtagNames = hashtags.stream()
.map(Hashtag::getHashtagName)
.collect(Collectors.toUnmodifiableSet());
hashtagNamesInContent.forEach(newHashtagName -> {
if (!existingHashtagNames.contains(newHashtagName)) {
hashtags.add(Hashtag.of(newHashtagName));
}
});
return hashtags;
}
public void updateArticle(Long articleId, ArticleDto dto) {
@@ -73,6 +100,18 @@ public class ArticleService {
if (article.getUserAccount().equals(userAccount)) {
if (dto.title() != null) { article.setTitle(dto.title()); }
if (dto.content() != null) { article.setContent(dto.content()); }
Set<Long> hashtagIds = article.getHashtags().stream()
.map(Hashtag::getId)
.collect(Collectors.toUnmodifiableSet());
article.clearHashtags();
articleRepository.flush();
hashtagIds.forEach(hashtagService::deleteHashtagWithoutArticles);
Set<Hashtag> hashtags = renewHashtagsFromContent(dto.content());
article.addHashtags(hashtags);
}
} catch (EntityNotFoundException e) {
log.warn("게시글 업데이트 실패. 게시글을 수정하는데 필요한 정보를 찾을 수 없습니다 - {}", e.getLocalizedMessage());
@@ -80,7 +119,15 @@ public class ArticleService {
}
public void deleteArticle(long articleId, String userId) {
Article article = articleRepository.getReferenceById(articleId);
Set<Long> hashtagIds = article.getHashtags().stream()
.map(Hashtag::getId)
.collect(Collectors.toUnmodifiableSet());
articleRepository.deleteByIdAndUserAccount_UserId(articleId, userId);
articleRepository.flush();
hashtagIds.forEach(hashtagService::deleteHashtagWithoutArticles);
}
public long getArticleCount() {
@@ -88,16 +135,17 @@ public class ArticleService {
}
@Transactional(readOnly = true)
public Page<ArticleDto> searchArticlesViaHashtag(String hashtag, Pageable pageable) {
if (hashtag == null || hashtag.isBlank()) {
public Page<ArticleDto> searchArticlesViaHashtag(String hashtagName, Pageable pageable) {
if (hashtagName == null || hashtagName.isBlank()) {
return Page.empty(pageable);
}
return articleRepository.findByHashtagNames(null, pageable).map(ArticleDto::from);
return articleRepository.findByHashtagNames(List.of(hashtagName), pageable)
.map(ArticleDto::from);
}
public List<String> getHashtags() {
return articleRepository.findAllDistinctHashtags();
return hashtagRepository.findAllHashtagNames();
}
}

View File

@@ -1,20 +1,45 @@
package com.example.board.service;
import com.example.board.domain.Hashtag;
import com.example.board.repository.HashtagRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
@RequiredArgsConstructor
public class HashtagService {
private final HashtagRepository hashtagRepository;
public Set<String> parseHashtagNames(String content) {
return null;
if (content == null) {
return Set.of();
}
Pattern pattern = Pattern.compile("#[\\w가-힣]+");
Matcher matcher = pattern.matcher(content.strip());
Set<String> result = new HashSet<>();
while (matcher.find()) {
result.add(matcher.group().replace("#", ""));
}
return Set.copyOf(result);
}
public Set<Hashtag> findHashtagsByNames(Set<String> expectedHashtagNames) {
return null;
public Set<Hashtag> findHashtagsByNames(Set<String> hashtagNames) {
return new HashSet<>(hashtagRepository.findByHashtagNameIn(hashtagNames));
}
public void deleteHashtagWithoutArticles(Object any) {
public void deleteHashtagWithoutArticles(Long hashtagId) {
Hashtag hashtag = hashtagRepository.getReferenceById(hashtagId);
if (hashtag.getArticles().isEmpty()) {
hashtagRepository.delete(hashtag);
}
}
}

View File

@@ -8,7 +8,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -17,9 +16,10 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.*;
import static org.mockito.BDDMockito.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
@DisplayName("비즈니스 로직 - 해시태그")
@ExtendWith(MockitoExtension.class)