Skip to content

Commit

Permalink
MATE-119 : [REFACTOR] 굿즈거래 대표사진 반환 로직 리팩토링 (#109)
Browse files Browse the repository at this point in the history
* MATE-119 : [REFACTOR] GoodsPost 엔티티에 mainImageUrl 필드 추가

* MATE-119 : [REFACTOR] 엔티티 변경에 따른 서비스 로직 변경

* MATE-119 : [CHORE] MemberInfo, LocationInfo 클래스 패키지 위치 변경

* MATE-119 : [REFACTOR] ValidPageableArgumentResolver 클래스 리팩토링
  • Loading branch information
hongjeZZ authored Dec 8, 2024
1 parent e28d07d commit d442bc5
Show file tree
Hide file tree
Showing 20 changed files with 95 additions and 165 deletions.
12 changes: 5 additions & 7 deletions src/main/java/com/example/mate/common/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,11 @@ public enum ErrorCode {
INVALID_MATE_POST_PARTICIPANT_IDS(HttpStatus.BAD_REQUEST, "MP013", "존재하지 않는 회원이 참여자 목록에 포함되어 있습니다"),

// Goods
GOODS_IMAGES_ARE_EMPTY(HttpStatus.BAD_REQUEST, "G001", "굿즈 이미지는 최소 1개 이상을 업로드 할 수 있습니다."),
GOODS_NOT_FOUND_BY_ID(HttpStatus.NOT_FOUND, "G002", "해당 ID의 굿즈 판매글 정보를 찾을 수 없습니다."),
GOODS_MODIFICATION_NOT_ALLOWED(HttpStatus.FORBIDDEN, "G003", "판매글의 판매자가 아니라면, 판매글을 수정하거나 삭제할 수 없습니다."),
GOODS_MAIN_IMAGE_IS_EMPTY(HttpStatus.NOT_FOUND, "G004", "굿즈 게시물의 대표사진을 찾을 수 없습니다."),
GOODS_DELETE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "G005", "거래완료 상태에서 판매글을 삭제할 수 없습니다."),
GOODS_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "G006", "이미 거래완료 상태인 굿즈는 거래를 완료할 수 없습니다."),
SELLER_CANNOT_BE_BUYER(HttpStatus.BAD_REQUEST, "G007", "판매자와 구매자는 동일할 수 없습니다."),
GOODS_NOT_FOUND_BY_ID(HttpStatus.NOT_FOUND, "G001", "해당 ID의 굿즈 판매글 정보를 찾을 수 없습니다."),
GOODS_MODIFICATION_NOT_ALLOWED(HttpStatus.FORBIDDEN, "G002", "판매글의 판매자가 아니라면, 판매글을 수정하거나 삭제할 수 없습니다."),
GOODS_DELETE_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "G003", "거래완료 상태에서 판매글을 삭제할 수 없습니다."),
GOODS_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "G004", "이미 거래완료 상태인 굿즈는 거래를 완료할 수 없습니다."),
SELLER_CANNOT_BE_BUYER(HttpStatus.BAD_REQUEST, "G005", "판매자와 구매자는 동일할 수 없습니다."),

// Goods Review
GOODS_REVIEW_STATUS_NOT_CLOSED(HttpStatus.BAD_REQUEST, "GR001", "굿즈거래 후기는 거래완료 상태에서만 작성할 수 있습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,44 @@
@Component
public class ValidPageableArgumentResolver extends PageableHandlerMethodArgumentResolver {

// @ValidPageable 이 있을 때만 실행
@Override
public Pageable resolveArgument(MethodParameter methodParameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(ValidPageable.class);
}

// 기본 Pageable 생성
Pageable pageable = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
@Override
public Pageable resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
// 클라이언트 요청값 생성
String pageRequest = webRequest.getParameter(this.getParameterNameToUse(this.getPageParameterName(), methodParameter));
String sizeRequest = webRequest.getParameter(this.getParameterNameToUse(this.getSizeParameterName(), methodParameter));

// @ValidPageable 어노테이션 확인
ValidPageable validPageable = methodParameter.getParameterAnnotation(ValidPageable.class);
assert validPageable != null;

// 클라이언트가 page 파라미터를 제공했는지 확인
String pageParam = webRequest.getParameter("page");
int page = extractPageValue(pageRequest, validPageable);
int size = extractSizeValue(sizeRequest, validPageable);

// pageParam이 존재하면 pageable.getPageNumber() 사용
// pageParam이 없고 validPageable이 존재하면 validPageable.page() 사용
// 둘 다 없으면 0을 사용
int pageNumber = pageParam != null ?
pageable.getPageNumber() :
(validPageable != null ? validPageable.page() : 0);
return PageRequest.of(page, size);
}

int pageSize = pageable.getPageSize() > 0
? pageable.getPageSize()
: validPageable != null ? validPageable.size() : 10;
// parameter 에 page 값이 있을 경우, 유효성 검증
private int extractPageValue(String pageRequest, ValidPageable validPageable) {
try {
return pageRequest != null ? Math.max(0, Integer.parseInt(pageRequest)) : validPageable.page();
} catch (NumberFormatException e) {
return validPageable.page();
}
}

return PageRequest.of(pageNumber, pageSize, pageable.getSort());
// parameter 에 size 값이 있을 경우, 유효성 검증
private int extractSizeValue(String sizeRequest, ValidPageable validPageable) {
try {
return sizeRequest != null ? Math.max(1, Integer.parseInt(sizeRequest)) : validPageable.size();
} catch (NumberFormatException e) {
return validPageable.size();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.mate.domain.goods.dto.request;

import com.example.mate.common.validator.ValidEnum;
import com.example.mate.domain.goods.dto.LocationInfo;
import com.example.mate.domain.goods.dto.response.LocationInfo;
import com.example.mate.domain.goods.entity.Category;
import com.example.mate.domain.goods.entity.GoodsPost;
import com.example.mate.domain.member.entity.Member;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.example.mate.domain.goods.dto.response;

import com.example.mate.domain.constant.TeamInfo;
import com.example.mate.domain.goods.dto.LocationInfo;
import com.example.mate.domain.goods.dto.MemberInfo;
import com.example.mate.domain.goods.entity.GoodsPost;
import com.example.mate.domain.goods.entity.GoodsPostImage;
import com.example.mate.domain.goods.entity.Role;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.mate.domain.goods.dto;
package com.example.mate.domain.goods.dto.response;

import com.example.mate.domain.goods.entity.Location;
import jakarta.validation.constraints.NotEmpty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.mate.domain.goods.dto;
package com.example.mate.domain.goods.dto.response;

import com.example.mate.domain.goods.entity.Role;
import com.example.mate.domain.member.entity.Member;
Expand Down
31 changes: 9 additions & 22 deletions src/main/java/com/example/mate/domain/goods/entity/GoodsPost.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.example.mate.domain.goods.entity;

import com.example.mate.common.BaseTimeEntity;
import com.example.mate.common.error.CustomException;
import com.example.mate.common.error.ErrorCode;
import com.example.mate.domain.member.entity.Member;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
Expand All @@ -26,13 +24,11 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Entity
@Table(name = "goods_post")
@Getter
@Builder
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class GoodsPost extends BaseTimeEntity {
Expand All @@ -53,10 +49,13 @@ public class GoodsPost extends BaseTimeEntity {
private Long teamId;

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("isMainImage DESC, id ASC")
@OrderBy("id ASC")
@Builder.Default
private List<GoodsPostImage> goodsPostImages = new ArrayList<>();

@Column(name = "main_image_url", columnDefinition = "TEXT")
private String mainImageUrl;

@Column(nullable = false, length = 100)
private String title;

Expand All @@ -78,23 +77,20 @@ public class GoodsPost extends BaseTimeEntity {
@Builder.Default
private Status status = Status.OPEN;

// 굿즈 판매글 이미지 업로드 및 수정 메서드
public void changeImages(List<GoodsPostImage> goodsPostImages) {
if (goodsPostImages.isEmpty()) {
throw new CustomException(ErrorCode.GOODS_IMAGES_ARE_EMPTY);
}

// 기존 이미지 전부 삭제
this.goodsPostImages.clear();

for (GoodsPostImage goodsPostImage : goodsPostImages) {
this.goodsPostImages.add(goodsPostImage);
goodsPostImage.changePost(this);
}
this.goodsPostImages.get(0).setAsMainImage();
changeMainImage();
}

private void changeMainImage() {
this.mainImageUrl = goodsPostImages.get(0).getImageUrl();
}

// 굿즈 판매글 수정 메서드
public void updatePostDetails(GoodsPost post) {
this.teamId = post.getTeamId();
this.title = post.getTitle();
Expand All @@ -104,17 +100,8 @@ public void updatePostDetails(GoodsPost post) {
this.location = post.getLocation();
}

// 거래 완료 메서드
public void completeTransaction(Member buyer) {
this.buyer = buyer;
this.status = Status.CLOSED;
}

public String getMainImageUrl() {
return goodsPostImages.stream()
.filter(GoodsPostImage::getIsMainImage)
.findFirst()
.map(GoodsPostImage::getImageUrl)
.orElse("upload/default.jpg");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,7 @@ public class GoodsPostImage {
@Column(name = "image_url", nullable = false, columnDefinition = "TEXT")
private String imageUrl;

@Column(name = "is_main_image", nullable = false)
@Builder.Default
private Boolean isMainImage = false;

public void changePost(GoodsPost post) {
this.post = post;
}

public void setAsMainImage() {
this.isMainImage = true;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.mate.domain.goods.repository;

import static com.example.mate.domain.goods.entity.QGoodsPost.goodsPost;
import static com.example.mate.domain.goods.entity.QGoodsPostImage.goodsPostImage;

import com.example.mate.domain.goods.entity.Category;
import com.example.mate.domain.goods.entity.GoodsPost;
Expand All @@ -22,13 +21,11 @@ public class GoodsPostRepositoryCustomImpl implements GoodsPostRepositoryCustom

@Override
public Page<GoodsPost> findPageGoodsPosts(Long teamId, Status status, Category category, Pageable pageable) {
BooleanBuilder conditions = createConditions(teamId, status, category);
BooleanBuilder conditions = buildConditions(teamId, status, category);

List<GoodsPost> fetch = queryFactory
.selectFrom(goodsPost)
.join(goodsPost.goodsPostImages, goodsPostImage).fetchJoin()
.where(conditions)
.where(goodsPostImage.isMainImage.eq(true))
.orderBy(goodsPost.createdAt.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
Expand All @@ -42,7 +39,7 @@ public Page<GoodsPost> findPageGoodsPosts(Long teamId, Status status, Category c
return PageableExecutionUtils.getPage(fetch, pageable, total::fetchOne);
}

private BooleanBuilder createConditions(Long teamId, Status status, Category category) {
private BooleanBuilder buildConditions(Long teamId, Status status, Category category) {
BooleanBuilder builder = new BooleanBuilder();

builder.and(goodsPost.status.eq(status));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.example.mate.domain.goods.repository;

import static com.example.mate.domain.goods.entity.QGoodsReview.*;

import com.example.mate.domain.goods.entity.QGoodsPost;
import com.example.mate.domain.goods.entity.QGoodsReview;
import com.example.mate.domain.member.dto.response.MyReviewResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@
import com.example.mate.domain.goods.dto.response.GoodsPostResponse;
import com.example.mate.domain.goods.dto.response.GoodsPostSummaryResponse;
import com.example.mate.domain.goods.dto.response.GoodsReviewResponse;
import com.example.mate.domain.goods.entity.*;
import com.example.mate.domain.goods.entity.Category;
import com.example.mate.domain.goods.entity.GoodsPost;
import com.example.mate.domain.goods.entity.GoodsPostImage;
import com.example.mate.domain.goods.entity.GoodsReview;
import com.example.mate.domain.goods.entity.Status;
import com.example.mate.domain.goods.repository.GoodsPostImageRepository;
import com.example.mate.domain.goods.repository.GoodsPostRepository;
import com.example.mate.domain.goods.repository.GoodsReviewRepository;
import com.example.mate.domain.member.entity.Member;
import com.example.mate.domain.member.repository.MemberRepository;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
Expand All @@ -25,9 +31,6 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
Expand All @@ -52,8 +55,7 @@ public GoodsPostResponse registerGoodsPost(Long memberId, GoodsPostRequest reque
return GoodsPostResponse.of(savedPost);
}

public GoodsPostResponse updateGoodsPost(Long memberId, Long goodsPostId, GoodsPostRequest request,
List<MultipartFile> files) {
public GoodsPostResponse updateGoodsPost(Long memberId, Long goodsPostId, GoodsPostRequest request, List<MultipartFile> files) {
Member seller = findMemberById(memberId);
GoodsPost goodsPost = findGoodsPostById(goodsPostId);

Expand Down Expand Up @@ -87,25 +89,17 @@ public GoodsPostResponse getGoodsPost(Long goodsPostId) {
@Transactional(readOnly = true)
public List<GoodsPostSummaryResponse> getMainGoodsPosts(Long teamId) {
validateTeamInfo(teamId);

return goodsPostRepository.findMainGoodsPosts(teamId, Status.OPEN, PageRequest.of(0, 4))
.stream()
.map(this::convertToSummaryResponse)
.toList();
return mapToGoodsPostSummaryResponses(
goodsPostRepository.findMainGoodsPosts(teamId, Status.OPEN, PageRequest.of(0, 4))
);
}

@Transactional(readOnly = true)
public PageResponse<GoodsPostSummaryResponse> getPageGoodsPosts(Long teamId, String categoryVal,
Pageable pageable) {
public PageResponse<GoodsPostSummaryResponse> getPageGoodsPosts(Long teamId, String categoryVal, Pageable pageable) {
validateTeamInfo(teamId);
Category category = Category.from(categoryVal);

Page<GoodsPost> pageGoodsPosts = goodsPostRepository.findPageGoodsPosts(teamId, Status.OPEN, category,
pageable);
List<GoodsPostSummaryResponse> responses = pageGoodsPosts.getContent().stream()
.map(this::convertToSummaryResponse).toList();

return PageResponse.from(pageGoodsPosts, responses);
Page<GoodsPost> pageGoodsPosts = goodsPostRepository.findPageGoodsPosts(teamId, Status.OPEN, category, pageable);
return PageResponse.from(pageGoodsPosts, mapToGoodsPostSummaryResponses(pageGoodsPosts.getContent()));
}

public void completeTransaction(Long sellerId, Long goodsPostId, Long buyerId) {
Expand Down Expand Up @@ -151,9 +145,10 @@ private void deleteExistingImageFiles(Long goodsPostId) {
imageRepository.deleteAllByPostId(goodsPostId);
}

private GoodsPostSummaryResponse convertToSummaryResponse(GoodsPost goodsPost) {
String mainImageUrl = goodsPost.getMainImageUrl();
return GoodsPostSummaryResponse.of(goodsPost, mainImageUrl);
private List<GoodsPostSummaryResponse> mapToGoodsPostSummaryResponses(List<GoodsPost> goodsPosts) {
return goodsPosts.stream()
.map(goodsPost -> GoodsPostSummaryResponse.of(goodsPost, goodsPost.getMainImageUrl()))
.toList();
}

private GoodsPost findGoodsPostById(Long goodsPostId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public ResponseEntity<ApiResponse<GoodsChatRoomResponse>> createGoodsChatRoom(
public ResponseEntity<ApiResponse<PageResponse<GoodsChatMessageResponse>>> getGoodsChatRoomMessages(
@AuthenticationPrincipal AuthMember member,
@Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId,
@Parameter(description = "페이징 정보") @ValidPageable(page = 1) Pageable pageable
@Parameter(description = "페이징 정보") @ValidPageable(page = 1, size = 20) Pageable pageable
) {
PageResponse<GoodsChatMessageResponse> response = goodsChatService.getMessagesForChatRoom(chatRoomId, member.getMemberId(), pageable);
return ResponseEntity.ok(ApiResponse.success(response));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import com.example.mate.common.security.filter.JwtCheckFilter;
import com.example.mate.config.WithAuthMember;
import com.example.mate.domain.constant.Rating;
import com.example.mate.domain.goods.dto.LocationInfo;
import com.example.mate.domain.goods.dto.response.LocationInfo;
import com.example.mate.domain.goods.dto.request.GoodsPostRequest;
import com.example.mate.domain.goods.dto.request.GoodsReviewRequest;
import com.example.mate.domain.goods.dto.response.GoodsPostResponse;
Expand Down
Loading

0 comments on commit d442bc5

Please sign in to comment.