From 86d01c98e0e8a24ba2908850033828fd57c77fcf Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sat, 26 Oct 2024 17:40:34 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=20feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9A=A9=20=EC=9A=94=EC=B2=AD=20=EC=A3=BC=EC=A0=9C(=ED=86=A0?= =?UTF-8?q?=ED=94=BD)=20=EC=83=9D=EC=84=B1/=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/category/CategoryType.java | 19 +- .../category/CategoryTypeConverter.java | 2 + .../topic/application/domain/Topic.java | 46 +++++ .../application/service/TopicService.java | 33 ++++ .../service/dto/ExposedTopicFindResponse.java | 60 +++++++ .../service/dto/TopicCreateRequest.java | 24 +++ .../topic/presentation/TopicController.java | 35 ++++ .../repository/CustomTopicRepository.java | 9 + .../repository/CustomTopicRepositoryImpl.java | 34 ++++ .../topic/repository/TopicRepository.java | 7 + .../com/listywave/common/IntegrationTest.java | 7 + .../application/service/TopicServiceTest.java | 169 ++++++++++++++++++ 12 files changed, 439 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/listywave/topic/application/domain/Topic.java create mode 100644 src/main/java/com/listywave/topic/application/service/TopicService.java create mode 100644 src/main/java/com/listywave/topic/application/service/dto/ExposedTopicFindResponse.java create mode 100644 src/main/java/com/listywave/topic/application/service/dto/TopicCreateRequest.java create mode 100644 src/main/java/com/listywave/topic/presentation/TopicController.java create mode 100644 src/main/java/com/listywave/topic/repository/CustomTopicRepository.java create mode 100644 src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java create mode 100644 src/main/java/com/listywave/topic/repository/TopicRepository.java create mode 100644 src/test/java/com/listywave/topic/application/service/TopicServiceTest.java diff --git a/src/main/java/com/listywave/list/application/domain/category/CategoryType.java b/src/main/java/com/listywave/list/application/domain/category/CategoryType.java index 47f69743..7cf7979a 100644 --- a/src/main/java/com/listywave/list/application/domain/category/CategoryType.java +++ b/src/main/java/com/listywave/list/application/domain/category/CategoryType.java @@ -2,7 +2,6 @@ import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND; -import com.fasterxml.jackson.annotation.JsonCreator; import com.listywave.common.exception.CustomException; import java.util.Arrays; import lombok.AllArgsConstructor; @@ -15,16 +14,18 @@ public enum CategoryType { ENTIRE("0", "전체"), MUSIC("1", "음악"), MOVIE_DRAMA("2", "영화&드라마"), - ENTERTAINMENT_ARTS("3","엔터&예술"), + ENTERTAINMENT_ARTS("3", "엔터&예술"), TRAVEL("4", "여행"), RESTAURANT_CAFE("5", "맛집&카페"), FOOD_RECIPES("6", "음식&레시피"), PLACE("7", "공간"), DAILYLIFE_THOUGHTS("8", "일상&생각"), HOBBY_LEISURE("9", "취미&레저"), - ETC("10", "기타") + ETC("10", "기타"), ; + private static final String ERROR_MESSAGE = "해당 카테고리는 존재하지 않습니다. 입력값: "; + private final String code; private final String viewName; @@ -32,14 +33,20 @@ public static CategoryType codeOf(String code) { return Arrays.stream(CategoryType.values()) .filter(t -> t.getCode().equals(code)) .findAny() - .orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND, "해당 카테고리코드는 존재하지 않습니다.")); + .orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND, ERROR_MESSAGE + code)); } - @JsonCreator public static CategoryType nameOf(String name) { return Arrays.stream(CategoryType.values()) .filter(categoryType -> categoryType.name().equalsIgnoreCase(name)) .findFirst() - .orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND, "해당 카테고리는 존재하지 않습니다.")); + .orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND, ERROR_MESSAGE + name)); + } + + public static CategoryType viewNameOf(String viewName) { + return Arrays.stream(CategoryType.values()) + .filter(categoryType -> categoryType.getViewName().equals(viewName)) + .findFirst() + .orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND, ERROR_MESSAGE + viewName)); } } diff --git a/src/main/java/com/listywave/list/application/domain/category/CategoryTypeConverter.java b/src/main/java/com/listywave/list/application/domain/category/CategoryTypeConverter.java index cab3022a..7dd6c340 100644 --- a/src/main/java/com/listywave/list/application/domain/category/CategoryTypeConverter.java +++ b/src/main/java/com/listywave/list/application/domain/category/CategoryTypeConverter.java @@ -1,7 +1,9 @@ package com.listywave.list.application.domain.category; import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +@Converter(autoApply = true) public class CategoryTypeConverter implements AttributeConverter { @Override diff --git a/src/main/java/com/listywave/topic/application/domain/Topic.java b/src/main/java/com/listywave/topic/application/domain/Topic.java new file mode 100644 index 00000000..bd2e0f40 --- /dev/null +++ b/src/main/java/com/listywave/topic/application/domain/Topic.java @@ -0,0 +1,46 @@ +package com.listywave.topic.application.domain; + +import static lombok.AccessLevel.PROTECTED; + +import com.listywave.common.BaseEntity; +import com.listywave.list.application.domain.category.CategoryType; +import com.listywave.list.application.domain.list.ListDescription; +import com.listywave.list.application.domain.list.ListTitle; +import com.listywave.user.application.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = PROTECTED) +@AllArgsConstructor +public class Topic extends BaseEntity { + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "topic_user_fk")) + private User user; + + @Column(name = "category_code", length = 10, nullable = false) + private CategoryType category; + + @Embedded + private ListTitle title; + + @Embedded + private ListDescription description; + + @Column(nullable = false) + private boolean isAnonymous; + + @Column(nullable = false) + private boolean isExposed; +} diff --git a/src/main/java/com/listywave/topic/application/service/TopicService.java b/src/main/java/com/listywave/topic/application/service/TopicService.java new file mode 100644 index 00000000..72aaea35 --- /dev/null +++ b/src/main/java/com/listywave/topic/application/service/TopicService.java @@ -0,0 +1,33 @@ +package com.listywave.topic.application.service; + +import com.listywave.topic.application.domain.Topic; +import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; +import com.listywave.topic.application.service.dto.TopicCreateRequest; +import com.listywave.topic.repository.TopicRepository; +import com.listywave.user.application.domain.User; +import com.listywave.user.repository.user.UserRepository; +import jakarta.annotation.Nullable; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class TopicService { + + private final UserRepository userRepository; + private final TopicRepository topicRepository; + + public void create(TopicCreateRequest request, Long userId) { + User user = userRepository.getById(userId); + Topic topic = request.toEntity(user); + topicRepository.save(topic); + } + + public ExposedTopicFindResponse findAllExposed(@Nullable Long cursorId, int size) { + List result = topicRepository.findAllExposed(cursorId, size); + return ExposedTopicFindResponse.of(result, size); + } +} diff --git a/src/main/java/com/listywave/topic/application/service/dto/ExposedTopicFindResponse.java b/src/main/java/com/listywave/topic/application/service/dto/ExposedTopicFindResponse.java new file mode 100644 index 00000000..07dff9bf --- /dev/null +++ b/src/main/java/com/listywave/topic/application/service/dto/ExposedTopicFindResponse.java @@ -0,0 +1,60 @@ +package com.listywave.topic.application.service.dto; + +import com.listywave.topic.application.domain.Topic; +import java.util.List; +import lombok.Builder; + +public record ExposedTopicFindResponse( + Long cursorId, + boolean hasNext, + List topics +) { + + public static ExposedTopicFindResponse of(List topics, int size) { + if (topics.isEmpty()) { + return new ExposedTopicFindResponse(null, false, List.of()); + } + + boolean hasNext = false; + if (topics.size() > size) { + hasNext = true; + topics.remove(topics.size() - 1); + } + Long cursorId = topics.get(topics.size() - 1).getId(); + List topicDtos = TopicDto.toList(topics); + + return new ExposedTopicFindResponse(cursorId, hasNext, topicDtos); + } + + @Builder + public record TopicDto( + Long id, + String categoryEngName, + String categoryKorName, + String title, + String description, + Long ownerId, + String ownerNickname, + boolean isAnonymous + ) { + + public static List toList(List topics) { + return topics.stream() + .map(TopicDto::of) + .toList(); + } + + public static TopicDto of(Topic topic) { + return TopicDto.builder() + .id(topic.getId()) + .categoryEngName(topic.getCategory().name()) + .categoryKorName(topic.getCategory().getViewName()) + .title(topic.getTitle().getValue()) + .description(topic.getDescription().getValue()) + .ownerId(topic.getUser().getId()) + .ownerNickname(topic.getUser().getNickname()) + .isAnonymous(topic.isAnonymous()) + .build(); + } + } +} diff --git a/src/main/java/com/listywave/topic/application/service/dto/TopicCreateRequest.java b/src/main/java/com/listywave/topic/application/service/dto/TopicCreateRequest.java new file mode 100644 index 00000000..db257919 --- /dev/null +++ b/src/main/java/com/listywave/topic/application/service/dto/TopicCreateRequest.java @@ -0,0 +1,24 @@ +package com.listywave.topic.application.service.dto; + +import com.listywave.list.application.domain.category.CategoryType; +import com.listywave.list.application.domain.list.ListDescription; +import com.listywave.list.application.domain.list.ListTitle; +import com.listywave.topic.application.domain.Topic; +import com.listywave.user.application.domain.User; + +public record TopicCreateRequest( + String categoryKorName, + String title, + String description, + boolean isAnonymous +) { + + public Topic toEntity(User user) { + return Topic.builder() + .user(user) + .category(CategoryType.viewNameOf(categoryKorName)) + .title(new ListTitle(title)) + .description(new ListDescription(description)) + .build(); + } +} diff --git a/src/main/java/com/listywave/topic/presentation/TopicController.java b/src/main/java/com/listywave/topic/presentation/TopicController.java new file mode 100644 index 00000000..11a70992 --- /dev/null +++ b/src/main/java/com/listywave/topic/presentation/TopicController.java @@ -0,0 +1,35 @@ +package com.listywave.topic.presentation; + +import com.listywave.common.auth.Auth; +import com.listywave.topic.application.service.TopicService; +import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; +import com.listywave.topic.application.service.dto.TopicCreateRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class TopicController { + + private final TopicService topicService; + + @PostMapping("/topics") + ResponseEntity create(@RequestBody TopicCreateRequest request, @Auth Long userId) { + topicService.create(request, userId); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/topics") + ResponseEntity findAllExposed( + @RequestParam(required = false) Long cursorId, + @RequestParam(defaultValue = "10") int size + ) { + ExposedTopicFindResponse result = topicService.findAllExposed(cursorId, size); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java b/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java new file mode 100644 index 00000000..f0faaa0f --- /dev/null +++ b/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java @@ -0,0 +1,9 @@ +package com.listywave.topic.repository; + +import com.listywave.topic.application.domain.Topic; +import java.util.List; + +public interface CustomTopicRepository { + + List findAllExposed(Long cursorId, int size); +} diff --git a/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java b/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java new file mode 100644 index 00000000..f0f200fa --- /dev/null +++ b/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java @@ -0,0 +1,34 @@ +package com.listywave.topic.repository; + +import static com.listywave.topic.application.domain.QTopic.topic; +import static com.listywave.user.application.domain.QUser.user; + +import com.listywave.topic.application.domain.Topic; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CustomTopicRepositoryImpl implements CustomTopicRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAllExposed(Long cursorId, int size) { + return queryFactory + .selectFrom(topic) + .join(user).on(topic.user.id.eq(user.id)) + .where( + cursorIdLowerThan(cursorId), + topic.isExposed.isTrue() + ) + .limit(size + 1) + .orderBy(topic.id.desc()) + .fetch(); + } + + private static BooleanExpression cursorIdLowerThan(Long cursorId) { + return cursorId == null ? null : topic.id.lt(cursorId); + } +} diff --git a/src/main/java/com/listywave/topic/repository/TopicRepository.java b/src/main/java/com/listywave/topic/repository/TopicRepository.java new file mode 100644 index 00000000..0723b390 --- /dev/null +++ b/src/main/java/com/listywave/topic/repository/TopicRepository.java @@ -0,0 +1,7 @@ +package com.listywave.topic.repository; + +import com.listywave.topic.application.domain.Topic; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TopicRepository extends JpaRepository, CustomTopicRepository { +} diff --git a/src/test/java/com/listywave/common/IntegrationTest.java b/src/test/java/com/listywave/common/IntegrationTest.java index 6a9515e7..4e2cfa4e 100644 --- a/src/test/java/com/listywave/common/IntegrationTest.java +++ b/src/test/java/com/listywave/common/IntegrationTest.java @@ -22,6 +22,8 @@ import com.listywave.list.repository.list.ListRepository; import com.listywave.list.repository.reply.ReplyRepository; import com.listywave.mention.MentionRepository; +import com.listywave.topic.application.service.TopicService; +import com.listywave.topic.repository.TopicRepository; import com.listywave.user.application.domain.User; import com.listywave.user.application.service.UserService; import com.listywave.user.repository.follow.FollowRepository; @@ -75,12 +77,17 @@ public abstract class IntegrationTest { protected FolderService folderService; @Autowired protected FollowRepository followRepository; + @Autowired + protected TopicService topicService; + @Autowired + protected TopicRepository topicRepository; protected User dh, js, ej, sy; protected ListEntity list; @BeforeEach void setUp() { + topicRepository.deleteAllInBatch(); followRepository.deleteAllInBatch(); collectionRepository.deleteAllInBatch(); alarmRepository.deleteAllInBatch(); diff --git a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java new file mode 100644 index 00000000..e6c8cdb9 --- /dev/null +++ b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java @@ -0,0 +1,169 @@ +package com.listywave.topic.application.service; + +import static com.listywave.list.application.domain.category.CategoryType.DAILYLIFE_THOUGHTS; +import static com.listywave.list.application.domain.category.CategoryType.MUSIC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.listywave.common.IntegrationTest; +import com.listywave.list.application.domain.list.ListDescription; +import com.listywave.list.application.domain.list.ListTitle; +import com.listywave.topic.application.domain.Topic; +import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; +import com.listywave.topic.application.service.dto.ExposedTopicFindResponse.TopicDto; +import com.listywave.topic.application.service.dto.TopicCreateRequest; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class TopicServiceTest extends IntegrationTest { + + @Nested + class 토픽_생성 { + + @Test + void 토픽을_생성한다() { + // given + TopicCreateRequest request = new TopicCreateRequest(DAILYLIFE_THOUGHTS.getViewName(), "제일 좋아하는 여자 아이돌 TOP3", "여러분들은 어떤 여돌을 가장 좋아하나요?", false); + Long userId = ej.getId(); + + // when + topicService.create(request, userId); + + // then + List result = topicRepository.findAll(); + Topic expect = Topic.builder() + .user(ej) + .title(new ListTitle("제일 좋아하는 여자 아이돌 TOP3")) + .description(new ListDescription("여러분들은 어떤 여돌을 가장 좋아하나요?")) + .category(DAILYLIFE_THOUGHTS) + .build(); + + assertAll( + () -> assertThat(result).hasSize(1), + () -> { + Topic topic = result.get(0); + assertThat(topic).usingRecursiveComparison() + .ignoringFields("id", "createdDate", "updatedDate") + .isEqualTo(expect); + assertThat(topic.isExposed()).isFalse(); + } + ); + } + } + + @Nested + class 토픽_조회 { + + @Test + void 노출이_승인된_토픽만_조회한다() { + // given + List topics = List.of( + new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("2"), new ListDescription("2"), false, false), + new Topic(dh, MUSIC, new ListTitle("3"), new ListDescription("3"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("4"), new ListDescription("4"), false, false), + new Topic(dh, MUSIC, new ListTitle("5"), new ListDescription("5"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("6"), new ListDescription("6"), false, false), + new Topic(dh, MUSIC, new ListTitle("7"), new ListDescription("7"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("8"), new ListDescription("8"), false, false), + new Topic(dh, MUSIC, new ListTitle("9"), new ListDescription("9"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("10"), new ListDescription("10"), false, false), + new Topic(dh, MUSIC, new ListTitle("11"), new ListDescription("11"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("12"), new ListDescription("12"), false, false), + new Topic(dh, MUSIC, new ListTitle("13"), new ListDescription("13"), false, true) // O + ); + topicRepository.saveAll(topics); + + // when + int size = 5; + ExposedTopicFindResponse result = topicService.findAllExposed(null, size); + + // then + assertAll( + () -> assertThat(result.hasNext()).isTrue(), + () -> { + List topicDtos = result.topics(); + + assertThat(result.cursorId()).isEqualTo(topicDtos.get(topicDtos.size() - 1).id()); + assertThat(topicDtos).extracting("title") + .isEqualTo(List.of("13", "11", "9", "7", "5")); + } + ); + } + + @Test + void cursorId가_null이고_size가_10일_때_노출이_승인된_토픽을_조회한다() { + // given + List topics = List.of( + new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), + new Topic(dh, MUSIC, new ListTitle("2"), new ListDescription("2"), false, true), + new Topic(dh, MUSIC, new ListTitle("3"), new ListDescription("3"), false, true), + new Topic(dh, MUSIC, new ListTitle("4"), new ListDescription("4"), false, true), + new Topic(dh, MUSIC, new ListTitle("5"), new ListDescription("5"), false, true), + new Topic(dh, MUSIC, new ListTitle("6"), new ListDescription("6"), false, true), + new Topic(dh, MUSIC, new ListTitle("7"), new ListDescription("7"), false, true), + new Topic(dh, MUSIC, new ListTitle("8"), new ListDescription("8"), false, true), + new Topic(dh, MUSIC, new ListTitle("9"), new ListDescription("9"), false, true), + new Topic(dh, MUSIC, new ListTitle("10"), new ListDescription("10"), false, true), + new Topic(dh, MUSIC, new ListTitle("11"), new ListDescription("11"), false, true), + new Topic(dh, MUSIC, new ListTitle("12"), new ListDescription("12"), false, true), + new Topic(dh, MUSIC, new ListTitle("13"), new ListDescription("13"), false, true) + ); + topicRepository.saveAll(topics); + + // when + int size = 10; + ExposedTopicFindResponse result = topicService.findAllExposed(null, size); + + // then + assertAll( + () -> assertThat(result.hasNext()).isTrue(), + () -> { + List topicDtos = result.topics(); + + assertThat(result.cursorId()).isEqualTo(topicDtos.get(topicDtos.size() - 1).id()); + assertThat(topicDtos).extracting("title") + .isEqualTo(List.of("13", "12", "11", "10", "9", "8", "7", "6", "5", "4")); + } + ); + } + + @Test + void cursorId가_5이고_size가_5일_때_노출이_승인된_토픽을_조회한다() { + // given + List topics = List.of( + new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), + new Topic(dh, MUSIC, new ListTitle("2"), new ListDescription("2"), false, true), + new Topic(dh, MUSIC, new ListTitle("3"), new ListDescription("3"), false, true), + new Topic(dh, MUSIC, new ListTitle("4"), new ListDescription("4"), false, true), + new Topic(dh, MUSIC, new ListTitle("5"), new ListDescription("5"), false, true), + new Topic(dh, MUSIC, new ListTitle("6"), new ListDescription("6"), false, true), + new Topic(dh, MUSIC, new ListTitle("7"), new ListDescription("7"), false, true), + new Topic(dh, MUSIC, new ListTitle("8"), new ListDescription("8"), false, true), + new Topic(dh, MUSIC, new ListTitle("9"), new ListDescription("9"), false, true), + new Topic(dh, MUSIC, new ListTitle("10"), new ListDescription("10"), false, true), + new Topic(dh, MUSIC, new ListTitle("11"), new ListDescription("11"), false, true), + new Topic(dh, MUSIC, new ListTitle("12"), new ListDescription("12"), false, true), + new Topic(dh, MUSIC, new ListTitle("13"), new ListDescription("13"), false, true) + ); + topicRepository.saveAll(topics); + + // when + ExposedTopicFindResponse result = topicService.findAllExposed(5L, 5); + + // then + assertAll( + () -> assertThat(result.hasNext()).isFalse(), + () -> { + List topicDtos = result.topics(); + + assertThat(result.cursorId()).isEqualTo(topicDtos.get(topicDtos.size() - 1).id()); + assertThat(topicDtos).extracting("title") + .isEqualTo(List.of("4", "3", "2", "1")); + } + ); + + } + } +} From a5eb37b74b316b1aa7dbf9627c647b1777ebbaca Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sat, 26 Oct 2024 17:40:55 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=20refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/listywave/list/application/domain/list/ListEntity.java | 3 --- .../com/listywave/list/application/service/ListService.java | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/main/java/com/listywave/list/application/domain/list/ListEntity.java b/src/main/java/com/listywave/list/application/domain/list/ListEntity.java index 8d35f98e..49b12ba2 100644 --- a/src/main/java/com/listywave/list/application/domain/list/ListEntity.java +++ b/src/main/java/com/listywave/list/application/domain/list/ListEntity.java @@ -12,13 +12,11 @@ import com.listywave.collaborator.application.domain.Collaborators; import com.listywave.common.exception.CustomException; import com.listywave.list.application.domain.category.CategoryType; -import com.listywave.list.application.domain.category.CategoryTypeConverter; import com.listywave.list.application.domain.item.Item; import com.listywave.list.application.domain.item.Items; import com.listywave.list.application.domain.label.Labels; import com.listywave.user.application.domain.User; import jakarta.persistence.Column; -import jakarta.persistence.Convert; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -56,7 +54,6 @@ public class ListEntity { private User user; @Column(name = "category_code", length = 10, nullable = false) - @Convert(converter = CategoryTypeConverter.class) private CategoryType category; @Embedded diff --git a/src/main/java/com/listywave/list/application/service/ListService.java b/src/main/java/com/listywave/list/application/service/ListService.java index 089ac073..9919f6c0 100644 --- a/src/main/java/com/listywave/list/application/service/ListService.java +++ b/src/main/java/com/listywave/list/application/service/ListService.java @@ -51,7 +51,6 @@ import java.util.List; import java.util.Set; import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -75,7 +74,6 @@ public class ListService { private final AlarmRepository alarmRepository; private final CollaboratorService collaboratorService; private final HistoryService historyService; - private final ApplicationEventPublisher applicationEventPublisher; public ListCreateResponse listCreate(ListCreateRequest request, Long loginUserId) { User user = userRepository.getById(loginUserId); From 3ed4a0c203dbb0f2e5bcd63eef73ca97167ec94d Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sat, 26 Oct 2024 17:44:31 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=20feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9A=A9=20=EC=9A=94=EC=B2=AD=20=EC=A3=BC=EC=A0=9C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=20URI=EB=A5=BC=20WhiteList?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/listywave/common/auth/AuthorizationInterceptor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java b/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java index 66cbe6c9..245f65b1 100644 --- a/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java +++ b/src/main/java/com/listywave/common/auth/AuthorizationInterceptor.java @@ -37,6 +37,7 @@ public class AuthorizationInterceptor implements HandlerInterceptor { new UriAndMethod("/categories", GET), new UriAndMethod("/users/basic-profile-image", GET), new UriAndMethod("/users/basic-background-image", GET), + new UriAndMethod("/topics", GET) }; private final JwtManager jwtManager; From ccd07f701c8bd53def068d4d1cd5fb0ab48f8add Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sat, 26 Oct 2024 17:50:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=20test:=20=ED=86=A0=ED=94=BD=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/TopicServiceTest.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java index e6c8cdb9..7cf2973d 100644 --- a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java +++ b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java @@ -32,20 +32,14 @@ class 토픽_생성 { // then List result = topicRepository.findAll(); - Topic expect = Topic.builder() - .user(ej) - .title(new ListTitle("제일 좋아하는 여자 아이돌 TOP3")) - .description(new ListDescription("여러분들은 어떤 여돌을 가장 좋아하나요?")) - .category(DAILYLIFE_THOUGHTS) - .build(); - assertAll( () -> assertThat(result).hasSize(1), () -> { Topic topic = result.get(0); - assertThat(topic).usingRecursiveComparison() - .ignoringFields("id", "createdDate", "updatedDate") - .isEqualTo(expect); + assertThat(topic.getCategory()).isEqualTo(DAILYLIFE_THOUGHTS); + assertThat(topic.getTitle().getValue()).isEqualTo("제일 좋아하는 여자 아이돌 TOP3"); + assertThat(topic.getDescription().getValue()).isEqualTo("여러분들은 어떤 여돌을 가장 좋아하나요?"); + assertThat(topic.isAnonymous()).isFalse(); assertThat(topic.isExposed()).isFalse(); } ); From 739763b0e92b5ae322f0c13a71a29a5e06ab2b00 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sun, 27 Oct 2024 14:34:03 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=20feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=EC=9A=A9=20=ED=86=A0=ED=94=BD=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#307)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/TopicService.java | 7 ++ .../service/dto/TopicFindResponse.java | 69 +++++++++++ .../topic/presentation/TopicController.java | 10 ++ .../repository/CustomTopicRepository.java | 2 + .../repository/CustomTopicRepositoryImpl.java | 22 +++- .../application/service/TopicServiceTest.java | 114 +++++++++++++++++- 6 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/listywave/topic/application/service/dto/TopicFindResponse.java diff --git a/src/main/java/com/listywave/topic/application/service/TopicService.java b/src/main/java/com/listywave/topic/application/service/TopicService.java index 72aaea35..1addf96e 100644 --- a/src/main/java/com/listywave/topic/application/service/TopicService.java +++ b/src/main/java/com/listywave/topic/application/service/TopicService.java @@ -3,6 +3,7 @@ import com.listywave.topic.application.domain.Topic; import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; import com.listywave.topic.application.service.dto.TopicCreateRequest; +import com.listywave.topic.application.service.dto.TopicFindResponse; import com.listywave.topic.repository.TopicRepository; import com.listywave.user.application.domain.User; import com.listywave.user.repository.user.UserRepository; @@ -30,4 +31,10 @@ public ExposedTopicFindResponse findAllExposed(@Nullable Long cursorId, int size List result = topicRepository.findAllExposed(cursorId, size); return ExposedTopicFindResponse.of(result, size); } + + public TopicFindResponse findAll(@Nullable Long cursorId, int size) { + List result = topicRepository.findAll(cursorId, size); + long totalCount = (topicRepository.count() / size) + 1; + return TopicFindResponse.from(result, size, totalCount); + } } diff --git a/src/main/java/com/listywave/topic/application/service/dto/TopicFindResponse.java b/src/main/java/com/listywave/topic/application/service/dto/TopicFindResponse.java new file mode 100644 index 00000000..c2ed981b --- /dev/null +++ b/src/main/java/com/listywave/topic/application/service/dto/TopicFindResponse.java @@ -0,0 +1,69 @@ +package com.listywave.topic.application.service.dto; + +import com.listywave.topic.application.domain.Topic; +import java.time.LocalDateTime; +import java.util.List; +import lombok.Builder; + +@Builder +public record TopicFindResponse( + boolean hasNext, + long totalCount, + Long cursorId, + List topics +) { + + public static TopicFindResponse from(List topics, int size, long totalCount) { + if (topics.isEmpty()) { + return new TopicFindResponse(false, totalCount, null, List.of()); + } + + boolean hasNext = false; + if (topics.size() > size) { + hasNext = true; + topics.remove(topics.size() - 1); + } + long cursorId = topics.get(topics.size() - 1).getId(); + + return TopicFindResponse.builder() + .hasNext(hasNext) + .totalCount(totalCount) + .cursorId(cursorId) + .topics(TopicDto.toList(topics)) + .build(); + } + + @Builder + public record TopicDto( + String categoryEngName, + String categoryKorName, + String title, + String description, + LocalDateTime createdDate, + Long ownerId, + String ownerNickname, + boolean isAnonymous, + boolean isExposed + ) { + + public static List toList(List topics) { + return topics.stream() + .map(TopicDto::of) + .toList(); + } + + private static TopicDto of(Topic topic) { + return TopicDto.builder() + .categoryEngName(topic.getCategory().name()) + .categoryKorName(topic.getCategory().getViewName()) + .title(topic.getTitle().getValue()) + .description(topic.getDescription().getValue()) + .createdDate(topic.getCreatedDate()) + .ownerId(topic.getUser().getId()) + .ownerNickname(topic.getUser().getNickname()) + .isAnonymous(topic.isAnonymous()) + .isExposed(topic.isExposed()) + .build(); + } + } +} diff --git a/src/main/java/com/listywave/topic/presentation/TopicController.java b/src/main/java/com/listywave/topic/presentation/TopicController.java index 11a70992..50dc5adf 100644 --- a/src/main/java/com/listywave/topic/presentation/TopicController.java +++ b/src/main/java/com/listywave/topic/presentation/TopicController.java @@ -4,6 +4,7 @@ import com.listywave.topic.application.service.TopicService; import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; import com.listywave.topic.application.service.dto.TopicCreateRequest; +import com.listywave.topic.application.service.dto.TopicFindResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -32,4 +33,13 @@ ResponseEntity findAllExposed( ExposedTopicFindResponse result = topicService.findAllExposed(cursorId, size); return ResponseEntity.ok(result); } + + @GetMapping("/admin/topics") + ResponseEntity findAll( + @RequestParam(required = false) Long cursorId, + @RequestParam(defaultValue = "5") int size + ) { + TopicFindResponse result = topicService.findAll(cursorId, size); + return ResponseEntity.ok(result); + } } diff --git a/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java b/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java index f0faaa0f..b1c8a43c 100644 --- a/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java +++ b/src/main/java/com/listywave/topic/repository/CustomTopicRepository.java @@ -6,4 +6,6 @@ public interface CustomTopicRepository { List findAllExposed(Long cursorId, int size); + + List findAll(Long cursorId, int size); } diff --git a/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java b/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java index f0f200fa..70a6d57f 100644 --- a/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java +++ b/src/main/java/com/listywave/topic/repository/CustomTopicRepositoryImpl.java @@ -6,6 +6,7 @@ import com.listywave.topic.application.domain.Topic; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.annotation.Nullable; import java.util.List; import lombok.RequiredArgsConstructor; @@ -15,7 +16,7 @@ public class CustomTopicRepositoryImpl implements CustomTopicRepository { private final JPAQueryFactory queryFactory; @Override - public List findAllExposed(Long cursorId, int size) { + public List findAllExposed(@Nullable Long cursorId, int size) { return queryFactory .selectFrom(topic) .join(user).on(topic.user.id.eq(user.id)) @@ -28,7 +29,24 @@ public List findAllExposed(Long cursorId, int size) { .fetch(); } - private static BooleanExpression cursorIdLowerThan(Long cursorId) { + private BooleanExpression cursorIdLowerThan(Long cursorId) { return cursorId == null ? null : topic.id.lt(cursorId); } + + @Override + public List findAll(@Nullable Long cursorId, int size) { + return queryFactory + .selectFrom(topic) + .join(user).on(topic.user.id.eq(user.id)) + .where( + cursorIdGreaterThan(cursorId) + ) + .limit(size + 1) + .orderBy(topic.id.asc()) + .fetch(); + } + + private BooleanExpression cursorIdGreaterThan(Long cursorId) { + return cursorId == null ? null : topic.id.gt(cursorId); + } } diff --git a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java index 7cf2973d..aaf4414c 100644 --- a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java +++ b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java @@ -12,6 +12,7 @@ import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; import com.listywave.topic.application.service.dto.ExposedTopicFindResponse.TopicDto; import com.listywave.topic.application.service.dto.TopicCreateRequest; +import com.listywave.topic.application.service.dto.TopicFindResponse; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -47,7 +48,7 @@ class 토픽_생성 { } @Nested - class 토픽_조회 { + class 사용자용_토픽_조회 { @Test void 노출이_승인된_토픽만_조회한다() { @@ -124,7 +125,7 @@ class 토픽_조회 { } @Test - void cursorId가_5이고_size가_5일_때_노출이_승인된_토픽을_조회한다() { + void cursorId가_뒤에서_다섯_번째고_size가_5일_때_노출이_승인된_토픽을_조회한다() { // given List topics = List.of( new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), @@ -144,20 +145,121 @@ class 토픽_조회 { topicRepository.saveAll(topics); // when - ExposedTopicFindResponse result = topicService.findAllExposed(5L, 5); + long cursorId = topics.get(8).getId(); + ExposedTopicFindResponse result = topicService.findAllExposed(cursorId, 5); // then assertAll( - () -> assertThat(result.hasNext()).isFalse(), + () -> assertThat(result.hasNext()).isTrue(), () -> { List topicDtos = result.topics(); - assertThat(result.cursorId()).isEqualTo(topicDtos.get(topicDtos.size() - 1).id()); + assertThat(result.cursorId()).isEqualTo(topics.get(3).getId()); assertThat(topicDtos).extracting("title") - .isEqualTo(List.of("4", "3", "2", "1")); + .isEqualTo(List.of("8", "7", "6", "5", "4")); } ); } } + + @Nested + class 관리자용_토픽_조회 { + + @Test + void cursorId가_null이고_size가_10일_때_모든_토픽을_조회한다() { + // given + List topics = List.of( + new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("2"), new ListDescription("2"), false, false), + new Topic(dh, MUSIC, new ListTitle("3"), new ListDescription("3"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("4"), new ListDescription("4"), false, false), + new Topic(dh, MUSIC, new ListTitle("5"), new ListDescription("5"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("6"), new ListDescription("6"), false, false), + new Topic(dh, MUSIC, new ListTitle("7"), new ListDescription("7"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("8"), new ListDescription("8"), false, false), + new Topic(dh, MUSIC, new ListTitle("9"), new ListDescription("9"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("10"), new ListDescription("10"), false, false), + new Topic(dh, MUSIC, new ListTitle("11"), new ListDescription("11"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("12"), new ListDescription("12"), false, false), + new Topic(dh, MUSIC, new ListTitle("13"), new ListDescription("13"), false, true) // O + ); + topicRepository.saveAll(topics); + + // when + TopicFindResponse result = topicService.findAll(null, 10); + + // then + assertAll( + () -> assertThat(result.hasNext()).isTrue(), + () -> assertThat(result.totalCount()).isEqualTo(2), + () -> assertThat(result.topics()).extracting("title") + .isEqualTo(List.of("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")) + ); + } + + @Test + void cursorId가_열_번째_ID이고_size가_10일_때_모든_토픽을_조회한다() { + // given + List topics = List.of( + new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("2"), new ListDescription("2"), false, false), + new Topic(dh, MUSIC, new ListTitle("3"), new ListDescription("3"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("4"), new ListDescription("4"), false, false), + new Topic(dh, MUSIC, new ListTitle("5"), new ListDescription("5"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("6"), new ListDescription("6"), false, false), + new Topic(dh, MUSIC, new ListTitle("7"), new ListDescription("7"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("8"), new ListDescription("8"), false, false), + new Topic(dh, MUSIC, new ListTitle("9"), new ListDescription("9"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("10"), new ListDescription("10"), false, false), + new Topic(dh, MUSIC, new ListTitle("11"), new ListDescription("11"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("12"), new ListDescription("12"), false, false), + new Topic(dh, MUSIC, new ListTitle("13"), new ListDescription("13"), false, true) // O + ); + topicRepository.saveAll(topics); + + // when + TopicFindResponse result = topicService.findAll(topics.get(9).getId(), 10); + + // then + assertAll( + () -> assertThat(result.hasNext()).isFalse(), + () -> assertThat(result.totalCount()).isEqualTo(2), + () -> assertThat(result.topics()).extracting("title") + .isEqualTo(List.of("11", "12", "13")) + ); + } + + @Test + void cursorId가_다섯_번째이고_size가_5일_때_모든_토픽을_조회한다() { + // given + List topics = List.of( + new Topic(dh, MUSIC, new ListTitle("1"), new ListDescription("1"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("2"), new ListDescription("2"), false, false), + new Topic(dh, MUSIC, new ListTitle("3"), new ListDescription("3"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("4"), new ListDescription("4"), false, false), + new Topic(dh, MUSIC, new ListTitle("5"), new ListDescription("5"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("6"), new ListDescription("6"), false, false), + new Topic(dh, MUSIC, new ListTitle("7"), new ListDescription("7"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("8"), new ListDescription("8"), false, false), + new Topic(dh, MUSIC, new ListTitle("9"), new ListDescription("9"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("10"), new ListDescription("10"), false, false), + new Topic(dh, MUSIC, new ListTitle("11"), new ListDescription("11"), false, true), // O + new Topic(dh, MUSIC, new ListTitle("12"), new ListDescription("12"), false, false), + new Topic(dh, MUSIC, new ListTitle("13"), new ListDescription("13"), false, true) // O + ); + topicRepository.saveAll(topics); + + // when + TopicFindResponse result = topicService.findAll(topics.get(4).getId(), 5); + + // then + assertAll( + () -> assertThat(result.hasNext()).isTrue(), + () -> assertThat(result.totalCount()).isEqualTo(3), + () -> assertThat(result.topics()).extracting("title") + .isEqualTo(List.of("6", "7", "8", "9", "10")) + ); + } + } } From f7f47c83b0209be0e9b53540cf6a770b43e0c03f Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sun, 27 Oct 2024 14:45:58 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=20feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=EC=9A=A9=20=EC=9A=94=EC=B2=AD=20=EC=A3=BC=EC=A0=9C(=ED=86=A0?= =?UTF-8?q?=ED=94=BD)=20=EC=88=98=EC=A0=95=20API=20=EA=B5=AC=ED=98=84=20(#?= =?UTF-8?q?307)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../topic/application/domain/Topic.java | 6 ++++++ .../application/service/TopicService.java | 8 ++++++++ .../topic/presentation/TopicController.java | 9 +++++++++ .../presentation/dto/TopicUpdateRequest.java | 8 ++++++++ .../topic/repository/TopicRepository.java | 7 +++++++ .../application/service/TopicServiceTest.java | 19 +++++++++++++++++++ 6 files changed, 57 insertions(+) create mode 100644 src/main/java/com/listywave/topic/presentation/dto/TopicUpdateRequest.java diff --git a/src/main/java/com/listywave/topic/application/domain/Topic.java b/src/main/java/com/listywave/topic/application/domain/Topic.java index bd2e0f40..e77fdb48 100644 --- a/src/main/java/com/listywave/topic/application/domain/Topic.java +++ b/src/main/java/com/listywave/topic/application/domain/Topic.java @@ -43,4 +43,10 @@ public class Topic extends BaseEntity { @Column(nullable = false) private boolean isExposed; + + public void update(boolean isExposed, CategoryType categoryType, String title) { + this.isExposed = isExposed; + this.category = categoryType; + this.title = new ListTitle(title); + } } diff --git a/src/main/java/com/listywave/topic/application/service/TopicService.java b/src/main/java/com/listywave/topic/application/service/TopicService.java index 1addf96e..68e287f1 100644 --- a/src/main/java/com/listywave/topic/application/service/TopicService.java +++ b/src/main/java/com/listywave/topic/application/service/TopicService.java @@ -1,5 +1,6 @@ package com.listywave.topic.application.service; +import com.listywave.list.application.domain.category.CategoryType; import com.listywave.topic.application.domain.Topic; import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; import com.listywave.topic.application.service.dto.TopicCreateRequest; @@ -27,14 +28,21 @@ public void create(TopicCreateRequest request, Long userId) { topicRepository.save(topic); } + @Transactional(readOnly = true) public ExposedTopicFindResponse findAllExposed(@Nullable Long cursorId, int size) { List result = topicRepository.findAllExposed(cursorId, size); return ExposedTopicFindResponse.of(result, size); } + @Transactional(readOnly = true) public TopicFindResponse findAll(@Nullable Long cursorId, int size) { List result = topicRepository.findAll(cursorId, size); long totalCount = (topicRepository.count() / size) + 1; return TopicFindResponse.from(result, size, totalCount); } + + public void update(Long topicId, boolean isExposed, String categoryCode, String title) { + Topic topic = topicRepository.getById(topicId); + topic.update(isExposed, CategoryType.codeOf(categoryCode), title); + } } diff --git a/src/main/java/com/listywave/topic/presentation/TopicController.java b/src/main/java/com/listywave/topic/presentation/TopicController.java index 50dc5adf..120a6005 100644 --- a/src/main/java/com/listywave/topic/presentation/TopicController.java +++ b/src/main/java/com/listywave/topic/presentation/TopicController.java @@ -5,10 +5,13 @@ import com.listywave.topic.application.service.dto.ExposedTopicFindResponse; import com.listywave.topic.application.service.dto.TopicCreateRequest; import com.listywave.topic.application.service.dto.TopicFindResponse; +import com.listywave.topic.presentation.dto.TopicUpdateRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -42,4 +45,10 @@ ResponseEntity findAll( TopicFindResponse result = topicService.findAll(cursorId, size); return ResponseEntity.ok(result); } + + @PutMapping("/admin/topics/{topicId}") + ResponseEntity update(@PathVariable Long topicId, @RequestBody TopicUpdateRequest request) { + topicService.update(topicId, request.isExposed(), request.categoryCode(), request.title()); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/listywave/topic/presentation/dto/TopicUpdateRequest.java b/src/main/java/com/listywave/topic/presentation/dto/TopicUpdateRequest.java new file mode 100644 index 00000000..133a5763 --- /dev/null +++ b/src/main/java/com/listywave/topic/presentation/dto/TopicUpdateRequest.java @@ -0,0 +1,8 @@ +package com.listywave.topic.presentation.dto; + +public record TopicUpdateRequest( + boolean isExposed, + String categoryCode, + String title +) { +} diff --git a/src/main/java/com/listywave/topic/repository/TopicRepository.java b/src/main/java/com/listywave/topic/repository/TopicRepository.java index 0723b390..841aa91d 100644 --- a/src/main/java/com/listywave/topic/repository/TopicRepository.java +++ b/src/main/java/com/listywave/topic/repository/TopicRepository.java @@ -1,7 +1,14 @@ package com.listywave.topic.repository; +import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND; + +import com.listywave.common.exception.CustomException; import com.listywave.topic.application.domain.Topic; import org.springframework.data.jpa.repository.JpaRepository; public interface TopicRepository extends JpaRepository, CustomTopicRepository { + + default Topic getById(Long id) { + return findById(id).orElseThrow(() -> new CustomException(RESOURCE_NOT_FOUND)); + } } diff --git a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java index aaf4414c..41f72fd0 100644 --- a/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java +++ b/src/test/java/com/listywave/topic/application/service/TopicServiceTest.java @@ -1,6 +1,7 @@ package com.listywave.topic.application.service; import static com.listywave.list.application.domain.category.CategoryType.DAILYLIFE_THOUGHTS; +import static com.listywave.list.application.domain.category.CategoryType.MOVIE_DRAMA; import static com.listywave.list.application.domain.category.CategoryType.MUSIC; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -262,4 +263,22 @@ class 관리자용_토픽_조회 { ); } } + + @Test + void 노출_여부와_타이틀과_카테고리_수정() { + // given + Topic topic = new Topic(dh, MUSIC, new ListTitle("origin"), new ListDescription("origin"), true, false); + topicRepository.save(topic); + + // when + topicService.update(topic.getId(), true, MOVIE_DRAMA.getCode(), "new"); + + // then + Topic result = topicRepository.getById(topic.getId()); + assertAll( + () -> assertThat(result.isExposed()).isTrue(), + () -> assertThat(result.getCategory()).isEqualTo(MOVIE_DRAMA), + () -> assertThat(result.getTitle()).isEqualTo(new ListTitle("new")) + ); + } }