diff --git a/src/main/java/com/projectX/projectX/domain/tour/entity/Tour.java b/src/main/java/com/projectX/projectX/domain/tour/entity/Tour.java index 9c2c20d..940e681 100644 --- a/src/main/java/com/projectX/projectX/domain/tour/entity/Tour.java +++ b/src/main/java/com/projectX/projectX/domain/tour/entity/Tour.java @@ -1,8 +1,10 @@ package com.projectX.projectX.domain.tour.entity; +import com.projectX.projectX.domain.member.entity.Member; import com.projectX.projectX.global.common.BaseEntity; import com.projectX.projectX.global.common.ContentType; import com.projectX.projectX.global.common.JejuRegion; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -13,6 +15,7 @@ import jakarta.persistence.OneToMany; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -86,6 +89,10 @@ public class Tour extends BaseEntity { @Comment("카카오맵 URL") private String kakaoMapUrl; + @Comment("user 스크랩 정보") + @OneToMany(mappedBy = "tour", cascade = CascadeType.ALL, orphanRemoval = true) + private List scraps; + @Builder public Tour(Long id, String address, String spec_address, Long zipCode, Long contentId, ContentType contentType, JejuRegion jejuRegion, String imageUrl, float mapX, float mapY, @@ -119,4 +126,23 @@ public void updatePhone(String phone) { public void updateKakaoMapUrl(String kakaoMapUrl){ this.kakaoMapUrl = kakaoMapUrl; } + + public Boolean updateScrap(Tour tour, Member member) { + UserScrapTour forRemove = null; + for (UserScrapTour scrap : this.scraps) { + if (Objects.equals(scrap.getTour(), tour) && Objects.equals(scrap.getMember(), + member)) { + forRemove = scrap; + break; + } + } + + if (Objects.nonNull(forRemove)) { + this.scraps.remove(forRemove); + return false; + } + + this.scraps.add(new UserScrapTour(tour, member)); + return true; + } } \ No newline at end of file diff --git a/src/main/java/com/projectX/projectX/domain/tour/entity/UserScrapTour.java b/src/main/java/com/projectX/projectX/domain/tour/entity/UserScrapTour.java new file mode 100644 index 0000000..e2c00a2 --- /dev/null +++ b/src/main/java/com/projectX/projectX/domain/tour/entity/UserScrapTour.java @@ -0,0 +1,38 @@ +package com.projectX.projectX.domain.tour.entity; + +import com.projectX.projectX.domain.member.entity.Member; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class UserScrapTour { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "tour_id") + private Tour tour; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "member_id") + private Member member; + + @Builder + public UserScrapTour(Tour tour, Member member) { + this.tour = tour; + this.member = member; + } +} diff --git a/src/main/java/com/projectX/projectX/domain/travel/controller/TravelRestController.java b/src/main/java/com/projectX/projectX/domain/travel/controller/TravelRestController.java index fa03dfb..c5ed56e 100644 --- a/src/main/java/com/projectX/projectX/domain/travel/controller/TravelRestController.java +++ b/src/main/java/com/projectX/projectX/domain/travel/controller/TravelRestController.java @@ -5,6 +5,7 @@ import com.projectX.projectX.domain.travel.service.TravelService; import com.projectX.projectX.global.common.JejuRegion; import com.projectX.projectX.global.common.ResponseDTO; +import com.projectX.projectX.global.security.dto.CustomOAuth2User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotBlank; @@ -12,8 +13,10 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +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; import org.springframework.web.bind.annotation.ResponseStatus; @@ -59,4 +62,15 @@ public ResponseDTO getRecommdTravelInfo( return ResponseDTO.res(travelService.getTravelRecommd(jejuRegion), "travel 추천 조회에 성공했습니다."); } + @PostMapping("/{travel_id}/scrap") + @ResponseStatus(HttpStatus.CREATED) + @Operation(summary = "travel 스크랩 API", description = "travel 게시물을 스크랩하는 API입니다.") + public ResponseDTO postTravelScrapInfo( + @PathVariable("travel_id") @NotNull Long travelId, + @AuthenticationPrincipal CustomOAuth2User user + ) { + String result = travelService.postTravelScrapInfo(travelId, user.getEmail()); + return ResponseDTO.res(result); + } + } diff --git a/src/main/java/com/projectX/projectX/domain/travel/service/TravelService.java b/src/main/java/com/projectX/projectX/domain/travel/service/TravelService.java index a5ee304..d333ab5 100644 --- a/src/main/java/com/projectX/projectX/domain/travel/service/TravelService.java +++ b/src/main/java/com/projectX/projectX/domain/travel/service/TravelService.java @@ -1,5 +1,8 @@ package com.projectX.projectX.domain.travel.service; +import com.projectX.projectX.domain.member.entity.Member; +import com.projectX.projectX.domain.member.exception.InvalidMemberException; +import com.projectX.projectX.domain.member.repository.MemberRepository; import com.projectX.projectX.domain.tour.entity.Tour; import com.projectX.projectX.domain.tour.entity.TourImage; import com.projectX.projectX.domain.tour.repository.TourRepository; @@ -33,6 +36,7 @@ public class TravelService { private final TourRepository tourRepository; + private final MemberRepository memberRepository; private static final int RECOMMEND_WORK_SIZE = 3; @Transactional(readOnly = true) @@ -82,6 +86,14 @@ private Tour getTour(long tourId) { .orElseThrow(() -> new TravelNotFoundException(ErrorCode.TRAVEL_NOT_FOUND)); } + private Member getMember(String userEmail) { + Member member = memberRepository.findByUserEmail(userEmail).orElseThrow( + () -> new InvalidMemberException(ErrorCode.INVALID_MEMBER_EXCEPTION) + ); + + return member; + } + private List getTravelImageUrlList(List tourImageList) { if (tourImageList.isEmpty()) { return null; @@ -113,5 +125,13 @@ public List getTravelRecommd(JejuRegion jejuRegion) { return TravelMapper.toTravelGetRecommendResponse(tours); } + @Transactional + public String postTravelScrapInfo(Long travelId, String userEmail) { + Tour tour = getTour(travelId); + Member member = getMember(userEmail); + + Boolean result = tour.updateScrap(tour, member); + return result ? "travel 정보를 스크랩했습니다." : "travel 스크랩을 취소했습니다."; + } }