diff --git a/src/main/java/com/recordit/server/controller/RecordController.java b/src/main/java/com/recordit/server/controller/RecordController.java index 92d41c8e..f9682159 100644 --- a/src/main/java/com/recordit/server/controller/RecordController.java +++ b/src/main/java/com/recordit/server/controller/RecordController.java @@ -19,6 +19,8 @@ import org.springframework.web.multipart.MultipartFile; import com.recordit.server.dto.record.ModifyRecordRequestDto; +import com.recordit.server.dto.record.RandomRecordRequestDto; +import com.recordit.server.dto.record.RandomRecordResponseDto; import com.recordit.server.dto.record.RecordByDateRequestDto; import com.recordit.server.dto.record.RecordByDateResponseDto; import com.recordit.server.dto.record.RecordDetailResponseDto; @@ -26,6 +28,7 @@ import com.recordit.server.dto.record.WriteRecordResponseDto; import com.recordit.server.dto.record.memory.MemoryRecordRequestDto; import com.recordit.server.dto.record.memory.MemoryRecordResponseDto; +import com.recordit.server.dto.record.mix.MixRecordResponseDto; import com.recordit.server.exception.ErrorMessage; import com.recordit.server.service.RecordService; @@ -171,4 +174,31 @@ public ResponseEntity modifyRecord( ) { return ResponseEntity.ok().body(recordService.modifyRecord(recordId, modifyRecordRequestDto, attachments)); } + + @ApiOperation( + value = "레코드 랜덤 조회", + notes = "레코드를 랜덤으로 조회합니다." + ) + @ApiResponses({ + @ApiResponse( + code = 200, message = "레코드 랜덤 조회 성공", + response = RandomRecordResponseDto.class + ), + @ApiResponse( + code = 400, + message = "잘못 된 요청", + response = ErrorMessage.class + ) + }) + @GetMapping("/random") + public ResponseEntity> getRandomRecord( + @ModelAttribute @Valid RandomRecordRequestDto randomRecordRequestDto + ) { + return ResponseEntity.ok(recordService.getRandomRecord(randomRecordRequestDto)); + } + + @GetMapping("/mix") + public ResponseEntity getMixRecords() { + return ResponseEntity.ok().body(recordService.getMixRecords()); + } } diff --git a/src/main/java/com/recordit/server/dto/record/RandomRecordRequestDto.java b/src/main/java/com/recordit/server/dto/record/RandomRecordRequestDto.java new file mode 100644 index 00000000..eac5c70a --- /dev/null +++ b/src/main/java/com/recordit/server/dto/record/RandomRecordRequestDto.java @@ -0,0 +1,26 @@ +package com.recordit.server.dto.record; + +import javax.validation.constraints.NotNull; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiParam; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@ApiModel +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +public class RandomRecordRequestDto { + @ApiParam(value = "카테고리 ID", required = true, example = "1") + @NotNull + private Long recordCategoryId; + + @ApiParam(value = "댓글 리스트의 사이즈", required = true, example = "5") + @NotNull + private Integer size; +} diff --git a/src/main/java/com/recordit/server/dto/record/RandomRecordResponseDto.java b/src/main/java/com/recordit/server/dto/record/RandomRecordResponseDto.java new file mode 100644 index 00000000..0608e651 --- /dev/null +++ b/src/main/java/com/recordit/server/dto/record/RandomRecordResponseDto.java @@ -0,0 +1,49 @@ +package com.recordit.server.dto.record; + +import com.recordit.server.domain.Record; + +import io.swagger.annotations.ApiParam; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +public class RandomRecordResponseDto { + @ApiParam(value = "레코드 ID", required = true) + private Long recordId; + + @ApiParam(value = "레코드 제목", required = true) + private String title; + + @ApiParam(value = "레코드 컬러명", required = true) + private String colorName; + + @ApiParam(value = "레코드 아이콘명", required = true) + private String iconName; + + @ApiParam(value = "댓글 개수", required = true) + private Long commentCount; + + private RandomRecordResponseDto(Long recordId, String title, String colorName, String iconName, Long commentCount) { + this.recordId = recordId; + this.title = title; + this.colorName = colorName; + this.iconName = iconName; + this.commentCount = commentCount; + } + + public static RandomRecordResponseDto of( + Record record, + Long commentCount + ) { + return new RandomRecordResponseDto( + record.getId(), + record.getTitle(), + record.getRecordColor().getName(), + record.getRecordIcon().getName(), + commentCount + ); + } +} diff --git a/src/main/java/com/recordit/server/dto/record/mix/MixRecordDto.java b/src/main/java/com/recordit/server/dto/record/mix/MixRecordDto.java new file mode 100644 index 00000000..f9fdb387 --- /dev/null +++ b/src/main/java/com/recordit/server/dto/record/mix/MixRecordDto.java @@ -0,0 +1,45 @@ +package com.recordit.server.dto.record.mix; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@ApiModel +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MixRecordDto { + @ApiModelProperty(notes = "믹스 레코드 아이디") + private Long recordId; + + @ApiModelProperty(notes = "믹스 레코드 색상이름") + private String colorName; + + @ApiModelProperty(notes = "믹스 레코드 아이콘이름") + private String iconName; + + @ApiModelProperty(notes = "믹스 레코드 댓글 아이디") + private Long commentId; + + @ApiModelProperty(notes = "믹스 레코드 댓글 내용") + private String commentContent; + + @Builder + public MixRecordDto( + Long recordId, + String colorName, + String iconName, + Long commentId, + String commentContent + ) { + this.recordId = recordId; + this.colorName = colorName; + this.iconName = iconName; + this.commentId = commentId; + this.commentContent = commentContent; + } +} diff --git a/src/main/java/com/recordit/server/dto/record/mix/MixRecordResponseDto.java b/src/main/java/com/recordit/server/dto/record/mix/MixRecordResponseDto.java new file mode 100644 index 00000000..7f32ee49 --- /dev/null +++ b/src/main/java/com/recordit/server/dto/record/mix/MixRecordResponseDto.java @@ -0,0 +1,25 @@ +package com.recordit.server.dto.record.mix; + +import java.util.List; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@ToString +@ApiModel +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MixRecordResponseDto { + @ApiModelProperty(notes = "믹스레코드 리스트") + private List mixRecordDto; + + @Builder + public MixRecordResponseDto(List mixRecordDto) { + this.mixRecordDto = mixRecordDto; + } +} diff --git a/src/main/java/com/recordit/server/exception/record/FixRecordNotExistException.java b/src/main/java/com/recordit/server/exception/record/FixRecordNotExistException.java new file mode 100644 index 00000000..474dbcb3 --- /dev/null +++ b/src/main/java/com/recordit/server/exception/record/FixRecordNotExistException.java @@ -0,0 +1,7 @@ +package com.recordit.server.exception.record; + +public class FixRecordNotExistException extends RuntimeException { + public FixRecordNotExistException(String message) { + super(message); + } +} diff --git a/src/main/java/com/recordit/server/exception/record/RecordExceptionHandler.java b/src/main/java/com/recordit/server/exception/record/RecordExceptionHandler.java index 8719a76c..a8ee3583 100644 --- a/src/main/java/com/recordit/server/exception/record/RecordExceptionHandler.java +++ b/src/main/java/com/recordit/server/exception/record/RecordExceptionHandler.java @@ -56,4 +56,11 @@ public ResponseEntity handleInvalidPageParameterException( return ResponseEntity.badRequest() .body(ErrorMessage.of(exception, HttpStatus.BAD_REQUEST)); } + + @ExceptionHandler(FixRecordNotExistException.class) + public ResponseEntity handleFixRecordNotExistException( + FixRecordNotExistException exception) { + return ResponseEntity.badRequest() + .body(ErrorMessage.of(exception, HttpStatus.BAD_REQUEST)); + } } diff --git a/src/main/java/com/recordit/server/repository/CommentRepository.java b/src/main/java/com/recordit/server/repository/CommentRepository.java index 4d10cff4..66d283dc 100644 --- a/src/main/java/com/recordit/server/repository/CommentRepository.java +++ b/src/main/java/com/recordit/server/repository/CommentRepository.java @@ -25,5 +25,10 @@ public interface CommentRepository extends JpaRepository { Long countAllByParentComment(Comment parentComment); - List findAllByRecord(Record record, Pageable pageable); + List findAllByRecordAndParentCommentIsNull(Record record, Pageable pageable); + + Long countByRecordId(Long recordId); + + @EntityGraph(attributePaths = {"record", "record.recordColor", "record.recordIcon"}) + List findByRecord(Record fixRecord); } diff --git a/src/main/java/com/recordit/server/repository/RecordRepository.java b/src/main/java/com/recordit/server/repository/RecordRepository.java index 839d90ce..75d641a6 100644 --- a/src/main/java/com/recordit/server/repository/RecordRepository.java +++ b/src/main/java/com/recordit/server/repository/RecordRepository.java @@ -1,6 +1,7 @@ package com.recordit.server.repository; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -52,4 +53,13 @@ Page findAllByWriterAndCreatedAtBetweenOrderByCreatedAtDesc( @Query("select r from RECORD r join fetch r.writer where r.id = :id") Optional findByIdFetchWriter(Long id); + + @Query(value = "select * from RECORD r " + + "where r.DELETED_AT is null " + + "and r.RECORD_CATEGORY_ID IN (" + + "select c.RECORD_CATEGORY_ID " + + "from RECORD_CATEGORY c where c.PARENT_RECORD_CATEGORY_ID = :categoryId" + + ") " + + "order by RAND() limit :size", nativeQuery = true) + List findRandomRecordByRecordCategoryId(Integer size, Long categoryId); } diff --git a/src/main/java/com/recordit/server/service/RecordService.java b/src/main/java/com/recordit/server/service/RecordService.java index c74b43d3..20daeeba 100644 --- a/src/main/java/com/recordit/server/service/RecordService.java +++ b/src/main/java/com/recordit/server/service/RecordService.java @@ -1,8 +1,10 @@ package com.recordit.server.service; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.stream.Collectors; import org.springframework.data.domain.Page; @@ -21,6 +23,8 @@ import com.recordit.server.domain.RecordColor; import com.recordit.server.domain.RecordIcon; import com.recordit.server.dto.record.ModifyRecordRequestDto; +import com.recordit.server.dto.record.RandomRecordRequestDto; +import com.recordit.server.dto.record.RandomRecordResponseDto; import com.recordit.server.dto.record.RecordByDateRequestDto; import com.recordit.server.dto.record.RecordByDateResponseDto; import com.recordit.server.dto.record.RecordDetailResponseDto; @@ -28,7 +32,10 @@ import com.recordit.server.dto.record.WriteRecordResponseDto; import com.recordit.server.dto.record.memory.MemoryRecordRequestDto; import com.recordit.server.dto.record.memory.MemoryRecordResponseDto; +import com.recordit.server.dto.record.mix.MixRecordDto; +import com.recordit.server.dto.record.mix.MixRecordResponseDto; import com.recordit.server.exception.member.MemberNotFoundException; +import com.recordit.server.exception.record.FixRecordNotExistException; import com.recordit.server.exception.record.NotMatchLoginUserWithRecordWriterException; import com.recordit.server.exception.record.RecordColorNotFoundException; import com.recordit.server.exception.record.RecordIconNotFoundException; @@ -52,6 +59,8 @@ @RequiredArgsConstructor public class RecordService { + private final int MIX_RECORD_COMMENT_SIZE = 10; + private final long FIX_RECORD_PK_VALUE = 31L; private final int FIRST_PAGE = 0; private final ImageFileRepository imageFileRepository; @@ -192,7 +201,7 @@ public MemoryRecordResponseDto getMemoryRecords(MemoryRecordRequestDto memoryRec // key findRecord, // value - commentRepository.findAllByRecord( + commentRepository.findAllByRecordAndParentCommentIsNull( findRecord, PageRequest.of( FIRST_PAGE, @@ -268,4 +277,53 @@ public Long modifyRecord( return record.modify(modifyRecordRequestDto, recordColor, recordIcon); } + + @Transactional + public List getRandomRecord( + RandomRecordRequestDto randomRecordRequestDto + ) { + if (!recordRepository.existsById(randomRecordRequestDto.getRecordCategoryId())) { + throw new RecordCategoryNotFoundException("카테고리 정보를 찾을 수 없습니다."); + } + + List recordList = recordRepository.findRandomRecordByRecordCategoryId( + randomRecordRequestDto.getSize(), + randomRecordRequestDto.getRecordCategoryId() + ); + + return recordList.stream() + .map(record -> RandomRecordResponseDto.of( + record, + commentRepository.countByRecordId(record.getId()) + )).collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public MixRecordResponseDto getMixRecords() { + Record fixRecord = recordRepository.findById(FIX_RECORD_PK_VALUE) + .orElseThrow(() -> new FixRecordNotExistException("서버에 고정 레코드가 존재하지 않습니다.")); + + List commentList = commentRepository.findByRecord(fixRecord).stream() + .map(comment -> MixRecordDto.builder() + .commentId(comment.getId()) + .colorName(comment.getRecord().getRecordColor().getName()) + .iconName(comment.getRecord().getRecordIcon().getName()) + .commentContent(comment.getContent()) + .recordId(comment.getRecord().getId()) + .build() + ).collect(Collectors.toList()); + + Random random = new Random(); + List randomCommentList = new ArrayList<>(); + + if (commentList.size() != 0) { + for (int i = 0; i < MIX_RECORD_COMMENT_SIZE; i++) { + randomCommentList.add(commentList.get(random.nextInt(commentList.size()))); + } + } + + return MixRecordResponseDto.builder() + .mixRecordDto(randomCommentList) + .build(); + } } diff --git a/src/test/java/com/recordit/server/service/RecordServiceTest.java b/src/test/java/com/recordit/server/service/RecordServiceTest.java index 6b8e7aa6..0595eb38 100644 --- a/src/test/java/com/recordit/server/service/RecordServiceTest.java +++ b/src/test/java/com/recordit/server/service/RecordServiceTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -24,15 +25,18 @@ import com.recordit.server.domain.RecordColor; import com.recordit.server.domain.RecordIcon; import com.recordit.server.dto.record.ModifyRecordRequestDto; +import com.recordit.server.dto.record.RandomRecordRequestDto; import com.recordit.server.dto.record.RecordByDateRequestDto; import com.recordit.server.dto.record.WriteRecordRequestDto; import com.recordit.server.dto.record.memory.MemoryRecordRequestDto; import com.recordit.server.exception.member.MemberNotFoundException; +import com.recordit.server.exception.record.FixRecordNotExistException; import com.recordit.server.exception.record.NotMatchLoginUserWithRecordWriterException; import com.recordit.server.exception.record.RecordColorNotFoundException; import com.recordit.server.exception.record.RecordIconNotFoundException; import com.recordit.server.exception.record.RecordNotFoundException; import com.recordit.server.exception.record.category.RecordCategoryNotFoundException; +import com.recordit.server.repository.CommentRepository; import com.recordit.server.repository.ImageFileRepository; import com.recordit.server.repository.MemberRepository; import com.recordit.server.repository.RecordCategoryRepository; @@ -71,6 +75,9 @@ class RecordServiceTest { @Mock private RecordRepository recordRepository; + @Mock + private CommentRepository commentRepository; + @Mock private Member mockMember; @@ -485,4 +492,68 @@ class 레코드를_수정_할_때 { .doesNotThrowAnyException(); } } -} \ No newline at end of file + + @Nested + @DisplayName("랜덤 레코드를 조회 할 때") + class 랜덤_레코드를_죠회_할_때 { + private final RandomRecordRequestDto randomRecordRequestDto = RandomRecordRequestDto + .builder() + .recordCategoryId(1L) + .size(5) + .build(); + + @Test + @DisplayName("레코드 카테고리를 찾지 못한다면 예외를 던진다") + void 레코드_카테고리를_찾지_못한다면_예외를_던진다() { + // when, then + assertThatThrownBy(() -> recordService.getRandomRecord(randomRecordRequestDto)) + .isInstanceOf(RecordCategoryNotFoundException.class) + .hasMessage("카테고리 정보를 찾을 수 없습니다."); + } + + @Test + @DisplayName("정상적이라면 예외를 던지지 않는다") + void 정상적이라면_예외를_던지지_않는다() { + // given + given(recordRepository.existsById(anyLong())) + .willReturn(true); + given(recordRepository.findRandomRecordByRecordCategoryId(any(), anyLong())) + .willReturn(new ArrayList<>()); + // when, then + assertThatCode(() -> recordService.getRandomRecord(randomRecordRequestDto)) + .doesNotThrowAnyException(); + } + } + + @Nested + @DisplayName("믹스_레코드를_조회_할_때") + class 믹스_레코드를_조회_할_때 { + @Test + @DisplayName("서버에 고정레코드가 존재하지 않을때 예외를 던진다") + void 서버에_고정레코드가_존재하지_않을때_예외를_던진다() { + //given + given(recordRepository.findById(anyLong())) + .willReturn(Optional.empty()); + + //when, then + assertThatThrownBy(() -> recordService.getMixRecords()) + .isInstanceOf(FixRecordNotExistException.class) + .hasMessage("서버에 고정 레코드가 존재하지 않습니다."); + } + + @Test + @DisplayName("정상적이라면 예외를 던지지 않는다") + void 정상적이라면_예외를_던지지_않는다() { + //given + given(recordRepository.findById(anyLong())) + .willReturn(Optional.of(mockRecord)); + + given(commentRepository.findByRecord(mockRecord)) + .willReturn(new ArrayList<>()); + + //when, then + assertThatCode(() -> recordService.getMixRecords()) + .doesNotThrowAnyException(); + } + } +}