Skip to content

Commit

Permalink
feat: 커서 페이지네이션 이용한 피드 전체리스트 API 구현 (#53)
Browse files Browse the repository at this point in the history
* feature: 대소문자 구분 없이 category value 담아서 요청 시 대문자 변환 처리 (#27)

* feature: 커서 페이지네이션 이용한 피드 전체리스트 API 구현 (#27)

* refactor: 콜라보레이터 최대 20명까지 가능하도록 수정 (#27)

* refactor: 충돌 해결을 위한 코드 일부 수정 (#27)

* refactor: 코드 컨벤션 및 코드 메서드화 적용 (#27)

* refactor: of 메서드 Builder 적용 및 컨벤션 리펙토링 (#27)

* chore: 개행 제거 (#27)

---------

Co-authored-by: 김동호 <[email protected]>
Co-authored-by: kdkdhoho <[email protected]>
  • Loading branch information
3 people authored Feb 3, 2024
1 parent 9c7ad25 commit 378878c
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 18 deletions.
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,18 @@ dependencies {
// AWS
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4'
implementation 'io.awspring.cloud:spring-cloud-starter-aws-secrets-manager-config:2.4.4'

//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
useJUnitPlatform()
}

clean {
delete file('src/main/generated')
}
15 changes: 15 additions & 0 deletions src/main/java/com/listywave/common/config/EnumMappingConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.listywave.common.config;

import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class EnumMappingConfig implements WebMvcConfigurer {

@Override
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.configure(registry);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/listywave/common/config/QuerydslConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.listywave.common.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuerydslConfig {

@Bean
JPAQueryFactory jpaQueryFactory(EntityManager em){
return new JPAQueryFactory(em);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.listywave.list.application.domain;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.listywave.common.exception.CustomException;
import com.listywave.common.exception.ErrorCode;
import java.util.Arrays;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -8,24 +11,32 @@
@AllArgsConstructor
public enum CategoryType {

ENTIRE("1","ENTIRE","전체"),
CULTURE("2","CULTURE","문화"),
LIFE("3","LIFE","일상생활"),
PLACE("4","PLACE","장소"),
MUSIC("5","MUSIC","음악"),
MOVIE_DRAMA("6","MOVIE_DRAMA","영화/드라마"),
BOOK("7","BOOK","도서"),
ANIMAL_PLANT("8","ANIMAL_PLANT","동식물"),
ETC("9","ETC","기타"),
ENTIRE("0", "전체"),
CULTURE("1", "문화"),
LIFE("2", "일상생활"),
PLACE("3", "장소"),
MUSIC("4", "음악"),
MOVIE_DRAMA("5", "영화/드라마"),
BOOK("6", "도서"),
ANIMAL_PLANT("7", "동식물"),
ETC("8", "기타"),
;

private final String codeValue;
private final String nameValue;
private final String korNameValue;

public static CategoryType enumOf(String codeValue) {
return Arrays.stream(CategoryType.values())
.filter( t -> t.getCodeValue().equals(codeValue))
.findAny().orElse(null);
.filter(t -> t.getCodeValue().equals(codeValue))
.findAny()
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "해당 카테고리코드는 존재하지 않습니다."));
}

@JsonCreator
public static CategoryType fromString(String key) {
return Arrays.stream(CategoryType.values())
.filter(categoryType -> categoryType.name().equalsIgnoreCase(key))
.findFirst()
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND, "해당 카테고리는 존재하지 않습니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public ListCreateResponse listCreate(

Boolean isLabels = isLabelCountValid(labels);
validateItemsCount(items);
Boolean hasCollaboratorId = hascollaboratorExistence(collaboratorIds);
Boolean hasCollaboratorId = isExistCollaborator(collaboratorIds);

Lists list = Lists.createList(
user,
Expand Down Expand Up @@ -79,7 +79,10 @@ private List<User> findExistingCollaborators(List<Long> collaboratorIds) {
return existingCollaborators;
}

private Boolean hascollaboratorExistence(List<Long> collaboratorIds) {
private Boolean isExistCollaborator(List<Long> collaboratorIds) {
if (collaboratorIds != null && collaboratorIds.size() > 20) {
throw new CustomException(ErrorCode.INVALID_COUNT, "콜라보레이터는 최대 20명까지 가능합니다.");
}
return collaboratorIds != null && !collaboratorIds.isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public record CategoryTypeResponse(
public static CategoryTypeResponse fromEnum(CategoryType categoryType) {
return new CategoryTypeResponse(
categoryType.getCodeValue(),
categoryType.getNameValue(),
categoryType.name().toLowerCase(),
categoryType.getKorNameValue()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.listywave.user.application.dto;

import com.listywave.list.application.domain.Item;
import com.listywave.list.application.domain.Lists;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Builder;

@Builder
public record FeedListsResponse(
Long id,
String title,
Boolean isPublic,
List<ListItemsResponse> listItems
) {
public static FeedListsResponse of(Lists lists) {
return FeedListsResponse.builder()
.id(lists.getId())
.title(lists.getTitle())
.isPublic(lists.isPublic())
.listItems(ListItemsResponse.toList(lists))
.build();
}
}

@Builder
record ListItemsResponse(
Long id,
int ranking,
String title,
String imageUrl
) {
public static ListItemsResponse of(Item item) {
return ListItemsResponse.builder()
.id(item.getId())
.ranking(item.getRanking())
.title(item.getTitle())
.imageUrl(item.getImageUrl())
.build();
}

public static List<ListItemsResponse> toList(Lists lists) {
return lists.getItems().stream()
.map(ListItemsResponse::of)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

import com.listywave.auth.application.domain.JwtManager;
import com.listywave.common.exception.CustomException;
import com.listywave.common.util.UserUtil;
import com.listywave.list.application.domain.CategoryType;
import com.listywave.list.application.domain.Lists;
import com.listywave.user.application.domain.User;
import com.listywave.user.application.dto.AllUserResponse;
import com.listywave.user.application.dto.UserInfoResponse;
import com.listywave.user.presentation.dto.response.AllUserListsResponse;
import com.listywave.user.repository.UserRepository;
import java.util.List;
import java.util.Optional;
Expand All @@ -19,6 +23,7 @@
public class UserService {

private final JwtManager jwtManager;
private final UserUtil userUtil;
private final UserRepository userRepository;

@Transactional(readOnly = true)
Expand All @@ -37,10 +42,34 @@ public UserInfoResponse getUserInfo(Long userId, String accessToken) {
return UserInfoResponse.of(foundUser, false, false);
}

private static boolean isSignedIn(String accessToken) {

private boolean isSignedIn(String accessToken) {
return accessToken.isBlank();
}

@Transactional(readOnly = true)
public AllUserListsResponse getAllListOfUser(
Long userId,
String type,
CategoryType category,
Long cursorId,
int size
) {
userUtil.getUserByUserid(userId);

List<Lists> feedList = userRepository.findFeedLists(userId, type, category, cursorId, size);

boolean hasNext = false;
cursorId = null;

if (feedList.size() == size + 1) {
feedList.remove(size);
hasNext = true;
cursorId = feedList.get(feedList.size() - 1).getId();
}
return AllUserListsResponse.of(hasNext, cursorId, feedList);
}

@Transactional(readOnly = true)
public AllUserResponse getAllUser() {
List<User> allUser = userRepository.findAll();
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/listywave/user/presentation/UserController.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.listywave.user.presentation;

import com.listywave.list.application.domain.CategoryType;
import com.listywave.user.application.dto.AllUserResponse;
import com.listywave.user.application.dto.UserInfoResponse;
import com.listywave.user.application.service.UserService;
import com.listywave.user.presentation.dto.response.AllUserListsResponse;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -30,4 +33,16 @@ ResponseEntity<AllUserResponse> getAllUser() {
AllUserResponse allUserResponse = userService.getAllUser();
return ResponseEntity.ok(allUserResponse);
}

@GetMapping("/users/{userId}/lists")
ResponseEntity<AllUserListsResponse> getAllUserLists(
@PathVariable(name = "userId") Long userId,
@RequestParam(name = "type", defaultValue = "my") String type,
@RequestParam(name = "category", defaultValue = "entire") CategoryType category,
@RequestParam(name = "cursorId", required = false) Long cursorId,
@RequestParam(name = "size") int size
) {
AllUserListsResponse allUserListsResponse = userService.getAllListOfUser(userId, type, category, cursorId, size);
return ResponseEntity.ok(allUserListsResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.listywave.user.presentation.dto.response;

import com.listywave.list.application.domain.Lists;
import com.listywave.user.application.dto.FeedListsResponse;
import java.util.List;

public record AllUserListsResponse(
Long cursorId,
Boolean hasNext,
List<FeedListsResponse> feedLists
) {
public static AllUserListsResponse of(boolean hasNext, Long cursorId, List<Lists> feedLists) {
return new AllUserListsResponse(
cursorId,
hasNext,
toList(feedLists)
);
}

public static List<FeedListsResponse> toList(List<Lists> feedLists) {
return feedLists.stream()
.map(FeedListsResponse::of)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.listywave.user.repository;

import com.listywave.user.application.domain.User;
import com.listywave.user.repository.custom.UserRepositoryCustom;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {

Optional<User> findByOauthId(Long oauthId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.listywave.user.repository.custom;

import com.listywave.list.application.domain.CategoryType;
import com.listywave.list.application.domain.Lists;
import java.util.List;

public interface UserRepositoryCustom {

List<Lists> findFeedLists(Long userId, String type, CategoryType category, Long cursorId, int size);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.listywave.user.repository.custom.impl;

import static com.listywave.list.application.domain.QItem.item;
import static com.listywave.list.application.domain.QLists.lists;

import com.listywave.list.application.domain.CategoryType;
import com.listywave.list.application.domain.Lists;
import com.listywave.user.repository.custom.UserRepositoryCustom;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public List<Lists> findFeedLists(Long userId, String type, CategoryType category, Long cursorId, int size) {
List<Lists> fetch = queryFactory
.select(lists)
.from(lists)
.leftJoin(lists.items, item)
.where(
userIdEq(userId),
typeEq(type),
categoryEq(category),
listIdGt(cursorId)
)
.distinct()
.limit(size + 1)
.orderBy(lists.updatedDate.desc())
.fetch();
return fetch;
}

private BooleanExpression listIdGt(Long cursorId) {
return cursorId == null ? null : lists.id.lt(cursorId);
}

private BooleanExpression categoryEq(CategoryType categoryCode) {
if ("0".equals(categoryCode.getCodeValue())) {
return null;
}
return lists.category.eq(categoryCode);
}

private BooleanExpression typeEq(String type) {
if (type.equals("my")) {
return lists.hasCollaboration.eq(false);
}
return lists.hasCollaboration.eq(true);
}

private BooleanExpression userIdEq(Long userId) {
return userId == null ? null : lists.user.id.eq(userId);
}
}
Loading

0 comments on commit 378878c

Please sign in to comment.