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

feat: 개발질문 게시판의 해시태그 기능 구현 완료 #629

Merged
merged 27 commits into from
Dec 26, 2024
Merged
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
443535b
feat(Hashtag): Hashtag와 BoardHashtag 관련 entity와 domain 객체 설계
mingmingmon Dec 14, 2024
2c83f45
feat(HashtagRegister): 해시태그 등록 api 작성
mingmingmon Dec 14, 2024
6480448
feat(BoardRegister): 게시글 생성 시 개발질문 카테고리인 경우 해시태그 저장 로직 추가
mingmingmon Dec 14, 2024
3f32633
refactor(BoardDtoMapper): 게시판 관련 응답 시 해시태그 정보를 포함하도록 수정
mingmingmon Dec 14, 2024
1ca7590
feat(BoardUpdate): 게시글 수정 시 해시태그의 변경 로직 추가
mingmingmon Dec 14, 2024
6b2a81e
feat(BoardsByHashtagRetrievalController): 해시태그로 게시글 조회하는 API 작성
mingmingmon Dec 14, 2024
4c4a70d
fix(BoardsByHashtagRetrievalService): 잘못된 페이지네이션 수정
mingmingmon Dec 14, 2024
8496bd4
refactor(BoardRegisterController): 개발질문 게시판이 아닌 게시판에 해시태그 등록할 경우 예외발생…
mingmingmon Dec 14, 2024
c85f96b
fix(BoardRegisterService, BoardUpdateService): 개발질문 카테고리가 아닌 경우 해시태그 …
mingmingmon Dec 15, 2024
1527242
refactor(BoardHashtagPersistenceAdapter): repository 변수명 앞에 도메인명 삭제
mingmingmon Dec 22, 2024
0a53dd5
refactor(BoardHashtagRepository): SQL문에 개행추가, customRepository에서 JPQL…
mingmingmon Dec 22, 2024
991b9a1
refactor(ExternalBoardHashtag): external 패키지 아래에 위치했던 boardHashtag 관련…
mingmingmon Dec 22, 2024
32fe799
refactor(BoardRegisterService): 게시글 등록 시 해시태그를 표현할 수 있는 카테고리인지 검증하는 로…
mingmingmon Dec 22, 2024
8198678
refactor(BoardHashtagRegisterService): 존재하는 hashtag인지 판단하는 로직 메소드로 표현
mingmingmon Dec 22, 2024
2538b6a
refactor(BoardUpdateService): 복잡한 로직 메소드 추출 및 board update API에서 해시태그…
mingmingmon Dec 22, 2024
b01237d
refactor: 코드 컨벤션 준수하도록 수정
mingmingmon Dec 22, 2024
35f0975
refactor(HashtagRegisterController): 해시태그 등록 권한 수정, 응답 형식 수정 및 도메인 제약…
mingmingmon Dec 22, 2024
7fd88ae
refactor(BoardUpdateService): 사용하지 않는 로깅 코드 삭제
mingmingmon Dec 22, 2024
56b7c5e
feat(HashtagRetrievalController): 존재하는 해시태그 정보 조회하는 API 작성
mingmingmon Dec 22, 2024
b0103cf
refactor(BoardUpdateService): boardHashtag의 변화에 따른 hashtag의 boardUsag…
mingmingmon Dec 22, 2024
d92d201
refactor(HashtagPersistenceAdapter): 이름으로 해시태그를 찾을 때 없는 경우 notfound 예…
mingmingmon Dec 22, 2024
c682599
refactor(HashtagRetrievlaController): 권한 수정
mingmingmon Dec 22, 2024
7d925d3
refactor: 통일된 import문 정렬과 코드 정리 방식을 활용한 정리
mingmingmon Dec 22, 2024
df6ac3a
Merge branch 'develop' into feat/#609
mingmingmon Dec 22, 2024
be6d91d
refactor(HotBoardRetrievalService): 핫 게시글 응답에 HashtagInfo 필드 추가
mingmingmon Dec 22, 2024
fb49e64
refactor(BoardRegisterService): 중복된 해시태그 등록 가능 검사 삭제 및 코드컨벤션 준수
mingmingmon Dec 24, 2024
c33240f
refactor(BoardHashtagRepository): group by와 distinct 동시 사용 대신 group b…
mingmingmon Dec 24, 2024
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
Prev Previous commit
Next Next commit
feat(BoardsByHashtagRetrievalController): 해시태그로 게시글 조회하는 API 작성
mingmingmon committed Dec 14, 2024
commit 6b2a81e291b06f18a1ce28e12c0148dabfa1bb82
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByCategoryUseCase;
import page.clab.api.domain.community.board.domain.BoardCategory;
import page.clab.api.global.common.dto.ApiResponse;
@@ -33,15 +33,15 @@ public class BoardsByCategoryRetrievalController {
"DTO의 필드명을 기준으로 정렬 가능하며, 정렬 방향은 오름차순(asc)과 내림차순(desc)이 가능함")
@PreAuthorize("hasRole('GUEST')")
@GetMapping("/category")
public ApiResponse<PagedResponseDto<BoardCategoryResponseDto>> retrieveBoardsByCategory(
public ApiResponse<PagedResponseDto<BoardOverviewResponseDto>> retrieveBoardsByCategory(
@RequestParam(name = "category") BoardCategory category,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "sortBy", defaultValue = "createdAt") List<String> sortBy,
@RequestParam(name = "sortDirection", defaultValue = "desc") List<String> sortDirection
) throws SortingArgumentException, InvalidColumnException {
Pageable pageable = pageableUtils.createPageable(page, size, sortBy, sortDirection, BoardCategoryResponseDto.class);
PagedResponseDto<BoardCategoryResponseDto> boards = retrieveBoardsByCategoryUseCase.retrieveBoardsByCategory(category, pageable);
Pageable pageable = pageableUtils.createPageable(page, size, sortBy, sortDirection, BoardOverviewResponseDto.class);
PagedResponseDto<BoardOverviewResponseDto> boards = retrieveBoardsByCategoryUseCase.retrieveBoardsByCategory(category, pageable);
return ApiResponse.success(boards);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package page.clab.api.domain.community.board.adapter.in.web;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByHashtagUseCase;
import page.clab.api.global.common.dto.ApiResponse;
import page.clab.api.global.common.dto.PagedResponseDto;
import page.clab.api.global.exception.InvalidColumnException;
import page.clab.api.global.exception.SortingArgumentException;
import page.clab.api.global.util.PageableUtils;

@RestController
@RequestMapping("/api/v1/boards")
@RequiredArgsConstructor
@Tag(name = "Community - Board", description = "커뮤니티 게시판")
public class BoardsByHashtagRetrievalController {

private final RetrieveBoardsByHashtagUseCase retrieveBoardsByHashtagUseCase;
private final PageableUtils pageableUtils;

@Operation(summary = "[G] 커뮤니티 게시글 해시태그로 조회", description = "ROLE_GUEST 이상의 권한이 필요함<br>" +
"DTO의 필드명을 기준으로 정렬 가능하며, 정렬 방향은 오름차순(asc)과 내림차순(desc)이 가능함<br>" +
"현재는 카테고리가 개발질문인 게시글만 해시태그가 적용되어 있어서 해당 API의 응답으로 개발질문 게시판만 반환됨")
@PreAuthorize("hasRole('GUEST')")
@GetMapping("/hashtag")
public ApiResponse<PagedResponseDto<BoardOverviewResponseDto>> retrieveBoardsByCategory(
@RequestParam(name = "hashtags") List<String> hashtags,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "20") int size,
@RequestParam(name = "sortBy", defaultValue = "createdAt") List<String> sortBy,
@RequestParam(name = "sortDirection", defaultValue = "desc") List<String> sortDirection
) throws SortingArgumentException, InvalidColumnException {
Pageable pageable = pageableUtils.createPageable(page, size, sortBy, sortDirection, BoardOverviewResponseDto.class);
PagedResponseDto<BoardOverviewResponseDto> boards = retrieveBoardsByHashtagUseCase.retrieveBoardsByHashtag(hashtags, pageable);
return ApiResponse.success(boards);
}
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort;
import page.clab.api.domain.community.board.application.port.out.RetrieveBoardHashtagPort;
@@ -35,4 +37,8 @@ public List<BoardHashtag> getAllIncludingDeletedByBoardId(Long boardId) {
.map(mapper::toDomain)
.collect(Collectors.toList());
}

public List<Long> getBoardIdsByHashTagId(List<Long> hashtagIds, Pageable pageable) {
return boardHashtagRepository.getBoardIdsByHashTagId(hashtagIds, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -3,10 +3,11 @@
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface BoardHashtagRepository extends JpaRepository<BoardHashtagJpaEntity, Long> {
public interface BoardHashtagRepository extends JpaRepository<BoardHashtagJpaEntity, Long>, BoardHashtagRepositoryCustom, QuerydslPredicateExecutor<BoardHashtagJpaEntity> {

List<BoardHashtagJpaEntity> findAllByBoardId(Long boardId);

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package page.clab.api.domain.community.board.adapter.out.persistence;

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface BoardHashtagRepositoryCustom {
List<Long> getBoardIdsByHashTagId(List<Long> hashtagIds, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package page.clab.api.domain.community.board.adapter.out.persistence;

import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class BoardHashtagRepositoryImpl implements BoardHashtagRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public List<Long> getBoardIdsByHashTagId(List<Long> hashtagIds, Pageable pageable) {
QBoardHashtagJpaEntity boardHashtag = QBoardHashtagJpaEntity.boardHashtagJpaEntity;

JPQLQuery<Long> query = queryFactory.selectDistinct(boardHashtag.boardId)
.from(boardHashtag)
.where(boardHashtag.hashtagId.in(hashtagIds)
.and(boardHashtag.isDeleted.eq(false)))
.groupBy(boardHashtag.boardId)
.having(boardHashtag.hashtagId.count().eq((long) hashtagIds.size()));

return query.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import page.clab.api.domain.community.board.application.dto.request.BoardRequestDto;
import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardDetailsResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardEmojiCountResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto;
@@ -81,9 +81,9 @@ public BoardCommentInfoDto toDto(Board board) {
.build();
}

public BoardCategoryResponseDto toCategoryDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount, List<BoardHashtagResponseDto> boardHashtagInfos) {
public BoardOverviewResponseDto toCategoryDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount, List<BoardHashtagResponseDto> boardHashtagInfos) {
WriterInfo writerInfo = create(board, memberInfo);
return BoardCategoryResponseDto.builder()
return BoardOverviewResponseDto.builder()
.id(board.getId())
.category(board.getCategory().getKey())
.writerId(writerInfo.getId())
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package page.clab.api.domain.community.board.application.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
@@ -9,7 +8,7 @@

@Getter
@Builder
public class BoardCategoryResponseDto {
public class BoardOverviewResponseDto {

private Long id;
private String category;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package page.clab.api.domain.community.board.application.port.in;

import org.springframework.data.domain.Pageable;
import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.domain.community.board.domain.BoardCategory;
import page.clab.api.global.common.dto.PagedResponseDto;

public interface RetrieveBoardsByCategoryUseCase {
PagedResponseDto<BoardCategoryResponseDto> retrieveBoardsByCategory(BoardCategory category, Pageable pageable);
PagedResponseDto<BoardOverviewResponseDto> retrieveBoardsByCategory(BoardCategory category, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package page.clab.api.domain.community.board.application.port.in;

import java.util.List;
import org.springframework.data.domain.Pageable;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.global.common.dto.PagedResponseDto;

public interface RetrieveBoardsByHashtagUseCase {
PagedResponseDto<BoardOverviewResponseDto> retrieveBoardsByHashtag(List<String> hashtags, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package page.clab.api.domain.community.board.application.port.out;

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import page.clab.api.domain.community.board.domain.BoardHashtag;

public interface RetrieveBoardHashtagPort {
List<BoardHashtag> getAllByBoardId(Long boardId);

List<BoardHashtag> getAllIncludingDeletedByBoardId(Long boardId);

List<Long> getBoardIdsByHashTagId(List<Long> hashtagIds, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper;
import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto;
import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByCategoryUseCase;
import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort;
@@ -31,7 +31,7 @@ public class BoardsByCategoryRetrievalService implements RetrieveBoardsByCategor

@Transactional
@Override
public PagedResponseDto<BoardCategoryResponseDto> retrieveBoardsByCategory(BoardCategory category, Pageable pageable) {
public PagedResponseDto<BoardOverviewResponseDto> retrieveBoardsByCategory(BoardCategory category, Pageable pageable) {
Page<Board> boards = retrieveBoardPort.findAllByCategory(category, pageable);
return new PagedResponseDto<>(boards.map(board -> {
long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId());
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package page.clab.api.domain.community.board.application.service;

import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper;
import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto;
import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto;
import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByHashtagUseCase;
import page.clab.api.domain.community.board.domain.Board;
import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto;
import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase;
import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardUseCase;
import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase;
import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
import page.clab.api.global.common.dto.PagedResponseDto;

@Service
@RequiredArgsConstructor
public class BoardsByHashtagRetrievalService implements RetrieveBoardsByHashtagUseCase {

private final ExternalRetrieveHashtagUseCase externalRetrieveHashtagUseCase;
private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase;
private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase;
private final ExternalRetrieveBoardHashtagUseCase externalRetrieveBoardHashtagUseCase;
private final ExternalRetrieveBoardUseCase externalRetrieveBoardUseCase;
private final BoardDtoMapper mapper;

@Override
public PagedResponseDto<BoardOverviewResponseDto> retrieveBoardsByHashtag(List<String> hashtags, Pageable pageable) {
List<Long> hashtagIds = new ArrayList<>();
for (String hashtag : hashtags) {
hashtagIds.add(externalRetrieveHashtagUseCase.getByName(hashtag).getId());
}

List<Long> boardIds = externalRetrieveBoardHashtagUseCase.getBoardIdsByHashTagId(hashtagIds, pageable);

List<Board> boards = boardIds.stream()
.map(externalRetrieveBoardUseCase::getById)
.toList();

Page<Board> boardPage = new PageImpl<>(boards, pageable, boardIds.size());

return new PagedResponseDto<>(boardPage.map(board -> {
long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId());
List<BoardHashtagResponseDto> boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId());
return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount, boardHashtagInfos);
}));
}

private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) {
return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package page.clab.api.external.community.board.application.port;

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto;
import page.clab.api.domain.community.board.domain.BoardHashtag;

@@ -13,4 +15,5 @@ public interface ExternalRetrieveBoardHashtagUseCase {

List<BoardHashtag> getAllIncludingDeletedByBoardId(Long boardId);

List<Long> getBoardIdsByHashTagId(List<Long> hashtagIds, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper;
import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto;
@@ -46,4 +47,9 @@ public List<Long> extractAllHashtagId(List<BoardHashtag> boardHashtagList) {
public List<BoardHashtag> getAllIncludingDeletedByBoardId(Long boardId) {
return retrieveBoardHashtagPort.getAllIncludingDeletedByBoardId(boardId);
}

@Override
public List<Long> getBoardIdsByHashTagId(List<Long> hashtagIds, Pageable pageable) {
return retrieveBoardHashtagPort.getBoardIdsByHashTagId(hashtagIds, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -6,4 +6,6 @@ public interface ExternalRetrieveHashtagUseCase {
Hashtag getById(Long id);

Boolean existById(Long id);

Hashtag getByName(String name);
}
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import page.clab.api.domain.community.board.domain.BoardHashtag;
import page.clab.api.domain.community.hashtag.application.port.in.RetrieveHashtagUseCase;
import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort;
import page.clab.api.domain.community.hashtag.domain.Hashtag;
import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase;
@@ -12,6 +12,7 @@
@RequiredArgsConstructor
public class ExternalHashtagRetrievalService implements ExternalRetrieveHashtagUseCase {

private final RetrieveHashtagUseCase retrieveHashtagUseCase;
private final RetrieveHashtagPort retrieveHashtagPort;

@Override
@@ -23,4 +24,9 @@ public Hashtag getById(Long id) {
public Boolean existById(Long id) {
return retrieveHashtagPort.existsById(id);
}

@Override
public Hashtag getByName(String name) {
return retrieveHashtagUseCase.getByName(name);
}
}