#16 board: hashtag service implementation
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user