diff --git a/src/main/java/com/art/cheric/module/art/controller/ArtController.java b/src/main/java/com/art/cheric/module/art/controller/ArtController.java index 30d621f..ec6a604 100644 --- a/src/main/java/com/art/cheric/module/art/controller/ArtController.java +++ b/src/main/java/com/art/cheric/module/art/controller/ArtController.java @@ -47,7 +47,7 @@ public ResponseEntity postArtistArt(@RequestAttribute("user") User @GetMapping("/{id}") public ResponseEntity getArt(@RequestAttribute("user") User user, @PathVariable("id") Long artId) { - ArtResDto resDto = artService.getArt(artId); + ArtResDto resDto = artService.getArt(user, artId); return ResponseEntity.status(200).body(DataResponseDto.of(resDto, 200)); } diff --git a/src/main/java/com/art/cheric/module/art/domain/repository/ArtRepository.java b/src/main/java/com/art/cheric/module/art/domain/repository/ArtRepository.java index 3df00b2..ff3306d 100644 --- a/src/main/java/com/art/cheric/module/art/domain/repository/ArtRepository.java +++ b/src/main/java/com/art/cheric/module/art/domain/repository/ArtRepository.java @@ -2,7 +2,11 @@ import com.art.cheric.module.art.domain.entity.Art; import com.art.cheric.module.art.domain.repository.custom.ArtRepositoryCustom; +import java.util.List; +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; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -11,4 +15,8 @@ @Repository public interface ArtRepository extends JpaRepository, ArtRepositoryCustom { Optional findByIdAndUserId(Long id, Long userId); + + @Query("SELECT a FROM Art a WHERE a.user.id = :userId ORDER BY a.createdAt DESC") + List findMostRecentArtByUserId(@Param("userId") Long userId, Pageable pageable); + } diff --git a/src/main/java/com/art/cheric/module/art/dto/res/ArtResDto.java b/src/main/java/com/art/cheric/module/art/dto/res/ArtResDto.java index e23dcc1..a94fbf1 100644 --- a/src/main/java/com/art/cheric/module/art/dto/res/ArtResDto.java +++ b/src/main/java/com/art/cheric/module/art/dto/res/ArtResDto.java @@ -59,11 +59,15 @@ public class ArtResDto { @Schema(description = "소장 작품 관련 추가 정보 DTO", example = "작가 작품인 경우 나오지 않음") private final OwnArtResDto ownArtRes; + @Schema(description = "좋아요 여부", example = "true") + private final Boolean isHeart; + public static ArtResDto of(boolean isCollectorsArt, String imgUrl, Integer cherryPrice, String name, String artistName, String series, int horizontalSize, int verticalSize, String material, Year madeAt, List artTypes, - UserResDto userRes, int heartCount, String description, OwnArtResDto ownArtRes) { + UserResDto userRes, int heartCount, String description, OwnArtResDto ownArtRes, + Boolean isHeart) { return ArtResDto.builder() .isCollectorsArt(isCollectorsArt) .imgUrl(imgUrl) @@ -80,6 +84,7 @@ public static ArtResDto of(boolean isCollectorsArt, String imgUrl, Integer cherr .heartCount(heartCount) .description(description) .ownArtRes(ownArtRes) + .isHeart(isHeart) .build(); } } diff --git a/src/main/java/com/art/cheric/module/art/service/ArtService.java b/src/main/java/com/art/cheric/module/art/service/ArtService.java index 1ccc500..babf262 100644 --- a/src/main/java/com/art/cheric/module/art/service/ArtService.java +++ b/src/main/java/com/art/cheric/module/art/service/ArtService.java @@ -3,12 +3,28 @@ import com.art.cheric.global.enums.ArtOrderType; import com.art.cheric.global.enums.ArtType; import com.art.cheric.global.error.exception.AppException; -import com.art.cheric.global.util.DateFormatUtil; -import com.art.cheric.module.art.domain.entity.*; -import com.art.cheric.module.art.domain.repository.*; +import com.art.cheric.module.art.domain.entity.Art; +import com.art.cheric.module.art.domain.entity.ArtFile; +import com.art.cheric.module.art.domain.entity.ArtHeart; +import com.art.cheric.module.art.domain.entity.ArtPart; +import com.art.cheric.module.art.domain.entity.ArtPlusImage; +import com.art.cheric.module.art.domain.entity.ArtistArt; +import com.art.cheric.module.art.domain.entity.OwnArt; +import com.art.cheric.module.art.domain.repository.ArtFileRepository; +import com.art.cheric.module.art.domain.repository.ArtHeartRepository; +import com.art.cheric.module.art.domain.repository.ArtPartRepository; +import com.art.cheric.module.art.domain.repository.ArtPlusImageRepository; +import com.art.cheric.module.art.domain.repository.ArtRepository; +import com.art.cheric.module.art.domain.repository.ArtistArtRepository; +import com.art.cheric.module.art.domain.repository.OwnArtRepository; import com.art.cheric.module.art.dto.req.ArtReqDto; import com.art.cheric.module.art.dto.req.OwnArtReqDto; -import com.art.cheric.module.art.dto.res.*; +import com.art.cheric.module.art.dto.res.ArtBriefListResDto; +import com.art.cheric.module.art.dto.res.ArtDescriptionResDto; +import com.art.cheric.module.art.dto.res.ArtMostBriefListResDto; +import com.art.cheric.module.art.dto.res.ArtResDto; +import com.art.cheric.module.art.dto.res.ArtTypeSortListResDto; +import com.art.cheric.module.art.dto.res.OwnArtResDto; import com.art.cheric.module.art.error.ArtErrorCode; import com.art.cheric.module.artist.service.ArtistService; import com.art.cheric.module.following.service.FollowService; @@ -17,6 +33,11 @@ import com.art.cheric.module.user.dto.res.UserBriefResDto; import com.art.cheric.module.user.dto.res.UserResDto; import com.art.cheric.module.user.service.UserService; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -26,12 +47,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -54,7 +69,8 @@ public Long postOwnArt(User user, OwnArtReqDto ownArtReq) { // art 생성 ArtReqDto artReq = ownArtReq.artBasicInfo(); Art art = artRepository.save( - Art.of(user, artReq.name(), artReq.description(), artReq.series(), artReq.material(), artReq.madeAt(), null, + Art.of(user, artReq.name(), artReq.description(), artReq.series(), artReq.material(), artReq.madeAt(), + null, artReq.horizontalSize(), artReq.verticalSize(), artReq.imgUrl(), true)); // artPart 생성 @@ -105,6 +121,7 @@ public Long postArtistArt(User user, ArtReqDto artReq) { return art.getId(); } + // 등록 체리수 유효성 검증 private void checkCherryCountValidate(ArtReqDto artReq) { if (artReq.cherryPrice() == null || artReq.cherryPrice() < 0) { throw new AppException(ArtErrorCode.INVALID_CHERRY_PRICE); @@ -112,7 +129,7 @@ private void checkCherryCountValidate(ArtReqDto artReq) { } // 작품 상세 확인 - public ArtResDto getArt(Long artId) { + public ArtResDto getArt(User user, Long artId) { // 있는 작품인지 확인 Art art = findArtByIdWithValidation(artId); @@ -147,11 +164,12 @@ public ArtResDto getArt(Long artId) { userResDto, art.getHeartCount(), art.getDescription(), - OwnArtResDto.from(artPrice) + OwnArtResDto.from(artPrice), + artHeartRepository.findByArtIdAndUserId(art.getId(), user.getId()).isPresent() ); } - + // 작품 하트 추가 @Transactional public int postHeart(User user, Long artId) { // 유효한 작품인지 확인 @@ -174,12 +192,14 @@ public int postHeart(User user, Long artId) { return art.getHeartCount(); } + // 해당 사용자의 해당 작품 하트가 유일한지 확인 private void checkArtHeartUnique(Long userId, Long artId) { if (artHeartRepository.findByArtIdAndUserId(artId, userId).isPresent()) { throw new AppException(ArtErrorCode.ART_HEART_ALREADY_EXIST); } } + // 하트 취소 @Transactional public int deleteHeart(User user, Long artId) { // 유효한 작품인지 확인 @@ -276,16 +296,16 @@ public void checkArtistArtValid(Long artId) { } } - // 작가 작품 유효성 확인 + // 작가 작품 존재 여부 확인 public ArtistArt findArtistArtByArtId(Long artId) { return artistArtRepository.findByArtId(artId).orElseThrow( () -> new AppException(ArtErrorCode.ARTIST_ART_NOT_FOUND) ); } - // 작품 리스트 반환 - public Page getArts(User user, Boolean isFollowing, Long userId, Boolean isCollectorsArt, ArtType artType, + public Page getArts(User user, Boolean isFollowing, Long userId, Boolean isCollectorsArt, + ArtType artType, ArtOrderType order, int page, int size) { // 페이징 데이터 전달 @@ -298,7 +318,8 @@ public Page getArts(User user, Boolean isFollowing, Long use } // 필터링, 정렬에 따른 데이터 가져오기 - Page arts = artRepository.getArtsBySortAndFilterAndPaging(isFollowing, followingIds, userId, isCollectorsArt, artType, + Page arts = artRepository.getArtsBySortAndFilterAndPaging(isFollowing, followingIds, userId, + isCollectorsArt, artType, order, pageable); // 엔티티 dto 매핑 @@ -311,9 +332,9 @@ public Page getArts(User user, Boolean isFollowing, Long use art.getUser().getName(), UserBriefResDto.of( art.getUser().getId(), - art.getUser().getProfileImgUrl(), - DateFormatUtil.formatLocalDateTime(art.getCreatedAt() - )))).toList(); + art.getUser().getName(), + art.getUser().getProfileImgUrl() + ))).toList(); // 페이징된 결과물 반환 return new PageImpl<>(result, pageable, arts.getTotalElements()); @@ -336,7 +357,6 @@ public Page getArtsGroupByArtType(User user, ArtOrderType artTypeSortListRess.add(addArtTypesArtListToArtTypeSortListRess(order, artType, true, pageable)); } - // 사용되지 않은 나머지 ArtType 이름 기반 오름차순 리스트 뽑기 List remainingArtTypes = Arrays.stream(ArtType.values()) .filter(artType -> !excludedArtTypes.contains(artType)) @@ -353,9 +373,11 @@ public Page getArtsGroupByArtType(User user, ArtOrderType } // 작품 분야에 따라 작품 리스트 paging 해서 가져오는 메서드 - private ArtTypeSortListResDto addArtTypesArtListToArtTypeSortListRess(ArtOrderType order, ArtType artType, boolean isUserPreference, Pageable pageable) { + private ArtTypeSortListResDto addArtTypesArtListToArtTypeSortListRess(ArtOrderType order, ArtType artType, + boolean isUserPreference, Pageable pageable) { // 소장 작품만 해당하는 artType에 맞춰 가져오기 - Page arts = artRepository.getArtsBySortAndFilterAndPaging(null, null, null, false, artType, order, pageable); + Page arts = artRepository.getArtsBySortAndFilterAndPaging(null, null, null, false, artType, order, + pageable); // 엔티티 dto 매핑 return ArtTypeSortListResDto.of(artType.getValue(), diff --git a/src/main/java/com/art/cheric/module/collection/controller/CollectionController.java b/src/main/java/com/art/cheric/module/collection/controller/CollectionController.java index 9183429..96cd239 100644 --- a/src/main/java/com/art/cheric/module/collection/controller/CollectionController.java +++ b/src/main/java/com/art/cheric/module/collection/controller/CollectionController.java @@ -16,6 +16,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -39,9 +40,9 @@ public ResponseEntity postCollection(@RequestAttribute("user") User return ResponseEntity.status(201).body(ResponseDto.of(201, collectionId + " 컬렉션이 생성되었습니다.")); } - @PostMapping("/{collectionId}/art") + @PostMapping("/{id}/art") public ResponseEntity postCollectionArt(@RequestAttribute("user") User user, - @PathVariable("collectionId") Long collectionId, + @PathVariable("id") Long collectionId, @RequestBody @Valid ArtIdListReqDto artIdListReq) { collectionService.postCollectionArt(user, collectionId, artIdListReq); return ResponseEntity.status(201).body(ResponseDto.of(201)); @@ -61,4 +62,11 @@ public ResponseEntity getSelfCollectionList(@RequestAttribute("user order); return ResponseEntity.status(200).body(DataResponseDto.of(resDtos, 200)); } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCollection(@RequestAttribute("user") User user, + @PathVariable("id") Long collectionId){ + collectionService.deleteCollection(user, collectionId); + return ResponseEntity.ok(ResponseDto.of(200)); + } } diff --git a/src/main/java/com/art/cheric/module/collection/controller/CollectionControllerDocs.java b/src/main/java/com/art/cheric/module/collection/controller/CollectionControllerDocs.java index bf19a5e..dc66862 100644 --- a/src/main/java/com/art/cheric/module/collection/controller/CollectionControllerDocs.java +++ b/src/main/java/com/art/cheric/module/collection/controller/CollectionControllerDocs.java @@ -244,5 +244,18 @@ public interface CollectionControllerDocs { }) ResponseEntity getSelfCollectionList(User user, CollectionIdListReqDto collectionIdListReq, BasicOrderType sortType); + + @Operation(summary = "컬렉션 삭제 API", description = "컬렉션을 삭제합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Ok", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ResponseDto.class), + examples = + @ExampleObject(value = "{ \"code\": 200, \"message\": \"Ok\" }") + ) + ), + }) + ResponseEntity deleteCollection(User user, Long collectionId); } diff --git a/src/main/java/com/art/cheric/module/collection/domain/repository/CollectionArtRepository.java b/src/main/java/com/art/cheric/module/collection/domain/repository/CollectionArtRepository.java index 0bf4719..06b9397 100644 --- a/src/main/java/com/art/cheric/module/collection/domain/repository/CollectionArtRepository.java +++ b/src/main/java/com/art/cheric/module/collection/domain/repository/CollectionArtRepository.java @@ -1,6 +1,7 @@ package com.art.cheric.module.collection.domain.repository; import com.art.cheric.module.collection.domain.entity.CollectionArt; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,4 +9,5 @@ @Repository public interface CollectionArtRepository extends JpaRepository { Optional findByCollectionIdAndArtId(Long collectionId, Long artId); + List findByCollectionId(Long collectionId); } diff --git a/src/main/java/com/art/cheric/module/collection/service/CollectionService.java b/src/main/java/com/art/cheric/module/collection/service/CollectionService.java index 4643ce9..7960316 100644 --- a/src/main/java/com/art/cheric/module/collection/service/CollectionService.java +++ b/src/main/java/com/art/cheric/module/collection/service/CollectionService.java @@ -80,6 +80,21 @@ public void postCollectionArt(User user, Long collectionId, ArtIdListReqDto artI collectionArtRepository.saveAll(collectionArts); } + // 컬렉션 삭제 + @Transactional + public void deleteCollection(User user, Long collectionId){ + // 있는 컬렉션인지 확인 + Collection collection = collectionRepository.findByIdAndUserId(collectionId, user.getId()).orElseThrow( + () -> new AppException(CollectionErrorCode.COLLECTION_NOT_FOUND) + ); + + // 작품 리스트 삭제 + collectionArtRepository.deleteAll(collectionArtRepository.findByCollectionId(collectionId)); + + // 컬렉션 삭제 + collectionRepository.delete(collection); + } + // 작품 리스트에 없는 작품인지 확인 private void checkCollectionArtIsUnique(Long collectionId, Long artId) { if (collectionArtRepository.findByCollectionIdAndArtId(collectionId, artId).isPresent()) { diff --git a/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionController.java b/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionController.java index 3dcc629..0d02f64 100644 --- a/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionController.java +++ b/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionController.java @@ -44,8 +44,9 @@ public ResponseEntity postExhibition(@RequestAttribute("user") User } @GetMapping("/{id}") - public ResponseEntity getExhibitionContent(@PathVariable(name = "id") Long exhibitionId) { - ExhibitionResDto resDto = exhibitionService.getExhibitionContent(exhibitionId); + public ResponseEntity getExhibitionContent(@RequestAttribute("user") User user, + @PathVariable(name = "id") Long exhibitionId) { + ExhibitionResDto resDto = exhibitionService.getExhibitionContent(user, exhibitionId); return ResponseEntity.status(200).body(DataResponseDto.of(resDto, 200)); } @@ -72,7 +73,7 @@ public ResponseEntity postReview(@RequestAttribute("user") User use } @PostMapping("/{id}/reviews/{reviewId}") - public ResponseEntity postReview(@RequestAttribute("user") User user, + public ResponseEntity postReReview(@RequestAttribute("user") User user, @PathVariable(name = "id") Long exhibitionId, @PathVariable(name = "reviewId") Long reviewId, @RequestBody @Valid ExhibitionReviewReqDto exhibitionReviewReq) { @@ -118,19 +119,21 @@ public ResponseEntity getExhibitions( } @GetMapping("/{id}/reviews") - public ResponseEntity getExhibitionReviews(@PathVariable(name = "id") Long exhibitionId, + public ResponseEntity getExhibitionReviews(@RequestAttribute("user") User user, + @PathVariable(name = "id") Long exhibitionId, @RequestParam(name = "page") int page, @RequestParam(name = "size") int size) { - Page resPage = exhibitionService.getExhibitionReviews(exhibitionId, page, size); + Page resPage = exhibitionService.getExhibitionReviews(user, exhibitionId, page, size); return ResponseEntity.status(200).body( DataPageResponseDto.of(resPage.getContent(), 200, resPage.getTotalElements(), resPage.getTotalPages(), resPage.getSize(), resPage.getNumberOfElements())); } @GetMapping("/{id}/reviews/{reviewId}") - public ResponseEntity getExhibitionReviews(@PathVariable(name = "id") Long exhibitionId, + public ResponseEntity getExhibitionReviews(@RequestAttribute("user") User user, + @PathVariable(name = "id") Long exhibitionId, @PathVariable(name = "reviewId") Long reviewId) { - ExhibitionReviewDetailResDto resDto = exhibitionService.getExhibitionReviewsById(exhibitionId, reviewId); + ExhibitionReviewDetailResDto resDto = exhibitionService.getExhibitionReviewsById(user, exhibitionId, reviewId); return ResponseEntity.status(200).body(DataResponseDto.of(resDto, 200)); } } diff --git a/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionControllerDocs.java b/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionControllerDocs.java index 50143bf..cbd3be2 100644 --- a/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionControllerDocs.java +++ b/src/main/java/com/art/cheric/module/exhibition/controller/ExhibitionControllerDocs.java @@ -13,8 +13,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "Exhibition", description = "전시 관련 API") public interface ExhibitionControllerDocs { @@ -113,7 +111,7 @@ public interface ExhibitionControllerDocs { ) ), }) - ResponseEntity getExhibitionContent(Long exhibitionId); + ResponseEntity getExhibitionContent(User user, Long exhibitionId); @Operation(summary = "전시 하트 추가 API", description = "전시에 하트를 추가합니다.") @ApiResponses({ @@ -201,7 +199,7 @@ public interface ExhibitionControllerDocs { ) ), }) - ResponseEntity postReview(User user, Long exhibitionId, Long reviewId, + ResponseEntity postReReview(User user, Long exhibitionId, Long reviewId, ExhibitionReviewReqDto exhibitionReviewReq); @Operation(summary = "전시 댓글 좋아요 생성 API", description = "전시에 댓글에 좋아요를 답니다.") @@ -287,63 +285,54 @@ ResponseEntity postReview(User user, Long exhibitionId, Long review mediaType = "application/json", schema = @Schema(implementation = ResponseDto.class), examples = - @ExampleObject(value = "{\n" + - " \"code\": 200,\n" + - " \"message\": \"OK\",\n" + - " \"data\": [\n" + - " {\n" + - " \"exhibitionId\": 2,\n" + - " \"name\": \"별빛:하늘을 그리다\",\n" + - " \"font\": \"BASIC\",\n" + - " \"fontColor\": \"BLACK\",\n" + - " \"colors\": [],\n" + - " \"exhibitionBackgroundType\": null,\n" + - " \"coverImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/EXHIBITION_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-exhibitionImage.png\",\n" + - " \"themes\": [\n" + - " \"별\",\n" + - " \"하늘\",\n" + - " \"빛\"\n" + - " ],\n" + - " \"heartCount\": 0,\n" + - " \"hits\": 0,\n" + - " \"userRes\": {\n" + - " \"id\": 1,\n" + - " \"name\": \"artist\",\n" + - " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\"\n" + - " }\n" + - " },\n" + - " {\n" + - " \"exhibitionId\": 1,\n" + - " \"name\": \"별빛:하늘을 그리다\",\n" + - " \"font\": \"BASIC\",\n" + - " \"fontColor\": \"BLACK\",\n" + - " \"colors\": [\n" + - " \"#CF3420\",\n" + - " \"#CF3421\",\n" + - " \"#CF3422\",\n" + - " \"#CF3423\"\n" + - " ],\n" + - " \"exhibitionBackgroundType\": \"TOP_DOWN\",\n" + - " \"coverImgUrl\": null,\n" + - " \"themes\": [\n" + - " \"별\",\n" + - " \"하늘\",\n" + - " \"빛\"\n" + - " ],\n" + - " \"heartCount\": 0,\n" + - " \"hits\": 0,\n" + - " \"userRes\": {\n" + - " \"id\": 1,\n" + - " \"name\": \"artist\",\n" + - " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\"\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"totalElements\": 2,\n" + - " \"totalPages\": 1,\n" + - " \"size\": 3,\n" + - " \"numberOfElements\": 2\n" + - "}") + @ExampleObject(value = "{\n" + + " \"code\": 200,\n" + + " \"message\": \"OK\",\n" + + " \"data\": {\n" + + " \"description\": \"별빛을 나타내는 작품을 모은 전시입니다.\",\n" + + " \"heartCount\": 0,\n" + + " \"hits\": 0,\n" + + " \"exhibitionArtRess\": [\n" + + " {\n" + + " \"description\": \"이러쿵 저러쿵을 통해 수집하게 되었습니다.\",\n" + + " \"reasonForPurchase\": \"별빛을 나타내는 게 마음에 와닿아서 수집하게 되었습니다.\",\n" + + " \"review\": \"별빛을 나타내는 작품을 보는 과정에서 행복했습니다.\",\n" + + " \"artExhibitionRes\": {\n" + + " \"imgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/ARTIST_ART_IMG/5_2_HenriRousseau/116_0_art_image.jpg\",\n" + + " \"cherryPrice\": 2,\n" + + " \"name\": \"세인트 클라우드 공원의 가로수 길\",\n" + + " \"artistName\": \"구스타프 클림트\",\n" + + " \"series\": null,\n" + + " \"horizontalSize\": 0,\n" + + " \"verticalSize\": 0,\n" + + " \"material\": \"캔버스에 유화\",\n" + + " \"madeAt\": \"2002\",\n" + + " \"artTypes\": [\n" + + " \"PAINTING\",\n" + + " \"OIL_PAINTING\"\n" + + " ],\n" + " \"ownArtRes\": {\n" + + " \"price\": null\n" + + " },\n" + + " \"heartCount\": 25,\n" + + " \"collectorsArt\": false\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"userRes\": {\n" + + " \"id\": 1,\n" + + " \"name\": \"artist\",\n" + + " \"description\": \"저는 3년차 회화 분야 작가입니다.\",\n" + + " \"artTypes\": [\n" + + " \"PAINTING\",\n" + + " \"OIL_PAINTING\"\n" + + " ],\n" + + " \"profileImgUrl\": \"https://i.ibb.co/NxM5XzJ/p-image-1.jpg\",\n" + + " \"isFollow\": false\n" + + " },\n" + + " \"exhibitionReviewRes\": null,\n" + + " \"isHeart\": false\n" + + " }\n" + + "}") ) ), }) @@ -359,49 +348,32 @@ ResponseEntity postReview(User user, Long exhibitionId, Long review @ExampleObject(value = "{\n" + " \"code\": 200,\n" + " \"message\": \"OK\",\n" - + " \"data\": [\n" - + " {\n" - + " \"id\": 4,\n" - + " \"review\": \"3수집하신 소장 작품 너무 좋네요!\",\n" - + " \"name\": null,\n" - + " \"heartCount\": 0,\n" - + " \"replyCount\": 0,\n" - + " \"createAt\": \"2024.11.27\"\n" - + " },\n" - + " {\n" - + " \"id\": 3,\n" - + " \"review\": \"2수집하신 소장 작품 너무 좋네요!\",\n" - + " \"name\": null,\n" - + " \"heartCount\": 0,\n" - + " \"replyCount\": 0,\n" - + " \"createAt\": \"2024.11.27\"\n" - + " },\n" - + " {\n" - + " \"id\": 2,\n" - + " \"review\": \"1수집하신 소장 작품 너무 좋네요!\",\n" - + " \"name\": null,\n" - + " \"heartCount\": 0,\n" - + " \"replyCount\": 0,\n" - + " \"createAt\": \"2024.11.27\"\n" - + " },\n" - + " {\n" - + " \"id\": 1,\n" - + " \"review\": \"수집하신 소장 작품 너무 좋네요!\",\n" - + " \"name\": null,\n" - + " \"heartCount\": 0,\n" - + " \"replyCount\": 2,\n" - + " \"createAt\": \"2024.11.27\"\n" - + " }\n" - + " ],\n" - + " \"totalElements\": 4,\n" - + " \"totalPages\": 1,\n" - + " \"size\": 4,\n" - + " \"numberOfElements\": 4\n" + + " \"data\": {\n" + + " \"id\": 16,\n" + + " \"review\": \"신선한 아이디어네요.\",\n" + + " \"name\": \"artist\",\n" + + " \"imgUrl\": \"https://i.ibb.co/NxM5XzJ/p-image-1.jpg\",\n" + + " \"heartCount\": 1,\n" + + " \"createAt\": \"1970.01.01 12:00\",\n" + + " \"replies\": [\n" + + " {\n" + + " \"id\": 15,\n" + + " \"review\": \"bbbb\",\n" + + " \"name\": \"artist\",\n" + + " \"imgUrl\": \"https://i.ibb.co/NxM5XzJ/p-image-1.jpg\",\n" + + " \"heartCount\": 1,\n" + + " \"createAt\": \"1970.01.01 12:00\",\n" + + " \"replies\": [],\n" + + " \"isHeart\": false\n" + + " }\n" + + " ],\n" + + " \"isHeart\": false\n" + + " }\n" + "}") ) ), }) - ResponseEntity getExhibitionReviews(Long exhibitionId, int page,int size); + ResponseEntity getExhibitionReviews( User user, Long exhibitionId, int page,int size); @Operation(summary = "전시 댓글 상세 조회 API", description = "전시 및 댓글 id에 해당하는 댓글과 대댓글을 조회합니다.") @ApiResponses({ @@ -476,6 +448,6 @@ ResponseEntity postReview(User user, Long exhibitionId, Long review ) ), }) - ResponseEntity getExhibitionReviews(Long exhibitionId, Long reviewId); + ResponseEntity getExhibitionReviews(User user, Long exhibitionId, Long reviewId); } diff --git a/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionResDto.java b/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionResDto.java index 1bb3c30..746c757 100644 --- a/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionResDto.java +++ b/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionResDto.java @@ -1,29 +1,16 @@ package com.art.cheric.module.exhibition.dto.res; -import com.art.cheric.global.enums.FontColorType; -import com.art.cheric.global.enums.FontType; -import com.art.cheric.module.user.dto.res.UserResDto; +import com.art.cheric.module.user.dto.res.ExhibitionUserResDto; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; -import java.util.List; - @Schema(description = "전시 보기 기본 DTO") @Getter @Builder(access = AccessLevel.PRIVATE) public class ExhibitionResDto { - - @Schema(description = "전시 제목", example = "별빛을 나타내는 작품을 모은 전시입니다.") - private final String name; - - @Schema(description = "전시 폰트", example = "별빛을 나타내는 작품을 모은 전시입니다.") - private final FontType font; - - @Schema(description = "전시 폰트 색상", example = "BLACK") - private final FontColorType fontColor; - @Schema(description = "전시 설명", example = "별빛을 나타내는 작품을 모은 전시입니다.") private final String description; @@ -37,24 +24,25 @@ public class ExhibitionResDto { private final List exhibitionArtRess; @Schema(description = "전시 등록자 정보 DTO") - private final UserResDto userRes; + private final ExhibitionUserResDto userRes; @Schema(description = "전시 댓글 1위 정보 DTO") private final ExhibitionReviewResDto exhibitionReviewRes; - public static ExhibitionResDto of(String name, FontType font, FontColorType fontColor, String description, int heartCount, int hits, - List exhibitionArtRess, UserResDto userRes, - ExhibitionReviewResDto exhibitionReviewRes) { + @Schema(description = "좋아요 여부", example = "true") + private final Boolean isHeart; + + public static ExhibitionResDto of(String description, int heartCount, int hits, + List exhibitionArtRess, ExhibitionUserResDto userRes, + ExhibitionReviewResDto exhibitionReviewRes, Boolean isHeart) { return ExhibitionResDto.builder() - .name(name) - .font(font) - .fontColor(fontColor) .description(description) .heartCount(heartCount) .hits(hits) .exhibitionArtRess(exhibitionArtRess) .userRes(userRes) .exhibitionReviewRes(exhibitionReviewRes) + .isHeart(isHeart) .build(); } } diff --git a/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewDetailResDto.java b/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewDetailResDto.java index 788101a..47c3ac7 100644 --- a/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewDetailResDto.java +++ b/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewDetailResDto.java @@ -21,6 +21,9 @@ public class ExhibitionReviewDetailResDto { @Schema(description = "댓글 작성자 이름", example = "체리시") private final String name; + @Schema(description = "댓글 작성자 이미지", example = "https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png") + private final String imgUrl; + @Schema(description = "좋아요 수", example = "12") private final int heartCount; @@ -30,15 +33,19 @@ public class ExhibitionReviewDetailResDto { @Schema(description = "대댓글 정보 DTO 리스트") private final List replies = new ArrayList<>(); + @Schema(description = "좋아요 여부", example = "true") + private final Boolean isHeart; - public static ExhibitionReviewDetailResDto of(Long id, String review, String name, int heartCount, - String createAt) { + public static ExhibitionReviewDetailResDto of(Long id, String review, String name, String imgUrl, int heartCount, + String createAt, Boolean isHeart) { return ExhibitionReviewDetailResDto.builder() .id(id) .review(review) .name(name) + .imgUrl(imgUrl) .heartCount(heartCount) .createAt(createAt) + .isHeart(isHeart) .build(); } diff --git a/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewListResDto.java b/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewListResDto.java index d03f453..614f9d5 100644 --- a/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewListResDto.java +++ b/src/main/java/com/art/cheric/module/exhibition/dto/res/ExhibitionReviewListResDto.java @@ -19,6 +19,9 @@ public class ExhibitionReviewListResDto { @Schema(description = "댓글 작성자 이름", example = "체리시") private final String name; + @Schema(description = "댓글 작성자 이미지", example = "https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png") + private final String imgUrl; + @Schema(description = "좋아요 수", example = "12") private final int heartCount; @@ -28,16 +31,20 @@ public class ExhibitionReviewListResDto { @Schema(description = "댓글 작성 시간", example = "2024-12-23") private final String createAt; + @Schema(description = "좋아요 여부", example = "true") + private final Boolean isHeart; - public static ExhibitionReviewListResDto of(Long id, String review, String name, int heartCount, int replyCount, - String createAt) { + public static ExhibitionReviewListResDto of(Long id, String review, String name, String imgUrl, int heartCount, + int replyCount, String createAt, Boolean isHeart) { return ExhibitionReviewListResDto.builder() .id(id) .review(review) .name(name) + .imgUrl(imgUrl) .heartCount(heartCount) .replyCount(replyCount) .createAt(createAt) + .isHeart(isHeart) .build(); } diff --git a/src/main/java/com/art/cheric/module/exhibition/service/ExhibitionService.java b/src/main/java/com/art/cheric/module/exhibition/service/ExhibitionService.java index 78ad95d..b2dc2ec 100644 --- a/src/main/java/com/art/cheric/module/exhibition/service/ExhibitionService.java +++ b/src/main/java/com/art/cheric/module/exhibition/service/ExhibitionService.java @@ -162,7 +162,7 @@ private ExhibitionArt createExhibitionArt(Exhibition exhibition, ExhibitionArtRe } // 전시 내용 확인 - public ExhibitionResDto getExhibitionContent(Long exhibitionId) { + public ExhibitionResDto getExhibitionContent(User user, Long exhibitionId) { // 전시 있는지 확인 Exhibition exhibition = findExhibitionById(exhibitionId); @@ -181,15 +181,13 @@ public ExhibitionResDto getExhibitionContent(Long exhibitionId) { } return ExhibitionResDto.of( - exhibition.getName(), - exhibition.getFont(), - exhibition.getFontColor(), exhibition.getDescription(), exhibition.getHeartCount(), exhibition.getHits(), exhibitionArtRess, - userService.createUserResDto(exhibition.getUser()), - exhibitionReviewRes + userService.createExhibitionUserResDto(exhibition.getUser(), user.getId()), + exhibitionReviewRes, + exhibitionHeartRepository.findByUserIdAndExhibitionId(user.getId(), exhibitionId).isPresent() ); } @@ -467,7 +465,7 @@ public Page getExhibitions(Long artId, Long userId, Exhibi return new PageImpl<>(exhibitionListRess, PageRequest.of((int) (exhibitionPage.getPageable().getOffset() / exhibitionPage.getPageable().getPageSize()), exhibitionPage.getPageable().getPageSize()), exhibitionPage.getTotalPages()); } - public Page getExhibitionReviews(Long exhibitionId, int page, int size) { + public Page getExhibitionReviews(User user, Long exhibitionId, int page, int size) { Pageable pageable = PageRequest.of(page, size); @@ -481,10 +479,12 @@ public Page getExhibitionReviews(Long exhibitionId, exhibitionReview.getId(), exhibitionReview.getMessage(), exhibitionReview.getUser().getName(), + exhibitionReview.getUser().getProfileImgUrl(), exhibitionReview.getHeartCount(), exhibitionReviewRepository.countByExhibitionIdAndExhibitionReviewId(exhibitionId, exhibitionReview.getId()), - DateFormatUtil.formatLocalDateTime(exhibitionReview.getCreatedAt()) + DateFormatUtil.formatLocalDateTime(exhibitionReview.getCreatedAt()), + exhibitionReviewHeartRepository.findByExhibitionReviewIdAndUserId(exhibitionReview.getId(), user.getId()).isPresent() ) ); } @@ -492,42 +492,44 @@ public Page getExhibitionReviews(Long exhibitionId, return new PageImpl<>(exhibitionReviewListRess, pageable, exhibitionReviewPage.getTotalPages()); } - public ExhibitionReviewDetailResDto getExhibitionReviewsById(Long exhibitionId, Long reviewId) { + public ExhibitionReviewDetailResDto getExhibitionReviewsById(User user, Long exhibitionId, Long reviewId) { ExhibitionReview exhibitionReview = exhibitionReviewRepository.findByIdAndExhibitionId(reviewId, exhibitionId) .orElseThrow(() -> new AppException(ExhibitionErrorCode.REVIEW_NOT_EXIST)); // 전시 기본 댓글 조회 - ExhibitionReviewDetailResDto exhibitionReviewDetailRes = convertToDetailResDto(exhibitionReview); + ExhibitionReviewDetailResDto exhibitionReviewDetailRes = convertToDetailResDto(user.getId(), exhibitionReview); // 대댓글 리스트를 가져와서 재귀적으로 처리 - List replies = getRepliesRecursively(reviewId); + List replies = getRepliesRecursively(user.getId(), reviewId); // 대댓글 정보를 메인 댓글 DTO에 추가 return exhibitionReviewDetailRes.addExhibitionReviewListResDto(replies); } - private List getRepliesRecursively(Long parentReviewId) { + private List getRepliesRecursively(Long userId, Long parentReviewId) { // 대댓글을 조회 List replies = exhibitionReviewRepository.findReplyById(parentReviewId); // 대댓글 각각에 대해 DTO 변환 및 재귀 호출 List replyDtos = new ArrayList<>(); for (ExhibitionReview reply : replies) { - ExhibitionReviewDetailResDto replyDto = convertToDetailResDto(reply); - replyDto.addExhibitionReviewListResDto(getRepliesRecursively(reply.getId())); // 재귀 호출 + ExhibitionReviewDetailResDto replyDto = convertToDetailResDto(userId, reply); + replyDto.addExhibitionReviewListResDto(getRepliesRecursively(userId, reply.getId())); // 재귀 호출 replyDtos.add(replyDto); } return replyDtos; } - private ExhibitionReviewDetailResDto convertToDetailResDto(ExhibitionReview review) { + private ExhibitionReviewDetailResDto convertToDetailResDto(Long userId, ExhibitionReview review) { return ExhibitionReviewDetailResDto.of( review.getId(), review.getMessage(), review.getUser().getName(), + review.getUser().getProfileImgUrl(), review.getHeartCount(), - DateFormatUtil.formatLocalDateTime(review.getCreatedAt()) + DateFormatUtil.formatLocalDateTime(review.getCreatedAt()), + exhibitionReviewHeartRepository.findByExhibitionReviewIdAndUserId(review.getId(), userId).isPresent() ); } } diff --git a/src/main/java/com/art/cheric/module/following/controller/FollowController.java b/src/main/java/com/art/cheric/module/following/controller/FollowController.java index cda7996..c7958b8 100644 --- a/src/main/java/com/art/cheric/module/following/controller/FollowController.java +++ b/src/main/java/com/art/cheric/module/following/controller/FollowController.java @@ -33,7 +33,7 @@ public ResponseEntity postFollow(@RequestAttribute("user") User use public ResponseEntity deleteFollow(@RequestAttribute("user") User user, @PathVariable(name = "followed-id") Long followedUserId) { followService.deleteFollow(user, followedUserId); - return ResponseEntity.status(200).body(ResponseDto.of(200, "Ok")); + return ResponseEntity.ok(ResponseDto.of(200)); } @GetMapping("/{userId}") diff --git a/src/main/java/com/art/cheric/module/user/controller/UserController.java b/src/main/java/com/art/cheric/module/user/controller/UserController.java index 24742df..7176fb9 100644 --- a/src/main/java/com/art/cheric/module/user/controller/UserController.java +++ b/src/main/java/com/art/cheric/module/user/controller/UserController.java @@ -99,17 +99,26 @@ public ResponseEntity getUserFollowInfoList( resPage.getTotalPages(), resPage.getSize(), resPage.getNumberOfElements())); } - @GetMapping("/recommend") public ResponseEntity getUserRecommend( @RequestAttribute("user") User user, + @Schema(description = "false 라면, 컬렉터만 / true 라면 작가만 / null이라면 모두 반환합니다.") @RequestParam(name = "isArtist") @Nullable Boolean isArtist, @RequestParam(name = "artType") ArtType artType, @RequestParam(name = "order") UserOrderType order, @Schema(description = "0번부터 시작합니다. 조회할 페이지 번호를 의미합니다.") @RequestParam(name = "page") int page, @Schema(description = "조회할 페이지 크기를 의미합니다.") @RequestParam(name = "size") int size) { - List resDto = userService.getUserRecommend(user, artType, order, page, size); - return ResponseEntity.status(201).body(DataResponseDto.of(resDto, 201)); - + List resDto = userService.getUserRecommend(user, artType, isArtist, order, page, size); + // TODO 추후에 Paging 정보 같이주도록 변경 + return ResponseEntity.status(200).body(DataResponseDto.of(resDto, 200)); } + @GetMapping("/hot") + public ResponseEntity getHotUser( + @Schema(description = "false 라면, 컬렉터만 / true 라면 작가만 / null이라면 모두 반환합니다.") @RequestParam(name = "isArtist") @Nullable Boolean isArtist, + @Schema(description = "0번부터 시작합니다. 조회할 페이지 번호를 의미합니다.") @RequestParam(name = "page") int page, + @Schema(description = "조회할 페이지 크기를 의미합니다.") @RequestParam(name = "size") int size) { + Page resPage = userService.getHotUser(isArtist, page, size); + return ResponseEntity.status(200).body(DataPageResponseDto.of(resPage.getContent(), 200, resPage.getTotalElements(), + resPage.getTotalPages(), resPage.getSize(), resPage.getNumberOfElements())); + } } diff --git a/src/main/java/com/art/cheric/module/user/controller/UserControllerDocs.java b/src/main/java/com/art/cheric/module/user/controller/UserControllerDocs.java index c0b8f4c..5bcf5a4 100644 --- a/src/main/java/com/art/cheric/module/user/controller/UserControllerDocs.java +++ b/src/main/java/com/art/cheric/module/user/controller/UserControllerDocs.java @@ -12,9 +12,8 @@ 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.springframework.http.ResponseEntity; - import java.util.List; +import org.springframework.http.ResponseEntity; @Tag(name = "User", description = "사용자 관련 API") public interface UserControllerDocs { @@ -269,12 +268,14 @@ public interface UserControllerDocs { " {\n" + " \"id\": 3,\n" + " \"name\": \"test2\",\n" + - " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\"\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\"\n" + + " },\n" + " {\n" + " \"id\": 2,\n" + " \"name\": \"test1\",\n" + - " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\"\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\"\n" + + " }\n" + " ],\n" + " \"totalElements\": 2,\n" + @@ -304,7 +305,8 @@ ResponseEntity getUserBriefList( " {\n" + " \"id\": 3,\n" + " \"name\": \"test2\",\n" + - " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\",\n" + + " \"artTypes\": [\n" + " \"WATER_PAINTING\",\n" + " \"OIL_PAINTING\"\n" + @@ -314,7 +316,8 @@ ResponseEntity getUserBriefList( " {\n" + " \"id\": 2,\n" + " \"name\": \"test1\",\n" + - " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png\",\n" + + " \"artTypes\": [\n" + " \"ORIENTAL_PAINTING\",\n" + " \"DESIGN_ART\"\n" + @@ -334,7 +337,7 @@ ResponseEntity getUserFollowInfoList( User user, Boolean isFollowing, Boolean isArtist, List artTypes, UserOrderType order, int page, int size); - @Operation(summary = "분야 기반 사용자 추천 리스트 조회 API", description = "조회한 분야를 기반으로 추천하는 컬렉터를 제공합니다.") + @Operation(summary = "분야 기반 사용자 추천 리스트 조회 API", description = "조회한 분야를 기반으로 추천하는 사용자를 제공합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Ok", content = @Content( @@ -406,6 +409,82 @@ ResponseEntity getUserFollowInfoList( ) ) }) - ResponseEntity getUserRecommend(User user, ArtType artType, UserOrderType order, int page, int size); + ResponseEntity getUserRecommend(User user, Boolean isArtist, ArtType artType, UserOrderType order, + int page, int size); + + @Operation(summary = "인기 기반 사용자 추천 리스트 조회 API", description = "팔로워 기반으로 사용자 추천 리스트를 제공합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Ok", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ResponseDto.class), + examples = + @ExampleObject(value = "{\n" + + " \"code\": 200,\n" + + " \"message\": \"OK\",\n" + + " \"data\": [\n" + + " {\n" + + " \"id\": 28,\n" + + " \"name\": \"앙리 루소\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/profile/22_profile_img.jpg\",\n" + + " \"artTypes\": [\n" + + " \"WATER_PAINTING\",\n" + + " \"PAINTING\"\n" + + " ],\n" + + " \"description\": null,\n" + + " \"artImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/ARTIST_ART_IMG/7_1_Rembrandt/193_0_art_image.jpg\"\n" + + " },\n" + + " {\n" + + " \"id\": 31,\n" + + " \"name\": \"캘빈 킴\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/profile/25_profile_img.jpg\",\n" + + " \"artTypes\": [\n" + + " \"WATER_PAINTING\",\n" + + " \"PAINTING\"\n" + + " ],\n" + + " \"description\": null,\n" + + " \"artImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/ARTIST_ART_IMG/7_4_LeeManIk/209_0_art_image.jpg\"\n" + + " },\n" + + " {\n" + + " \"id\": 20,\n" + + " \"name\": \"에드가 드가\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/profile/14_profile_img.jpg\",\n" + + " \"artTypes\": [\n" + + " \"DRAWING_ART\"\n" + + " ],\n" + + " \"description\": null,\n" + + " \"artImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/ARTIST_ART_IMG/3_3_EgonSchiele/57_0_art_image.jpg\"\n" + + " },\n" + + " {\n" + + " \"id\": 18,\n" + + " \"name\": \"알브레히트 뒤러\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/profile/12_profile_img.jpg\",\n" + + " \"artTypes\": [\n" + + " \"DRAWING_ART\"\n" + + " ],\n" + + " \"description\": null,\n" + + " \"artImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/ARTIST_ART_IMG/3_1_MarcChagall/47_0_art_image.png\"\n" + + " },\n" + + " {\n" + + " \"id\": 35,\n" + + " \"name\": \"이상원\",\n" + + " \"profileImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/profile/29_profile_img.jpg\",\n" + + " \"artTypes\": [\n" + + " \"OIL_PAINTING\",\n" + + " \"PAINTING\"\n" + + " ],\n" + + " \"description\": null,\n" + + " \"artImgUrl\": \"https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/ARTIST_ART_IMG/6_4_SangwonLee/176_0_art_image.jpg\"\n" + + " }\n" + + " ],\n" + + " \"totalElements\": 38,\n" + + " \"totalPages\": 8,\n" + + " \"size\": 5,\n" + + " \"numberOfElements\": 5\n" + + "}") + ) + ) + }) + ResponseEntity getHotUser(Boolean isArtist, int page, int size); } diff --git a/src/main/java/com/art/cheric/module/user/dto/res/ExhibitionUserResDto.java b/src/main/java/com/art/cheric/module/user/dto/res/ExhibitionUserResDto.java new file mode 100644 index 0000000..4bb9c20 --- /dev/null +++ b/src/main/java/com/art/cheric/module/user/dto/res/ExhibitionUserResDto.java @@ -0,0 +1,45 @@ +package com.art.cheric.module.user.dto.res; + + +import com.art.cheric.global.enums.ArtType; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; + +@Schema(description = "전시 제작자 간단 정보 DTO") +@Getter +@Builder(access = AccessLevel.PRIVATE) +public class ExhibitionUserResDto { + + @Schema(description = "사용자 id", example = "1") + private final Long id; + + @Schema(description = "사용자 이름", example = "이예림") + private final String name; + + @Schema(description = "사용자 소개", example = "저는 3년차 컬렉터로, 회화 분야를 주로 수집하고 있습니다.") + private final String description; + + @Schema(description = "사용자 선호 분야", example = "[\"PAINTING\", \"OIL_PAINTING\"]") + private final List artTypes; + + @Schema(description = "사용자 프로필 이미지", example = "https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png") + private final String profileImgUrl; + + @Schema(description = "팔로우 여부", example = "true") + private final Boolean isFollow; + + public static ExhibitionUserResDto of(Long id, String name, String description, List artTypes, + String profileImgUrl, Boolean isFollow) { + return ExhibitionUserResDto.builder() + .id(id) + .name(name) + .description(description) + .artTypes(artTypes) + .profileImgUrl(profileImgUrl) + .isFollow(isFollow) + .build(); + } +} diff --git a/src/main/java/com/art/cheric/module/user/dto/res/HotUserListResDto.java b/src/main/java/com/art/cheric/module/user/dto/res/HotUserListResDto.java new file mode 100644 index 0000000..c9d645c --- /dev/null +++ b/src/main/java/com/art/cheric/module/user/dto/res/HotUserListResDto.java @@ -0,0 +1,44 @@ +package com.art.cheric.module.user.dto.res; + +import com.art.cheric.global.enums.ArtType; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; + +@Schema(description = "hot한 사용자 추천 리스트 응답 DTO") +@Getter +@Builder(access = AccessLevel.PRIVATE) +public class HotUserListResDto { + + @Schema(description = "사용자 id", example = "1") + private final Long id; + + @Schema(description = "사용자 이름", example = "이예림") + private final String name; + + @Schema(description = "사용자 프로필 이미지", example = "https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/USER_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-profileimage.png") + private final String profileImgUrl; + + @Schema(description = "사용자 선호 분야", example = "[\"PAINTING\", \"OIL_PAINTING\"]") + private final List artTypes; + + @Schema(description = "사용자 소개", example = "저는 3년차 컬렉터로, 회화 분야를 주로 수집하고 있습니다.") + private final String description; + + @Schema(description = "작품 이미지 경로", example = "https://cheric-bucket.s3.ap-northeast-2.amazonaws.com/OWN_ART_IMG/1/716dc032-40da-4e9a-97a1-e27ea8abbbd2-ArtImage1.png") + private final String artImgUrl; + + public static HotUserListResDto of(Long id, String name, String profileImgUrl, List artTypes, + String description, String artImgUrl) { + return HotUserListResDto.builder() + .id(id) + .name(name) + .profileImgUrl(profileImgUrl) + .artTypes(artTypes) + .description(description) + .artImgUrl(artImgUrl) + .build(); + } +} diff --git a/src/main/java/com/art/cheric/module/user/service/UserService.java b/src/main/java/com/art/cheric/module/user/service/UserService.java index fb8f4cc..8b1f479 100644 --- a/src/main/java/com/art/cheric/module/user/service/UserService.java +++ b/src/main/java/com/art/cheric/module/user/service/UserService.java @@ -9,6 +9,7 @@ import com.art.cheric.global.util.GoogleOAuthUtil; import com.art.cheric.global.util.JwtUtil; import com.art.cheric.global.util.RedisUtil; +import com.art.cheric.module.art.domain.entity.Art; import com.art.cheric.module.art.domain.repository.ArtRepository; import com.art.cheric.module.art.dto.res.ArtBriefResDto; import com.art.cheric.module.artist.dto.req.ArtistBasicReqDto; @@ -18,25 +19,32 @@ import com.art.cheric.module.user.domain.repository.UserPartRepository; import com.art.cheric.module.user.domain.repository.UserRepository; import com.art.cheric.module.user.dto.req.SignUpReqDto; -import com.art.cheric.module.user.dto.res.*; +import com.art.cheric.module.user.dto.res.ExhibitionUserResDto; +import com.art.cheric.module.user.dto.res.HotUserListResDto; +import com.art.cheric.module.user.dto.res.LoginResDto; +import com.art.cheric.module.user.dto.res.UserBrief2ResDto; +import com.art.cheric.module.user.dto.res.UserBriefResDto; +import com.art.cheric.module.user.dto.res.UserDetailResDto; +import com.art.cheric.module.user.dto.res.UserListResDto; +import com.art.cheric.module.user.dto.res.UserResDto; import com.art.cheric.module.user.error.UserErrorCode; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -50,6 +58,7 @@ public class UserService { private final FollowRepository followRepository; private final ArtRepository artRepository; + // 로그인 @Transactional public LoginResDto getGoogleLogin(String idToken, String fcmToken, String deviceToken) { // 요청 인자 유효성 검사 @@ -82,6 +91,7 @@ public LoginResDto getGoogleLogin(String idToken, String fcmToken, String device return LoginResDto.of(jwtVo.getAccessToken(), jwtVo.getRefreshToken(), user.getInfo() == null); } + // refreshToken을 통한 accessToken 생성 @Transactional public LoginResDto getAccessToken(String refreshToken) { if (refreshToken.isBlank()) { @@ -107,6 +117,7 @@ public LoginResDto getAccessToken(String refreshToken) { return LoginResDto.of(jwtVo.getAccessToken(), jwtVo.getRefreshToken(), tokenUser.getInfo() == null); } + // 사용자 회원가입 @Transactional public void postSignUp(User user, SignUpReqDto signUpReq) { // 사용자 정보 업데이트 @@ -121,31 +132,20 @@ public void postSignUp(User user, SignUpReqDto signUpReq) { } + // 사용자 이름 중복 검사 public void checkNameIsDuplicated(String name) { checkNameIsBlankOrSizeError(name); checkArtistNameIsDuplicated(name); } - private void checkNameIsBlankOrSizeError(String name) { - if (name.isBlank()) { - throw new AppException(UserErrorCode.NAME_REQUIRED); - } else if (!(name.length() >= 2 && name.length() <= 10)) { - throw new AppException(UserErrorCode.NAME_SIZE_ERROR); - } - } - - private void checkArtistNameIsDuplicated(String name) { - userRepository.findByIsValidateArtistFalseAndName(name).ifPresent(user -> { - throw new AppException(UserErrorCode.NAME_DUPLICATED); - }); - } - + // 사용자 로그아웃 @Transactional public void deleteGoogleLogout(User user) { // 사용자 refreshToken 삭제 redisUtil.delete(user.getId() + "_refresh"); } + // 사용자 상세 정보 조회 public UserDetailResDto getUserDetailInfo(User user, Long userId) { User finalUser = user; if (userId != null) { @@ -171,25 +171,9 @@ public UserDetailResDto getUserDetailInfo(User user, Long userId) { ); } - - public UserResDto createUserResDto(User user) { - return UserResDto.of( - user.getId(), - user.getName(), - user.getInfo(), - getArtTypes(user.getUserParts()), - user.getProfileImgUrl() - ); - } - - - private List getArtTypes(List userParts) { - return userParts.stream() - .map(UserPart::getUserArtType) - .collect(Collectors.toList()); - } - - public Page getUserBriefList(User user, Boolean isFollowing, Boolean isArtist, List artTypes, + // 사용자 간단 정보 리스트 조회 + public Page getUserBriefList(User user, Boolean isFollowing, Boolean isArtist, + List artTypes, UserOrderType order, int page, int size) { // 페이징 데이터 전달 @@ -215,9 +199,10 @@ public Page getUserBriefList(User user, Boolean isFollowing, Bo return new PageImpl<>(result, pageable, users.getTotalElements()); } - public Page getUserFollowInfoList(User user, Boolean isFollowing, Boolean isArtist, List artTypes, + // 사용자 간단 정보 + follow 여부 정보 리스트 조회 + public Page getUserFollowInfoList(User user, Boolean isFollowing, Boolean isArtist, + List artTypes, UserOrderType order, int page, int size) { - // 페이징 데이터 전달 Pageable pageable = PageRequest.of(page, size); @@ -243,6 +228,89 @@ public Page getUserFollowInfoList(User user, Boolean isFollowi return new PageImpl<>(result, pageable, users.getTotalElements()); } + // 사용자 추천 + public List getUserRecommend(User user, ArtType artTypes, Boolean isArtist, + UserOrderType order, int page, int size) { + // 페이징 데이터 전달 + Pageable pageable = PageRequest.of(page, size); + + // 팔로잉 필터라면 팔로잉 id 전달 + 본인 id 제외 > 추천이기에 + List followingIds = new ArrayList<>(findFollowingIdsListByUserId(user.getId())); + followingIds.add(user.getId()); + + // 필터링, 정렬에 따른 데이터 가져오기 + Page users = userRepository.getUsersBySortAndFilterAndPaging(false, + followingIds, isArtist, List.of(artTypes), order, pageable); + + // 엔티티 dto 매핑 + return users.stream().map( + userItem -> UserListResDto.of( + userItem.getId(), + userItem.getName(), + userItem.getProfileImgUrl(), + artRepository.getArtsByUserIdAndCollectorsArtFalseOrderByCreatedAtDesc(userItem.getId()) + .stream().map( + // TODO 작품 유효성 검사 필요 + art -> ArtBriefResDto.of( + art.getId(), + art.isCollectorsArt(), + art.getImgUrl(), + art.getCherryPrice(), + art.getName()) + ).toList()) + ).toList(); + + } + + // hot 한 작가 추천 + public Page getHotUser(Boolean isArtist, int page, int size) { + // 페이징 데이터 전달 + Pageable pageable = PageRequest.of(page, size); + + // 필터링, 정렬에 따른 데이터 가져오기 + Page users = userRepository.getUsersBySortAndFilterAndPaging(null, null, isArtist, + null, UserOrderType.FOLLOWER, pageable); + + // 엔티티 dto 매핑 + List result = users.stream().map( + userItem -> { + + Pageable pageableArt = PageRequest.of(0, 1, Sort.by(Sort.Order.desc("createdAt"))); + List arts = artRepository.findMostRecentArtByUserId(userItem.getId(), pageableArt); + Art art = arts.isEmpty() ? null : arts.get(0); + + // TODO 작품 유효성 검사 필요 + + return HotUserListResDto.of( + userItem.getId(), + userItem.getName(), + userItem.getProfileImgUrl(), + getArtTypes(userItem.getUserParts()), + userItem.getInfo(), + art != null ? art.getImgUrl() : null + ); + }).toList(); + + // 페이징된 결과를 반환 + return new PageImpl<>(result, pageable, users.getTotalElements()); + } + + // 이름 유효성 검사 + private void checkNameIsBlankOrSizeError(String name) { + if (name.isBlank()) { + throw new AppException(UserErrorCode.NAME_REQUIRED); + } else if (!(name.length() >= 2 && name.length() <= 10)) { + throw new AppException(UserErrorCode.NAME_SIZE_ERROR); + } + } + + // 이름 중복 검사 + private void checkArtistNameIsDuplicated(String name) { + userRepository.findByIsValidateArtistFalseAndName(name).ifPresent(user -> { + throw new AppException(UserErrorCode.NAME_DUPLICATED); + }); + } + // id 기반 사용자 조회 public User findUserById(Long userId) { return userRepository.findById(userId.toString()).orElseThrow( @@ -250,6 +318,36 @@ public User findUserById(Long userId) { ); } + // 사용자 기본 dto 생성 + public UserResDto createUserResDto(User user) { + return UserResDto.of( + user.getId(), + user.getName(), + user.getInfo(), + getArtTypes(user.getUserParts()), + user.getProfileImgUrl() + ); + } + + // 전시 제작자 기본 dto 생성 + public ExhibitionUserResDto createExhibitionUserResDto(User user,Long userId) { + return ExhibitionUserResDto.of( + user.getId(), + user.getName(), + user.getInfo(), + getArtTypes(user.getUserParts()), + user.getProfileImgUrl(), + followRepository.findByFollowingUserIdAndFollowedUserId(userId, user.getId()).isPresent() + ); + } + + // 사용자 선호 분야 ArtType 으로 가져오기 + private List getArtTypes(List userParts) { + return userParts.stream() + .map(UserPart::getUserArtType) + .collect(Collectors.toList()); + } + // 유저 모두 저장 public void saveAllUser(List user) { userRepository.saveAll(user); @@ -284,34 +382,4 @@ private List findFollowingIdsListByUserId(Long userId) { ).toList(); } - public List getUserRecommend(User user, ArtType artTypes, - UserOrderType order, int page, int size) { - // 페이징 데이터 전달 - Pageable pageable = PageRequest.of(page, size); - - // 팔로잉 필터라면 팔로잉 id 전달 + 본인 id 제외 > 추천이기에 - List followingIds = new ArrayList<>(findFollowingIdsListByUserId(user.getId())); - followingIds.add(user.getId()); - - - // 필터링, 정렬에 따른 데이터 가져오기 - Page users = userRepository.getUsersBySortAndFilterAndPaging(false, - followingIds, true, List.of(artTypes), order, pageable); - - - // 엔티티 dto 매핑 - return users.stream().map( - userItem -> UserListResDto.of( - userItem.getId(), - userItem.getName(), - userItem.getProfileImgUrl(), - artRepository.getArtsByUserIdAndCollectorsArtFalseOrderByCreatedAtDesc(userItem.getId()).stream().map( - art -> ArtBriefResDto.of( - art.getId(), - art.isCollectorsArt(), - art.getImgUrl(), - art.getCherryPrice(), - art.getName())).toList())).toList(); - - } }