Skip to content

Commit

Permalink
✨ 크리에이터 찾기 API 작성 (#104)
Browse files Browse the repository at this point in the history
* ✨ Controller 작성

* 🎨 응답 dto 추가

* 🎨 서비스 인터페이스 작성

* 🎨 서비스 로직 오버라이드

* 🎨 응답 구조 변경

* ✨ Pageable 매개변수 추가

* 🎨 응답 구조 변경

* 🚧 응답 확인

* 🎨 작업 가능 필드 응답 변경

* ✨ 최소 비용 응답에 포함

* ✨ 가격 조건 추가

* 🎨 응답 dto에 포트폴리오 썸네일 이미지 경로 추가

* ✨ 가격순 정렬 추가

* ✨ 카테고리 정렬 기능 추가

* 📝 Swagger 문서 업데이트 (응답)

* 📝 가능한 작업 카테고리 예시 추가

* 📝 Swagger 문서 업데이트(Parameter)

* 🎨 작업가능 필드 응답 한글로 변경
  • Loading branch information
MinseoKangQ authored and Jindongleee committed Nov 16, 2024
1 parent 733fa74 commit 7bd2235
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
package org.example.gather_back_end.creator.controller;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.gather_back_end.bucket.service.BucketService;
import org.example.gather_back_end.creator.dto.CreateCreatorReq;
import org.example.gather_back_end.creator.dto.GetCreatorRes;
import org.example.gather_back_end.creator.dto.filtering.CreatorInfo;
import org.example.gather_back_end.creator.service.CreatorService;
import org.example.gather_back_end.domain.User;
import org.example.gather_back_end.portfolio.service.PortfolioService;
import org.example.gather_back_end.repository.UserRepository;
import org.example.gather_back_end.util.jwt.dto.CustomOAuth2User;
import org.example.gather_back_end.util.response.PageResponse;
import org.example.gather_back_end.util.response.SuccessResponse;
import org.example.gather_back_end.work.service.WorkService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -88,4 +93,19 @@ public SuccessResponse<?> getCreatorInfo(Authentication authentication) {
return SuccessResponse.of(getCreatorRes);
}

// 크리에이터 찾기
@GetMapping("/filtering")
public SuccessResponse<PageResponse<CreatorInfo>> filteringCreator(
Authentication authentication,
@PageableDefault(size = 12, page = 0) Pageable pageable,
@RequestParam(value = "price", required = false) Integer price,
@RequestParam(value = "category", required = false) String category,
@RequestParam(value = "align", defaultValue = "recently", required = false) String recently
) {
CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal();
String providerId = customOAuth2User.getUsername();
PageResponse<CreatorInfo> res = creatorService.filteringCreator(providerId, pageable, price, category, recently);
return SuccessResponse.of(res);
}

}
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
package org.example.gather_back_end.creator.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.example.gather_back_end.creator.dto.CreateCreatorReq;
import org.example.gather_back_end.creator.dto.filtering.CreatorInfo;
import org.example.gather_back_end.util.response.PageResponse;
import org.example.gather_back_end.util.response.SuccessResponse;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.Authentication;
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.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

@Tag(name = "크리에이터 프로필 관련", description = "크리에이터 프로필 관련된 API")
@Tag(name = "크리에이터 관련", description = "크리에이터 관련된 API")
public interface CreatorControllerApi {

@Operation(summary = "크리에이터 등록 완료")
Expand Down Expand Up @@ -77,4 +83,71 @@ SuccessResponse<?> createCreator(
@GetMapping
SuccessResponse<?> getCreatorInfo(Authentication authentication);

@Operation(summary = "크리에이터 찾기")
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "크리에이터 찾기에서 사용되는 API",
content = @Content(mediaType = "application/json",
examples = @ExampleObject(value = """
{
"timestamp": "2024-11-14T21:39:55.407616",
"isSuccess": true,
"code": "200",
"message": "호출에 성공하였습니다.",
"data": {
"totalPage": 1,
"totalElements": 3,
"pagingSize": 12,
"currentPage": 1,
"isFirst": true,
"isLast": true,
"isEmpty": false,
"data": [
{
"nickname": "USER4255",
"availableWork": [
"SNS"
],
"introductionTitle": "sds",
"startPrice": "49000",
"thumbnailImgUrl": null
},
{
"nickname": "USER3322",
"availableWork": [
"인쇄물",
"SNS"
],
"introductionTitle": "dkssudd",
"startPrice": "500",
"thumbnailImgUrl": "ㅇㅇ"
},
{
"nickname": "USER3333",
"availableWork": [
"SNS"
],
"introductionTitle": "dfdfdfdfdfdfdfdf",
"startPrice": "1000",
"thumbnailImgUrl": null
}
]
}
}
"""
),
schema = @Schema(implementation = SuccessResponse.class)))
})
@GetMapping
SuccessResponse<PageResponse<CreatorInfo>> filteringCreator(
Authentication authentication,
@PageableDefault(size = 12, page = 0) Pageable pageable,
@Parameter(description = "가격 필터링 기준 (선택)", example = "50000")
@RequestParam(value = "price", required = false) Integer price,
@Parameter(description = "카테고리 필터링 기준 (선택): PRINTS, VIDEO, SNS_POST 중 하나", example = "PRINTS")
@RequestParam(value = "category", required = false) String category,
@Parameter(description = "정렬 기준 (선택): recently, lowPrice, highPrice 중 하나, 기본값은 recently", example = "lowPrice")
@RequestParam(value = "align", defaultValue = "recently", required = false) String recently
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.example.gather_back_end.creator.dto.filtering;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.example.gather_back_end.domain.Portfolio;
import org.example.gather_back_end.domain.User;
import org.example.gather_back_end.domain.Work;
import org.example.gather_back_end.util.format.WorkTypeConverter;

public record CreatorInfo(
@Schema(description = "크리에이터명", example = "hello")
String nickname,

@Schema(description = "가능한 작업 카테고리", example = "[\"SNS\", \"인쇄물\", \"비디오\"]")
List<String> availableWork,

@Schema(description = "크리에이터 소개글 제목", example = "안녕하세요")
String introductionTitle,

@Schema(description = "작업 시작 가격", example = "5000")
String startPrice,

@Schema(description = "포트폴리오 썸네일 주소", example = "dfdfd")
String thumbnailImgUrl
) {

public static CreatorInfo from(User user, List<String> availableWork, List<Portfolio> portfolioList) {

// workList에서 startPrice 중 가장 작은 값 찾기
String minStartPrice = user.getWorkList().stream()
.map(Work::getStartPrice)
.min(Comparator.naturalOrder())
.map(String::valueOf) // int를 String으로 변환
.orElse("N/A"); // workList가 비어 있을 경우 기본값

// availableWork를 한글명으로 변환
List<String> translatedAvailableWork = user.getWorkList().stream()
.map(work -> WorkTypeConverter.toKorean(work.getCategory()))
.distinct()
.collect(Collectors.toList());

return new CreatorInfo(
user.getNickname(),
translatedAvailableWork,
user.getIntroductionTitle(),
minStartPrice,
portfolioList.getFirst().getThumbnailImgUrl()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.example.gather_back_end.creator.service;

import org.example.gather_back_end.creator.dto.GetCreatorRes;
import org.example.gather_back_end.creator.dto.filtering.CreatorInfo;
import org.example.gather_back_end.util.response.PageResponse;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.Authentication;

public interface CreatorService {
Expand All @@ -17,4 +20,5 @@ void createCreator(
GetCreatorRes getCreator(String nickname);

GetCreatorRes getCreatorInfo(Authentication authentication);
PageResponse<CreatorInfo> filteringCreator(String providerId, Pageable pageable, Integer price, String category, String align);
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
package org.example.gather_back_end.creator.service;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.gather_back_end.creator.dto.GetCreatorRes;
import org.example.gather_back_end.creator.dto.filtering.CreatorInfo;
import org.example.gather_back_end.domain.User;
import org.example.gather_back_end.domain.WorkType;
import org.example.gather_back_end.portfolio.dto.GetPortfolioRes;
import org.example.gather_back_end.repository.PortfolioRepository;
import org.example.gather_back_end.repository.UserRepository;
import org.example.gather_back_end.repository.WorkRepository;
import org.example.gather_back_end.util.jwt.dto.CustomOAuth2User;
import org.example.gather_back_end.util.response.PageResponse;
import org.example.gather_back_end.work.dto.GetWorkRes;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.util.List;

@RequiredArgsConstructor
@Slf4j
@Service
@RequiredArgsConstructor
public class CreatorServiceImpl implements CreatorService {

private final UserRepository userRepository;
Expand Down Expand Up @@ -89,4 +102,38 @@ public GetCreatorRes getCreatorInfo(Authentication authentication){

}

@Override
public PageResponse<CreatorInfo> filteringCreator(String providerId, Pageable pageable, Integer price, String category, String align) {

Sort sort = switch (align) {
case "lowPrice" -> Sort.by(Sort.Direction.ASC, "workList.startPrice");
case "highPrice" -> Sort.by(Sort.Direction.DESC, "workList.startPrice");
default -> Sort.by(Sort.Direction.DESC, "createAt");
};

Pageable sortedPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);

// category를 WorkType으로 변환, 값이 없는 경우 null로 설정
WorkType workCategory = (category != null) ? WorkType.valueOf(category) : null;

Page<User> creators = userRepository.customFiltering(price, workCategory, sortedPageable);

Set<Long> seenIds = new LinkedHashSet<>();
List<CreatorInfo> creatorInfoList = creators.getContent().stream()
.filter(user -> seenIds.add(user.getId()))
.map(user -> CreatorInfo.from(
user,
user.getWorkList().stream()
.map(work -> work.getCategory().name())
.toList(),
user.getPortfolioList()
))
.collect(Collectors.toList());

PageImpl<CreatorInfo> res = new PageImpl<>(creatorInfoList, sortedPageable, creatorInfoList.size());

return PageResponse.of(res);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import java.util.List;
import java.util.Optional;
import org.example.gather_back_end.domain.User;
import org.example.gather_back_end.domain.WorkType;
import org.example.gather_back_end.user.exception.UserNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
Expand All @@ -21,6 +24,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
default User getById(Long id) {
return findById(id).orElseThrow(UserNotFoundException::new);
}

default User getByUsername(String username) {
return findByUsername(username).orElseThrow(UserNotFoundException::new);
}
Expand All @@ -35,15 +39,28 @@ default User getByNickname(String nickname) {

// 크리에이터 찾기
@Query("SELECT u FROM User u " +
"JOIN u.workList w " +
"JOIN u.portfolioList p " +
"WHERE u.introductionTitle IS NOT NULL " + // 소개글 제목 존재
"AND SIZE(u.workList) > 0 " + // 작업 가능 항목 등록
"AND SIZE(u.portfolioList) > 0 ") // 포트폴리오 등록
"JOIN u.workList w " +
"JOIN u.portfolioList p " +
"WHERE u.introductionTitle IS NOT NULL " + // 소개글 제목 존재
"AND SIZE(u.workList) > 0 " + // 작업 가능 항목 등록
"AND SIZE(u.portfolioList) > 0 ")
// 포트폴리오 등록
// TODO: 포트폴리오 더미데이터 모두 넣은 후 주석 해제
// "AND SIZE(u.portfolioList) > 0 " + // 포트폴리오 등록
// "AND p.thumbnailImgUrl IS NOT NULL " + // 포트폴리오 썸네일 존재
// "AND p.fileUrl IS NOT NULL") // 포트폴리오 파일 존재
List<User> findAllCreators();

@Query("SELECT DISTINCT u FROM User u " +
"JOIN u.workList w " +
"WHERE u.introductionTitle IS NOT NULL " +
"AND (:price IS NULL OR " +
" (:price = 10000 AND w.startPrice < 10000) OR " +
" (:price = 50000 AND w.startPrice < 50000) OR " +
" (:price = 100000 AND w.startPrice < 100000) OR " +
" (:price = 200000 AND w.startPrice < 200000) OR " +
" (:price = 200001 AND w.startPrice >= 200000)) " +
"AND (:category IS NULL OR w.category = :category)")
Page<User> customFiltering(@Param("price") Integer price, @Param("category") WorkType category, Pageable pageable);

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class WorkTypeConverter {

private static final Map<WorkType, String> workTypeToKoreanMap = Map.of(
WorkType.PRINTS, "인쇄물",
WorkType.SNS_POST, "SNS 게시물",
WorkType.SNS_POST, "SNS",
WorkType.VIDEO, "영상"
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* 게시글 등의 리스트 조회 시 필요한 응답 클래스
* Pageable 이용
*/
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -13,13 +14,28 @@
@Builder
public class PageResponse<T> {

@Schema(description = "총 페이지 수", example = "1")
private int totalPage;

@Schema(description = "결과 데이터 개수", example = "3")
private Long totalElements;

@Schema(description = "한 번에 가져올 데이터 개수", example = "12")
private int pagingSize;

@Schema(description = "현재 페이지 (1부터 시작)", example = "1")
private int currentPage;

@Schema(description = "첫 번째 페이지인지 여부", example = "true")
private Boolean isFirst;

@Schema(description = "마지막 페이지인지 여부", example = "false")
private Boolean isLast;

@Schema(description = "결과 데이터가 비어있는지 여부", example = "false")
private Boolean isEmpty;

@Schema(description = "실제 데이터가 담기는 필드")
private List<T> data;

public static PageResponse of(Page page) {
Expand Down

0 comments on commit 7bd2235

Please sign in to comment.