From 988bb01d41fdbec7d09252492aa8f7df40a5d030 Mon Sep 17 00:00:00 2001 From: lulyulalla Date: Sat, 7 Jun 2025 01:30:40 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20:=20feat=20:=20course=EB=9E=91=20=EB=B6=81=EB=A7=88?= =?UTF-8?q?=ED=81=AC=20=EC=A2=8B=EC=95=84=EC=9A=94=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20togle=ED=98=95=EC=8B=9D=20https://github.c?= =?UTF-8?q?om/freeMates/FreeMates=5FBackend/issues/122?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BookmarkController.java | 55 +++++---- .../controller/CourseController.java | 39 ++++++ .../freemates/controller/PlaceController.java | 3 +- ...BookmarkResponse.java => BookmarkDto.java} | 7 +- .../jombi/freemates/model/dto/CourseDto.java | 9 +- .../freemates/model/dto/CoursePlaceDto.java | 25 ---- .../freemates/model/dto/GeoCodePlaceDto.java | 27 ---- .../jombi/freemates/model/dto/PlaceDto.java | 2 + .../freemates/model/postgres/Bookmark.java | 7 ++ .../model/postgres/BookmarkLike.java | 32 +++++ .../freemates/model/postgres/Course.java | 7 ++ .../freemates/model/postgres/CourseLike.java | 36 ++++++ .../model/postgres/id/BookmarkLikeId.java | 21 ++++ .../model/postgres/id/CourseLikeId.java | 20 +++ .../repository/BookmarkLikeRepository.java | 11 ++ .../repository/CourseLikeRepository.java | 12 ++ .../freemates/service/BookmarkService.java | 73 ++++++++--- .../freemates/service/CourseService.java | 116 +++++++++++------- .../jombi/freemates/service/PlaceService.java | 16 +-- .../freemates/util/exception/ErrorCode.java | 4 + 20 files changed, 358 insertions(+), 164 deletions(-) rename src/main/java/jombi/freemates/model/dto/{BookmarkResponse.java => BookmarkDto.java} (82%) delete mode 100644 src/main/java/jombi/freemates/model/dto/CoursePlaceDto.java delete mode 100644 src/main/java/jombi/freemates/model/dto/GeoCodePlaceDto.java create mode 100644 src/main/java/jombi/freemates/model/postgres/BookmarkLike.java create mode 100644 src/main/java/jombi/freemates/model/postgres/CourseLike.java create mode 100644 src/main/java/jombi/freemates/model/postgres/id/BookmarkLikeId.java create mode 100644 src/main/java/jombi/freemates/model/postgres/id/CourseLikeId.java create mode 100644 src/main/java/jombi/freemates/repository/BookmarkLikeRepository.java create mode 100644 src/main/java/jombi/freemates/repository/CourseLikeRepository.java diff --git a/src/main/java/jombi/freemates/controller/BookmarkController.java b/src/main/java/jombi/freemates/controller/BookmarkController.java index 48bce14..ce8ea39 100644 --- a/src/main/java/jombi/freemates/controller/BookmarkController.java +++ b/src/main/java/jombi/freemates/controller/BookmarkController.java @@ -10,13 +10,12 @@ import jombi.freemates.model.constant.PinColor; import jombi.freemates.model.constant.Visibility; import jombi.freemates.model.dto.BookmarkRequest; -import jombi.freemates.model.dto.BookmarkResponse; +import jombi.freemates.model.dto.BookmarkDto; import jombi.freemates.model.dto.CustomUserDetails; import jombi.freemates.model.dto.PlaceDto; import jombi.freemates.service.BookmarkService; import jombi.freemates.util.docs.ApiChangeLog; import jombi.freemates.util.docs.ApiChangeLogs; -import lombok.Builder.Default; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -78,7 +77,7 @@ public class BookmarkController { @PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity create( + public ResponseEntity create( @RequestParam String title, @RequestParam String description, @RequestParam PinColor pinColor, @@ -88,13 +87,14 @@ public ResponseEntity create( MultipartFile image, @AuthenticationPrincipal CustomUserDetails user ) { - BookmarkRequest req = new BookmarkRequest(); - req.setTitle(title); - req.setDescription(description); - req.setPinColor(pinColor); - req.setVisibility(visibility); - BookmarkResponse response = bookmarkService.create(user, req, image); - return ResponseEntity.status(HttpStatus.CREATED).body(response); + BookmarkRequest req = BookmarkRequest.builder() + .title(title) + .description(description) + .pinColor(pinColor) + .visibility(visibility) + .build(); + BookmarkDto dto = bookmarkService.create(user, req, image); + return ResponseEntity.status(HttpStatus.CREATED).body(dto); } @ApiChangeLogs({ @@ -123,7 +123,7 @@ public ResponseEntity create( """ ) @GetMapping("/mylist") - public List getMyBookmarks( + public List getMyBookmarks( @AuthenticationPrincipal CustomUserDetails customUserDetails ) { return bookmarkService.getMyBookmarks(customUserDetails); @@ -161,13 +161,13 @@ public List getMyBookmarks( """ ) @GetMapping("/list") - public ResponseEntity> getBookmarks( + public ResponseEntity> getBookmarks( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "PUBLIC") Visibility visibility ) { log.debug("page:{}, size:{}, visibility:{}", page, size, visibility); - Page bookmarks = bookmarkService.getBookmarks(page, size, visibility); + Page bookmarks = bookmarkService.getBookmarks(page, size, visibility); return ResponseEntity.ok(bookmarks); } @@ -215,34 +215,39 @@ public ResponseEntity addPlaceToBookmark( bookmarkService.addPlaceToBookmark(customUserDetails, bookmarkId, placeId); return ResponseEntity.ok().build(); // 혹은 204 No Content } + @ApiChangeLogs({ @ApiChangeLog( - date = "2025-06-05", + date = "2025-06-06", author = Author.LEEDAYE, - issueNumber = 114, - description = "북마크에 따른 장소 목록 가져오기" + issueNumber = 105, + description = "좋아요~" ) }) @Operation( - summary = "북마크에 따른 장소 목록 가져오기", + summary = "북마크 좋아요", description = """ ## 인증(JWT): **필요** ## 요청 파라미터 - **Path Variable** - - `bookmarkId` (UUID): 북마크 ID + - `bookmarkId` (UUID): 좋아요를 누를 즐겨찾기 ID - ## 반환값 (`List`) - - 즐겨찾기에 추가된 장소 목록 + ## 반환값 + - **HTTP Status 200 OK** (혹은 204 No Content) ## 에러코드 - - `BOOKMARK_NOT_FOUND (404)`: 존재하지 않는 북마크입니다. + - `UNAUTHORIZED (401)`: 인증되지 않은 사용자입니다. + - `PLACE_NOT_FOUND (404)`: 존재하지 않는 장소입니다. """ ) - - @GetMapping("/places/{bookmarkId}") - public List getPlaces(@PathVariable UUID bookmarkId) { - return bookmarkService.getPlacesByBookmarkId(bookmarkId); + @PostMapping("/like/{bookmarkId}") + public ResponseEntity likeBookmark( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable("bookmarkId") UUID bookmarkId + ) { + bookmarkService.likeBookmark(customUserDetails, bookmarkId); + return ResponseEntity.ok().build(); // 혹은 204 No Content } } diff --git a/src/main/java/jombi/freemates/controller/CourseController.java b/src/main/java/jombi/freemates/controller/CourseController.java index ed7758a..ab80641 100644 --- a/src/main/java/jombi/freemates/controller/CourseController.java +++ b/src/main/java/jombi/freemates/controller/CourseController.java @@ -22,6 +22,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; 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; @@ -182,6 +183,44 @@ public ResponseEntity> getCourses( return ResponseEntity.ok(pagedCourses); } + /** + * 코스 좋아요 + */ + @ApiChangeLogs({ + @ApiChangeLog( + date = "2025-06-06", + author = Author.LEEDAYE, + issueNumber = 105, + description = "코스 좋아요 추가/취소" + ) + }) + @Operation( + summary = "코스 좋아요", + description = """ + ## 인증(JWT): **필요** + + ## 요청 파라미터 + - **Path Variable** + - `courseId` (UUID): 좋아요를 누를 코스 ID + + ## 반환값 + - **HTTP Status 200 OK** (혹은 204 No Content) + + ## 에러코드 + - `UNAUTHORIZED (401)`: 인증되지 않은 사용자입니다. + - `COURSE_NOT_FOUND (404)`: 존재하지 않는 코스입니다. + """ + ) + @PostMapping("/like/{courseId}") + public ResponseEntity likeCourse( + @AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable("courseId") UUID courseId + ) { + courseService.likeCourse(customUserDetails, courseId); + return ResponseEntity.ok().build(); + } + + diff --git a/src/main/java/jombi/freemates/controller/PlaceController.java b/src/main/java/jombi/freemates/controller/PlaceController.java index fe84beb..b2ee1ab 100644 --- a/src/main/java/jombi/freemates/controller/PlaceController.java +++ b/src/main/java/jombi/freemates/controller/PlaceController.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jombi.freemates.model.constant.Author; import jombi.freemates.model.constant.CategoryType; -import jombi.freemates.model.dto.GeoCodePlaceDto; import jombi.freemates.model.dto.PlaceDto; import jombi.freemates.model.postgres.Place; import jombi.freemates.service.PlaceService; @@ -113,7 +112,7 @@ public ResponseEntity> getPlacesByCategory( """ ) @GetMapping("/geocode") - public ResponseEntity getPlaceByGeocode( + public ResponseEntity getPlaceByGeocode( @RequestParam String x, @RequestParam String y ) { diff --git a/src/main/java/jombi/freemates/model/dto/BookmarkResponse.java b/src/main/java/jombi/freemates/model/dto/BookmarkDto.java similarity index 82% rename from src/main/java/jombi/freemates/model/dto/BookmarkResponse.java rename to src/main/java/jombi/freemates/model/dto/BookmarkDto.java index c2e3adb..f3e4719 100644 --- a/src/main/java/jombi/freemates/model/dto/BookmarkResponse.java +++ b/src/main/java/jombi/freemates/model/dto/BookmarkDto.java @@ -1,5 +1,6 @@ package jombi.freemates.model.dto; +import java.util.List; import java.util.UUID; import jombi.freemates.model.constant.PinColor; import jombi.freemates.model.constant.Visibility; @@ -16,7 +17,7 @@ @Setter @AllArgsConstructor @NoArgsConstructor -public class BookmarkResponse { +public class BookmarkDto { private UUID bookmarkId; private String imageUrl; @@ -26,5 +27,9 @@ public class BookmarkResponse { private Visibility visibility; private UUID memberId; private String nickname; + private Long likeCount; // 좋아요 수 + + private List placeDtos; + } diff --git a/src/main/java/jombi/freemates/model/dto/CourseDto.java b/src/main/java/jombi/freemates/model/dto/CourseDto.java index c1c88b6..b2efdb1 100644 --- a/src/main/java/jombi/freemates/model/dto/CourseDto.java +++ b/src/main/java/jombi/freemates/model/dto/CourseDto.java @@ -39,11 +39,10 @@ public class CourseDto { @Schema(description = "이미지 URL (업로드한 경우)") private String imageUrl; - - @Schema(description = "해당 코스에 포함된 장소들의 ID 리스트") - private List placeIds; - @Schema(description = "해당 코스에 포함된 장소들의 상세 정보 리스트") - private List coursePlaceDtos; + private List placeDtos; + + @Schema(description = "코스 좋아요 수") + private Long likeCount; } diff --git a/src/main/java/jombi/freemates/model/dto/CoursePlaceDto.java b/src/main/java/jombi/freemates/model/dto/CoursePlaceDto.java deleted file mode 100644 index 0fab5cf..0000000 --- a/src/main/java/jombi/freemates/model/dto/CoursePlaceDto.java +++ /dev/null @@ -1,25 +0,0 @@ -package jombi.freemates.model.dto; - -import java.util.List; -import jombi.freemates.model.constant.CategoryType; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@ToString -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class CoursePlaceDto { - private String placeName; - private String distance; - private CategoryType categoryType; - private String imageUrl; - private List tags; - -} diff --git a/src/main/java/jombi/freemates/model/dto/GeoCodePlaceDto.java b/src/main/java/jombi/freemates/model/dto/GeoCodePlaceDto.java deleted file mode 100644 index a91b8d4..0000000 --- a/src/main/java/jombi/freemates/model/dto/GeoCodePlaceDto.java +++ /dev/null @@ -1,27 +0,0 @@ -package jombi.freemates.model.dto; - -import java.util.ArrayList; -import java.util.List; -import jombi.freemates.model.constant.CategoryType; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; - -@Builder -@ToString -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class GeoCodePlaceDto { - private String placeName; - private String roadAddressName; - private String imageUrl; - private String introText; - private List tags = new ArrayList<>(); - private CategoryType categoryType; - -} diff --git a/src/main/java/jombi/freemates/model/dto/PlaceDto.java b/src/main/java/jombi/freemates/model/dto/PlaceDto.java index 25ae116..2dca75c 100644 --- a/src/main/java/jombi/freemates/model/dto/PlaceDto.java +++ b/src/main/java/jombi/freemates/model/dto/PlaceDto.java @@ -27,6 +27,8 @@ public class PlaceDto { private Long likeCount; private Long viewCount; private String distance; + private String x; + private String y; diff --git a/src/main/java/jombi/freemates/model/postgres/Bookmark.java b/src/main/java/jombi/freemates/model/postgres/Bookmark.java index 48e5703..9300264 100644 --- a/src/main/java/jombi/freemates/model/postgres/Bookmark.java +++ b/src/main/java/jombi/freemates/model/postgres/Bookmark.java @@ -20,8 +20,10 @@ import jombi.freemates.model.constant.PinColor; import jombi.freemates.model.constant.Visibility; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.experimental.SuperBuilder; @Entity @@ -29,6 +31,7 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor +@Setter public class Bookmark extends BasePostgresEntity{ @Id @@ -45,6 +48,9 @@ public class Bookmark extends BasePostgresEntity{ private String description; + @Builder.Default + private Long likeCount = 0L; // 좋아요 수 + @Enumerated(EnumType.STRING) private PinColor pinColor; @@ -53,6 +59,7 @@ public class Bookmark extends BasePostgresEntity{ @OneToMany(mappedBy = "bookmark", fetch = FetchType.LAZY) + @Builder.Default private List bookmarkPlaces = new ArrayList<>(); diff --git a/src/main/java/jombi/freemates/model/postgres/BookmarkLike.java b/src/main/java/jombi/freemates/model/postgres/BookmarkLike.java new file mode 100644 index 0000000..9bdbcb9 --- /dev/null +++ b/src/main/java/jombi/freemates/model/postgres/BookmarkLike.java @@ -0,0 +1,32 @@ +package jombi.freemates.model.postgres; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jombi.freemates.model.postgres.id.BookmarkLikeId; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class BookmarkLike { + @EmbeddedId + private BookmarkLikeId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("memberId") + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("bookmarkId") + @JoinColumn(name = "bookmark_id", nullable = false) + private Bookmark bookmark;} diff --git a/src/main/java/jombi/freemates/model/postgres/Course.java b/src/main/java/jombi/freemates/model/postgres/Course.java index e858792..7beab77 100644 --- a/src/main/java/jombi/freemates/model/postgres/Course.java +++ b/src/main/java/jombi/freemates/model/postgres/Course.java @@ -19,8 +19,10 @@ import java.util.UUID; import jombi.freemates.model.constant.Visibility; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.experimental.SuperBuilder; @Entity @@ -28,6 +30,7 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor +@Setter public class Course extends BasePostgresEntity { @Id @@ -46,11 +49,15 @@ public class Course extends BasePostgresEntity { private Integer freeTime; // 코스에 걸리는 시간 (분 단위) + @Builder.Default + private Long likeCount = 0L; // 좋아요 수 + @Enumerated(EnumType.STRING) private Visibility visibility; @OneToMany(mappedBy = "course", fetch = FetchType.LAZY) + @Builder.Default private List coursePlaces = new ArrayList<>(); } diff --git a/src/main/java/jombi/freemates/model/postgres/CourseLike.java b/src/main/java/jombi/freemates/model/postgres/CourseLike.java new file mode 100644 index 0000000..6069679 --- /dev/null +++ b/src/main/java/jombi/freemates/model/postgres/CourseLike.java @@ -0,0 +1,36 @@ +package jombi.freemates.model.postgres; + +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jombi.freemates.model.postgres.id.BookmarkLikeId; +import jombi.freemates.model.postgres.id.CourseLikeId; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class CourseLike { + @EmbeddedId + private CourseLikeId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("memberId") + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("courseId") + @JoinColumn(name = "course_id", nullable = false) + private Course course; +} + + diff --git a/src/main/java/jombi/freemates/model/postgres/id/BookmarkLikeId.java b/src/main/java/jombi/freemates/model/postgres/id/BookmarkLikeId.java new file mode 100644 index 0000000..3aa52fc --- /dev/null +++ b/src/main/java/jombi/freemates/model/postgres/id/BookmarkLikeId.java @@ -0,0 +1,21 @@ +package jombi.freemates.model.postgres.id; + +import jakarta.persistence.Embeddable; +import java.io.Serializable; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class BookmarkLikeId implements Serializable { + private UUID memberId; + private UUID bookmarkId; + + +} diff --git a/src/main/java/jombi/freemates/model/postgres/id/CourseLikeId.java b/src/main/java/jombi/freemates/model/postgres/id/CourseLikeId.java new file mode 100644 index 0000000..c0e6cfb --- /dev/null +++ b/src/main/java/jombi/freemates/model/postgres/id/CourseLikeId.java @@ -0,0 +1,20 @@ +package jombi.freemates.model.postgres.id; + +import jakarta.persistence.Embeddable; +import java.io.Serializable; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class CourseLikeId implements Serializable { + private UUID memberId; + private UUID courseId; + +} diff --git a/src/main/java/jombi/freemates/repository/BookmarkLikeRepository.java b/src/main/java/jombi/freemates/repository/BookmarkLikeRepository.java new file mode 100644 index 0000000..c3d048e --- /dev/null +++ b/src/main/java/jombi/freemates/repository/BookmarkLikeRepository.java @@ -0,0 +1,11 @@ +package jombi.freemates.repository; + +import jombi.freemates.model.postgres.BookmarkLike; +import jombi.freemates.model.postgres.id.BookmarkLikeId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BookmarkLikeRepository extends JpaRepository { + boolean existsById(BookmarkLikeId id); +} diff --git a/src/main/java/jombi/freemates/repository/CourseLikeRepository.java b/src/main/java/jombi/freemates/repository/CourseLikeRepository.java new file mode 100644 index 0000000..a10a454 --- /dev/null +++ b/src/main/java/jombi/freemates/repository/CourseLikeRepository.java @@ -0,0 +1,12 @@ +package jombi.freemates.repository; + +import jombi.freemates.model.postgres.CourseLike; +import jombi.freemates.model.postgres.id.CourseLikeId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CourseLikeRepository extends JpaRepository { + + boolean existsById(CourseLikeId id); +} diff --git a/src/main/java/jombi/freemates/service/BookmarkService.java b/src/main/java/jombi/freemates/service/BookmarkService.java index fa57b10..9a18a29 100644 --- a/src/main/java/jombi/freemates/service/BookmarkService.java +++ b/src/main/java/jombi/freemates/service/BookmarkService.java @@ -5,14 +5,17 @@ import java.util.stream.Collectors; import jombi.freemates.model.constant.Visibility; import jombi.freemates.model.dto.BookmarkRequest; -import jombi.freemates.model.dto.BookmarkResponse; +import jombi.freemates.model.dto.BookmarkDto; import jombi.freemates.model.dto.CustomUserDetails; import jombi.freemates.model.dto.PlaceDto; import jombi.freemates.model.postgres.Bookmark; +import jombi.freemates.model.postgres.BookmarkLike; import jombi.freemates.model.postgres.BookmarkPlace; import jombi.freemates.model.postgres.Member; import jombi.freemates.model.postgres.Place; +import jombi.freemates.model.postgres.id.BookmarkLikeId; import jombi.freemates.model.postgres.id.BookmarkPlaceId; +import jombi.freemates.repository.BookmarkLikeRepository; import jombi.freemates.repository.BookmarkPlaceRepository; import jombi.freemates.repository.BookmarkRepository; import jombi.freemates.repository.PlaceRepository; @@ -35,13 +38,14 @@ public class BookmarkService { private final PlaceRepository placeRepository; private final BookmarkPlaceRepository bookmarkPlaceRepository; private final PlaceService placeService; + private final BookmarkLikeRepository bookmarkLikeRepository; /** * 즐겨찾기 생성 * */ @Transactional - public BookmarkResponse create( + public BookmarkDto create( CustomUserDetails customUserDetails, BookmarkRequest req, MultipartFile image @@ -71,17 +75,17 @@ public BookmarkResponse create( log.info("북마크 생성 완료 - ID: {}, 사용자: {}", b.getBookmarkId(), member.getNickname()); // 응답 DTO 반환 - return convertToBookmarkResponse(b); + return convertToBookmarkDto(b); } /** * 멤버 별 즐겨찾기 목록 조회 */ @Transactional(readOnly = true) - public List getMyBookmarks(CustomUserDetails customUserDetails) { + public List getMyBookmarks(CustomUserDetails customUserDetails) { Member member = customUserDetails.getMember(); return bookmarkRepository.findAllByMember(member).stream() - .map(this::convertToBookmarkResponse) + .map(this::convertToBookmarkDto) .collect(Collectors.toList()); } @@ -89,11 +93,47 @@ public List getMyBookmarks(CustomUserDetails customUserDetails * 즐겨찾기 목록 조회 (페이징) */ @Transactional(readOnly = true) - public Page getBookmarks(int page, int size, Visibility visibility) { + public Page getBookmarks(int page, int size, Visibility visibility) { return bookmarkRepository .findByVisibility(visibility, PageRequest.of(page, size)) - .map(this::convertToBookmarkResponse); + .map(this::convertToBookmarkDto); } + /** + * 즐겨찾기 좋아요 + */ + @Transactional + public void likeBookmark(CustomUserDetails customUser, UUID bookmarkId) { + Member member = customUser.getMember(); + if (member == null) { + throw new CustomException(ErrorCode.MEMBER_NOT_FOUND); + } + + Bookmark bookmark = bookmarkRepository.findById(bookmarkId) + .orElseThrow(() -> new CustomException(ErrorCode.BOOKMARK_NOT_FOUND)); + + BookmarkLikeId likeId = new BookmarkLikeId(member.getMemberId(), bookmark.getBookmarkId()); + boolean exists = bookmarkLikeRepository.existsById(likeId); + + if (exists) { + // 좋아요 이미 눌린 상태 → 취소 + bookmarkLikeRepository.deleteById(likeId); + long current = bookmark.getLikeCount() == null ? 0L : bookmark.getLikeCount(); + bookmark.setLikeCount(Math.max(0, current - 1)); + bookmarkRepository.save(bookmark); + } else { + // 좋아요가 아직 없는 상태 → 추가 + BookmarkLike like = BookmarkLike.builder() + .id(likeId) + .member(member) + .bookmark(bookmark) + .build(); + bookmarkLikeRepository.save(like); + long current = bookmark.getLikeCount() == null ? 0L : bookmark.getLikeCount(); + bookmark.setLikeCount(current + 1); + bookmarkRepository.save(bookmark); + } + } + @Transactional public void addPlaceToBookmark( @@ -149,20 +189,9 @@ public void addPlaceToBookmark( } - @Transactional(readOnly = true) - public List getPlacesByBookmarkId(UUID bookmarkId) { - // bookmarkId로 연결된 BookmarkPlace 목록 조회 - List bookmarkPlaces = - bookmarkPlaceRepository.findByBookmarkBookmarkId(bookmarkId); - - // 각 BookmarkPlace에서 Place를 꺼내어 PlaceDto로 변환 - return bookmarkPlaces.stream() - .map(bp -> placeService.convertToPlaceDto(bp.getPlace())) - .collect(Collectors.toList()); - } - public BookmarkResponse convertToBookmarkResponse(Bookmark bookmark) { - return BookmarkResponse.builder() + public BookmarkDto convertToBookmarkDto(Bookmark bookmark) { + return BookmarkDto.builder() .bookmarkId(bookmark.getBookmarkId()) .memberId(bookmark.getMember().getMemberId()) .nickname(bookmark.getMember().getNickname()) @@ -171,6 +200,10 @@ public BookmarkResponse convertToBookmarkResponse(Bookmark bookmark) { .description(bookmark.getDescription()) .pinColor(bookmark.getPinColor()) .visibility(bookmark.getVisibility()) + .likeCount(bookmark.getLikeCount()) + .placeDtos(bookmark.getBookmarkPlaces().stream() + .map(bp -> placeService.convertToPlaceDto(bp.getPlace())) + .collect(Collectors.toList())) .build(); } diff --git a/src/main/java/jombi/freemates/service/CourseService.java b/src/main/java/jombi/freemates/service/CourseService.java index 83ba4af..5c79781 100644 --- a/src/main/java/jombi/freemates/service/CourseService.java +++ b/src/main/java/jombi/freemates/service/CourseService.java @@ -1,20 +1,25 @@ package jombi.freemates.service; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import java.util.Comparator; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; import jombi.freemates.model.constant.Visibility; -import jombi.freemates.model.dto.CoursePlaceDto; import jombi.freemates.model.dto.CourseRequest; import jombi.freemates.model.dto.CourseDto; import jombi.freemates.model.dto.CustomUserDetails; +import jombi.freemates.model.dto.PlaceDto; import jombi.freemates.model.postgres.Course; +import jombi.freemates.model.postgres.CourseLike; import jombi.freemates.model.postgres.CoursePlace; import jombi.freemates.model.postgres.Member; import jombi.freemates.model.postgres.Place; +import jombi.freemates.model.postgres.id.CourseLikeId; import jombi.freemates.model.postgres.id.CoursePlaceId; +import jombi.freemates.repository.CourseLikeRepository; import jombi.freemates.repository.CoursePlaceRepository; import jombi.freemates.repository.CourseRepository; import jombi.freemates.repository.PlaceRepository; @@ -36,6 +41,11 @@ public class CourseService { private final FileStorageService storage; private final PlaceRepository placeRepository; private final CoursePlaceRepository coursePlaceRepository; + private final PlaceService placeService; + private final CourseLikeRepository courseLikeRepository; + + @PersistenceContext + private EntityManager entityManager; /** * 코스 생성 @@ -56,7 +66,8 @@ public CourseDto createCourse( String imageUrl = null; if (image != null && !image.isEmpty()) { imageUrl = storage.storeImage(image); - } + }// placeIds 각각으로 Place 조회 → CoursePlace 생성 + // Course 엔티티 생성·저장 Course course = courseRepository.save( @@ -67,10 +78,10 @@ public CourseDto createCourse( .freeTime(req.getFreeTime()) .visibility(req.getVisibility()) .imageUrl(imageUrl) + .likeCount(0L) // 초기 좋아요 수는 0 .build() ); - // placeIds 각각으로 Place 조회 → CoursePlace 생성 List placeIds = req.getPlaceIds(); List coursePlaceList = IntStream.range(0, placeIds.size()) .mapToObj(idx -> { @@ -90,8 +101,13 @@ public CourseDto createCourse( // CoursePlace 한꺼번에 저장 coursePlaceRepository.saveAll(coursePlaceList); + entityManager.flush(); + entityManager.clear(); + // 바로 DTO 변환 - return buildCourseDto(course, req.getPlaceIds(), coursePlaceList, member.getNickname()); + return courseRepository.findById(course.getCourseId()) + .map(this::converToCourseDto) + .orElseThrow(() -> new CustomException(ErrorCode.COURSE_NOT_FOUND));// 단일 코스 생성이므로 첫 번째 요소만 반환 } /** @@ -106,7 +122,7 @@ public List getMyCourses(CustomUserDetails customUser) { List courses = courseRepository.findAllByMember(member); return courses.stream() - .map(course -> buildCourseDto(course, extractPlaceIds(course.getCoursePlaces()), course.getCoursePlaces(), member.getNickname())) + .map(this::converToCourseDto) .collect(Collectors.toList()); } @@ -118,60 +134,66 @@ public Page getCourses(Visibility visibility, Pageable pageable) { Page coursePage = courseRepository.findAllByVisibility(visibility, pageable); // Page → Page로 변환 - return coursePage.map(course -> { - // buildCourseDto(...)는 앞서 리팩토링한 공통 메서드 - List placeIds = extractPlaceIds(course.getCoursePlaces()); - List coursePlaces = course.getCoursePlaces(); - String nickName = course.getMember().getNickname(); - return buildCourseDto(course, placeIds, coursePlaces, nickName); - }); + return coursePage.map(this::converToCourseDto); } + /** - * Course → CourseDto 변환 공통 로직 - * + * 코스 좋아요 */ - private CourseDto buildCourseDto( - Course course, - List placeIds, - List coursePlaces, - String nickName - ) { - // CoursePlaceDto 목록 생성 - List placeDtoList = coursePlaces.stream() - .sorted(Comparator.comparing(CoursePlace::getSequence)) - .map(cp -> { - Place p = cp.getPlace(); - return CoursePlaceDto.builder() - .placeName(p.getPlaceName()) - .distance(p.getDistance()) - .categoryType(p.getCategoryType()) - .imageUrl(p.getImageUrl()) - .tags(p.getTags()) - .build(); - }) - .collect(Collectors.toList()); + @Transactional + public void likeCourse(CustomUserDetails customUser, UUID courseId) { + Member member = customUser.getMember(); + if (member == null) { + throw new CustomException(ErrorCode.MEMBER_NOT_FOUND); + } + + Course course = courseRepository.findById(courseId) + .orElseThrow(() -> new CustomException(ErrorCode.COURSE_NOT_FOUND)); + CourseLikeId likeId = new CourseLikeId(member.getMemberId(), course.getCourseId()); + boolean exists = courseLikeRepository.existsById(likeId); + + if (exists) { + // 이미 좋아요된 상태 → 취소 + courseLikeRepository.deleteById(likeId); + long current = course.getLikeCount() == null ? 0L : course.getLikeCount(); + course.setLikeCount(Math.max(0, current - 1)); + courseRepository.save(course); + } else { + // 좋아요가 없는 상태 → 추가 + CourseLike like = CourseLike.builder() + .id(likeId) + .member(member) + .course(course) + .build(); + courseLikeRepository.save(like); + long current = course.getLikeCount() == null ? 0L : course.getLikeCount(); + course.setLikeCount(current + 1); + courseRepository.save(course); + } + } + + + + + /** + * 코스 dto를 빌드하는 공통 메서드 + */ + public CourseDto converToCourseDto(Course course) { return CourseDto.builder() .courseId(course.getCourseId()) - .nickName(nickName) + .nickName(course.getMember().getNickname()) .title(course.getTitle()) .description(course.getDescription()) .freeTime(course.getFreeTime()) .visibility(course.getVisibility()) .imageUrl(course.getImageUrl()) - .placeIds(placeIds) - .coursePlaceDtos(placeDtoList) + .placeDtos(course.getCoursePlaces().stream() + .sorted(Comparator.comparing(CoursePlace::getSequence)) + .map(cp -> placeService.convertToPlaceDto(cp.getPlace())) + .collect(Collectors.toList())) + .likeCount(course.getLikeCount()) .build(); } - - /** - * 주어진 CoursePlace 목록에서 Place ID를 순서에 맞춰 추출하여 반환 - */ - private List extractPlaceIds(List coursePlaces) { - return coursePlaces.stream() - .sorted(Comparator.comparing(CoursePlace::getSequence)) - .map(cp -> cp.getPlace().getPlaceId()) - .collect(Collectors.toList()); - } } diff --git a/src/main/java/jombi/freemates/service/PlaceService.java b/src/main/java/jombi/freemates/service/PlaceService.java index e8f2e4d..8b753d9 100644 --- a/src/main/java/jombi/freemates/service/PlaceService.java +++ b/src/main/java/jombi/freemates/service/PlaceService.java @@ -7,7 +7,6 @@ import java.util.UUID; import jombi.freemates.model.constant.CategoryType; import jombi.freemates.model.dto.KakaoPlaceCrawlDetail; -import jombi.freemates.model.dto.GeoCodePlaceDto; import jombi.freemates.model.dto.PlaceDto; import jombi.freemates.model.postgres.Place; import jombi.freemates.repository.PlaceRepository; @@ -119,7 +118,7 @@ public Page getPlacesByCategory(CategoryType category, Pageable pageab * 좌표에 따른 장소 조회 */ @Transactional(readOnly = true) - public GeoCodePlaceDto getPlacesByGeocode( + public PlaceDto getPlacesByGeocode( String x, String y ) { @@ -131,16 +130,7 @@ public GeoCodePlaceDto getPlacesByGeocode( log.warn("좌표 ({}, {})에 해당하는 장소가 없습니다.", x, y); throw new CustomException(ErrorCode.PLACE_NOT_FOUND); // 또는 예외 처리 } - Place place = placeOpt.get(); - GeoCodePlaceDto geoCodePlaceDto = new GeoCodePlaceDto( - place.getPlaceName(), - place.getRoadAddressName(), - place.getImageUrl(), - place.getIntroText(), - place.getTags(), - place.getCategoryType() - ); - return geoCodePlaceDto; + return convertToPlaceDto(placeOpt.get()); } /** @@ -158,6 +148,8 @@ public PlaceDto convertToPlaceDto(Place p) { .likeCount(p.getLikeCount()) .viewCount(p.getViewCount()) .distance(p.getDistance()) + .x(p.getX()) + .y(p.getY()) .build(); } } diff --git a/src/main/java/jombi/freemates/util/exception/ErrorCode.java b/src/main/java/jombi/freemates/util/exception/ErrorCode.java index b840c19..9486384 100644 --- a/src/main/java/jombi/freemates/util/exception/ErrorCode.java +++ b/src/main/java/jombi/freemates/util/exception/ErrorCode.java @@ -52,8 +52,12 @@ public enum ErrorCode { // Place PLACE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 장소입니다."), + COURSE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 코스입니다."), + // File FILE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 파일입니다."), + + ; private final HttpStatus status; From ef9d241dfb18449d81ee31ecb4382cc6387075cd Mon Sep 17 00:00:00 2001 From: lulyulalla Date: Sat, 7 Jun 2025 01:40:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20:=20refactor=20:=20docs=EC=88=98=EC=A0=95=20https:/?= =?UTF-8?q?/github.com/freeMates/FreeMates=5FBackend/issues/122?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/jombi/freemates/controller/BookmarkController.java | 2 +- src/main/java/jombi/freemates/model/postgres/CourseLike.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/jombi/freemates/controller/BookmarkController.java b/src/main/java/jombi/freemates/controller/BookmarkController.java index ce8ea39..0f983ee 100644 --- a/src/main/java/jombi/freemates/controller/BookmarkController.java +++ b/src/main/java/jombi/freemates/controller/BookmarkController.java @@ -238,7 +238,7 @@ public ResponseEntity addPlaceToBookmark( ## 에러코드 - `UNAUTHORIZED (401)`: 인증되지 않은 사용자입니다. - - `PLACE_NOT_FOUND (404)`: 존재하지 않는 장소입니다. + - 'BOOKMARK_NOT_FOUND (404)': 존재하지 않는 북마크입니다. """ ) @PostMapping("/like/{bookmarkId}") diff --git a/src/main/java/jombi/freemates/model/postgres/CourseLike.java b/src/main/java/jombi/freemates/model/postgres/CourseLike.java index 6069679..fd13ae9 100644 --- a/src/main/java/jombi/freemates/model/postgres/CourseLike.java +++ b/src/main/java/jombi/freemates/model/postgres/CourseLike.java @@ -6,7 +6,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.MapsId; -import jombi.freemates.model.postgres.id.BookmarkLikeId; import jombi.freemates.model.postgres.id.CourseLikeId; import lombok.AllArgsConstructor; import lombok.Getter;