-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: develop
Are you sure you want to change the base?
Changes from all commits
443535b
2c83f45
6480448
3f32633
1ca7590
6b2a81e
4c4a70d
8496bd4
c85f96b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이부분 메서드 네임을 |
||
@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 |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package page.clab.api.domain.community.board.adapter.out.persistence; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.Table; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Setter; | ||
import org.hibernate.annotations.SQLDelete; | ||
import org.hibernate.annotations.SQLRestriction; | ||
import page.clab.api.global.common.domain.BaseEntity; | ||
|
||
@Entity | ||
@Getter | ||
@Setter | ||
@Builder | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@AllArgsConstructor(access = AccessLevel.PRIVATE) | ||
@SQLDelete(sql = "UPDATE board_hashtag SET is_deleted = true WHERE id = ?") | ||
@SQLRestriction("is_deleted = false") | ||
@Table(name = "board_hashtag") | ||
public class BoardHashtagJpaEntity extends BaseEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Column(name = "board_id", nullable = false) | ||
private Long boardId; | ||
|
||
@Column(name = "hashtag_id", nullable = false) | ||
private Long hashtagId; | ||
|
||
@Column(name = "is_deleted", nullable = false) | ||
private Boolean isDeleted; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package page.clab.api.domain.community.board.adapter.out.persistence; | ||
|
||
import org.mapstruct.Mapper; | ||
import page.clab.api.domain.community.board.domain.BoardHashtag; | ||
|
||
@Mapper(componentModel = "spring") | ||
public interface BoardHashtagMapper { | ||
|
||
BoardHashtagJpaEntity toEntity(BoardHashtag boardHashTag); | ||
|
||
BoardHashtag toDomain(BoardHashtagJpaEntity entity); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package page.clab.api.domain.community.board.adapter.out.persistence; | ||
|
||
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; | ||
import page.clab.api.domain.community.board.domain.BoardHashtag; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class BoardHashtagPersistenceAdapter implements | ||
RegisterBoardHashtagPort, RetrieveBoardHashtagPort { | ||
|
||
private final BoardHashtagRepository boardHashtagRepository; | ||
private final BoardHashtagMapper mapper; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 프로젝트의 |
||
@Override | ||
public BoardHashtag save(BoardHashtag boardHashtag) { | ||
BoardHashtagJpaEntity entity = mapper.toEntity(boardHashtag); | ||
BoardHashtagJpaEntity savedEntity = boardHashtagRepository.save(entity); | ||
return mapper.toDomain(savedEntity); | ||
} | ||
|
||
@Override | ||
public List<BoardHashtag> getAllByBoardId(Long boardId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전에 코드베이스 전반 검토할 때, |
||
return boardHashtagRepository.findAllByBoardId(boardId).stream() | ||
.map(mapper::toDomain) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
@Override | ||
public List<BoardHashtag> getAllIncludingDeletedByBoardId(Long boardId) { | ||
return boardHashtagRepository.findAllIncludingDeletedByBoardId(boardId).stream() | ||
.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 |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package page.clab.api.domain.community.board.adapter.out.persistence; | ||
|
||
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>, BoardHashtagRepositoryCustom, QuerydslPredicateExecutor<BoardHashtagJpaEntity> { | ||
|
||
List<BoardHashtagJpaEntity> findAllByBoardId(Long boardId); | ||
|
||
@Query(value = "SELECT b.* FROM board_hashtag b WHERE b.board_id = :boardId", nativeQuery = true) | ||
List<BoardHashtagJpaEntity> findAllIncludingDeletedByBoardId(Long boardId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Query(
value = "SELECT b.* " +
"FROM board_hashtag b " +
"WHERE b.board_id = :boardId",
nativeQuery = true
) 위 예시처럼 적절히 개행을 추가해주면 가독성 개선에 도움이 될 것 같아요. |
||
} |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기존에 사용하던 QueryDSL을 대신하여 JPQL을 선택하게 된 이유가 궁금해요. 간단히 설명 부탁드려도 될까요? |
||
|
||
@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 |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package page.clab.api.domain.community.board.application.dto.mapper; | ||
|
||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
import page.clab.api.domain.community.board.application.dto.request.BoardHashtagRequestDto; | ||
import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; | ||
import page.clab.api.domain.community.board.domain.BoardHashtag; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class BoardHashtagDtoMapper { | ||
|
||
public BoardHashtag fromDto(Long boardId, Long hashtagId){ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return BoardHashtag.builder() | ||
.boardId(boardId) | ||
.hashtagId(hashtagId) | ||
.isDeleted(false) | ||
.build(); | ||
} | ||
|
||
public BoardHashtagRequestDto toDto(Long boardId, List<Long> hashtagIdList) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return BoardHashtagRequestDto.builder() | ||
.boardId(boardId) | ||
.hashtagIdList(hashtagIdList) | ||
.build(); | ||
} | ||
|
||
public BoardHashtagResponseDto toDto(BoardHashtag boardHashtag, String name) { | ||
return BoardHashtagResponseDto.builder() | ||
.id(boardHashtag.getId()) | ||
.boardId(boardHashtag.getBoardId()) | ||
.name(name) | ||
.hashtagId(boardHashtag.getHashtagId()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package page.clab.api.domain.community.board.application.dto.request; | ||
|
||
import java.util.List; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
@Builder | ||
public class BoardHashtagRequestDto { | ||
|
||
private Long boardId; | ||
private List<Long> hashtagIdList; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
불필요한 클래스가 import되었어요. 제거 부탁드러요.