diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/controller/TravelogueController.java b/backend/src/main/java/woowacourse/touroot/travelogue/controller/TravelogueController.java index 41792d9d..57fd67c8 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/controller/TravelogueController.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/controller/TravelogueController.java @@ -1,15 +1,25 @@ package woowacourse.touroot.travelogue.controller; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; +import org.springdoc.core.converters.models.PageableAsQueryParam; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; 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.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import woowacourse.touroot.travelogue.dto.TravelogueResponse; +import woowacourse.touroot.travelogue.dto.request.TravelogueRequest; +import woowacourse.touroot.travelogue.dto.response.TravelogueResponse; import woowacourse.touroot.travelogue.service.TravelogueFacadeService; @Tag(name = "여행기") @@ -20,9 +30,29 @@ public class TravelogueController { private final TravelogueFacadeService travelogueFacadeService; + @Operation(description = "여행기 작성") + @PostMapping + public ResponseEntity createTravelogue(@Valid @RequestBody TravelogueRequest request) { + TravelogueResponse response = travelogueFacadeService.createTravelogue(request); + + return ResponseEntity.created(URI.create("/api/v1/travelogues/" + response.id())) + .body(response); + } + @Operation(description = "여행기 상세 조회") @GetMapping("/{id}") public ResponseEntity findTravelogue(@Valid @PathVariable Long id) { return ResponseEntity.ok(travelogueFacadeService.findTravelogueById(id)); } + + @Operation(description = "여행기 메인 페이지 조회") + @PageableAsQueryParam + @GetMapping + public ResponseEntity> findMainPageTravelogues( + @Parameter(hidden = true) + @PageableDefault(size = 5, sort = "id", direction = Direction.DESC) + Pageable pageable + ) { + return ResponseEntity.ok(travelogueFacadeService.findTravelogues(pageable)); + } } diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/domain/TravelogueDay.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/TravelogueDay.java similarity index 88% rename from backend/src/main/java/woowacourse/touroot/travelogue/domain/day/domain/TravelogueDay.java rename to backend/src/main/java/woowacourse/touroot/travelogue/domain/TravelogueDay.java index e3d306e9..9d15e83a 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/domain/TravelogueDay.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/domain/TravelogueDay.java @@ -1,4 +1,4 @@ -package woowacourse.touroot.travelogue.domain.day.domain; +package woowacourse.touroot.travelogue.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -10,12 +10,13 @@ import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import woowacourse.touroot.entity.BaseEntity; -import woowacourse.touroot.travelogue.domain.Travelogue; @Getter +@EqualsAndHashCode(of = "id", callSuper = false) @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @Entity diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/domain/TraveloguePhoto.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/TraveloguePhoto.java similarity index 84% rename from backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/domain/TraveloguePhoto.java rename to backend/src/main/java/woowacourse/touroot/travelogue/domain/TraveloguePhoto.java index bdc59bd6..38d64560 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/domain/TraveloguePhoto.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/domain/TraveloguePhoto.java @@ -1,4 +1,4 @@ -package woowacourse.touroot.travelogue.domain.photo.domain; +package woowacourse.touroot.travelogue.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -13,7 +13,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import woowacourse.touroot.entity.BaseEntity; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -35,7 +34,7 @@ public class TraveloguePhoto extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private TraveloguePlace traveloguePlace; - public TraveloguePhoto(String key, Integer order, TraveloguePlace traveloguePlace) { + public TraveloguePhoto(Integer order, String key, TraveloguePlace traveloguePlace) { this(null, key, order, traveloguePlace); } } diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/domain/TraveloguePlace.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/TraveloguePlace.java similarity index 91% rename from backend/src/main/java/woowacourse/touroot/travelogue/domain/place/domain/TraveloguePlace.java rename to backend/src/main/java/woowacourse/touroot/travelogue/domain/TraveloguePlace.java index 21942047..b09d2ac5 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/domain/TraveloguePlace.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/domain/TraveloguePlace.java @@ -1,4 +1,4 @@ -package woowacourse.touroot.travelogue.domain.place.domain; +package woowacourse.touroot.travelogue.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -14,7 +14,6 @@ import lombok.NoArgsConstructor; import woowacourse.touroot.entity.BaseEntity; import woowacourse.touroot.place.domain.Place; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/dto/TravelogueDayResponse.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/dto/TravelogueDayResponse.java deleted file mode 100644 index 15fbd212..00000000 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/dto/TravelogueDayResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package woowacourse.touroot.travelogue.domain.day.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import java.util.List; -import lombok.Builder; -import woowacourse.touroot.travelogue.domain.place.dto.TraveloguePlaceResponse; - -@Builder -public record TravelogueDayResponse( - @Schema(description = "여행기 일자 ID", example = "1") - @NotNull(message = "ID는 비어있을 수 없습니다.") - Long id, - @Schema(description = "여행기 장소 목록") - @NotNull(message = "여행기 장소 정보는 비어있을 수 없습니다.") - List places -) { -} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/service/TravelogueDayService.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/service/TravelogueDayService.java deleted file mode 100644 index 93a99bbd..00000000 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/service/TravelogueDayService.java +++ /dev/null @@ -1,28 +0,0 @@ -package woowacourse.touroot.travelogue.domain.day.service; - -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import woowacourse.touroot.global.exception.BadRequestException; -import woowacourse.touroot.travelogue.domain.Travelogue; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; -import woowacourse.touroot.travelogue.domain.day.repository.TravelogueDayRepository; - -@RequiredArgsConstructor -@Service -public class TravelogueDayService { - - private final TravelogueDayRepository travelogueDayRepository; - - @Transactional(readOnly = true) - public List findDaysByTravelogue(Travelogue travelogue) { - return travelogueDayRepository.findByTravelogue(travelogue); - } - - @Transactional(readOnly = true) - public TravelogueDay findDayById(Long id) { - return travelogueDayRepository.findById(id) - .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 일자입니다.")); - } -} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/service/TraveloguePhotoService.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/service/TraveloguePhotoService.java deleted file mode 100644 index eb082eb8..00000000 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/service/TraveloguePhotoService.java +++ /dev/null @@ -1,25 +0,0 @@ -package woowacourse.touroot.travelogue.domain.photo.service; - -import java.util.Comparator; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import woowacourse.touroot.travelogue.domain.photo.domain.TraveloguePhoto; -import woowacourse.touroot.travelogue.domain.photo.repository.TraveloguePhotoRepository; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; - -@RequiredArgsConstructor -@Service -public class TraveloguePhotoService { - - private final TraveloguePhotoRepository traveloguePhotoRepository; - - public List findPhotoUrlsByPlace(TraveloguePlace traveloguePlace) { - List photos = traveloguePhotoRepository.findByTraveloguePlace(traveloguePlace); - - return photos.stream() - .sorted(Comparator.comparing(TraveloguePhoto::getOrder)) - .map(TraveloguePhoto::getKey) - .toList(); - } -} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/dto/TraveloguePlaceResponse.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/dto/TraveloguePlaceResponse.java deleted file mode 100644 index d89220af..00000000 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/dto/TraveloguePlaceResponse.java +++ /dev/null @@ -1,28 +0,0 @@ -package woowacourse.touroot.travelogue.domain.place.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.util.List; -import lombok.Builder; - -@Builder -public record TraveloguePlaceResponse( - @Schema(description = "여행기 장소 ID", example = "1") - @NotNull(message = "ID는 비어있을 수 없습니다.") - Long id, - @Schema(description = "여행기 장소 이름", example = "선릉 캠퍼스") - @NotBlank(message = "여행기 장소 이름은 비어있을 수 없습니다.") - String name, - @Schema(description = "여행기 장소 설명", example = "성담 빌딩에 위치한 선릉 캠퍼스입니다.") - @NotBlank(message = "여행기 장소 설명은 비어있을 수 없습니다.") - String description, - @Schema(description = "여행기 장소 위도", example = "37.5175896") - @NotBlank(message = "여행기 장소 위도는 비어있을 수 없습니다.") - String lat, - @Schema(description = "여행기 장소 설명", example = "127.0867236") - @NotBlank(message = "여행기 장소 경도는 비어있을 수 없습니다.") - String lng, - List photoUrls -) { -} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/service/TraveloguePlaceService.java b/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/service/TraveloguePlaceService.java deleted file mode 100644 index 9a6694de..00000000 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/service/TraveloguePlaceService.java +++ /dev/null @@ -1,28 +0,0 @@ -package woowacourse.touroot.travelogue.domain.place.service; - -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import woowacourse.touroot.global.exception.BadRequestException; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; -import woowacourse.touroot.travelogue.domain.place.repsitory.TraveloguePlaceRepository; - -@RequiredArgsConstructor -@Service -public class TraveloguePlaceService { - - private final TraveloguePlaceRepository traveloguePlaceRepository; - - @Transactional(readOnly = true) - public List findTraveloguePlaceByDay(TravelogueDay travelogueDay) { - return traveloguePlaceRepository.findByTravelogueDay(travelogueDay); - } - - @Transactional(readOnly = true) - public TraveloguePlace findTraveloguePlaceById(Long id) { - return traveloguePlaceRepository.findById(id) - .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 장소입니다.")); - } -} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueDayRequest.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueDayRequest.java new file mode 100644 index 00000000..9a84a0ed --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueDayRequest.java @@ -0,0 +1,20 @@ +package woowacourse.touroot.travelogue.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import woowacourse.touroot.travelogue.domain.Travelogue; +import woowacourse.touroot.travelogue.domain.TravelogueDay; + +public record TravelogueDayRequest( + @Schema(description = "여행기 장소 목록") + @NotNull(message = "여행기 장소 목록은 비어있을 수 없습니다.") + @Valid + List places +) { + + public TravelogueDay toTravelogueDay(int order, Travelogue travelogue) { + return new TravelogueDay(order, travelogue); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueLocationRequest.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueLocationRequest.java new file mode 100644 index 00000000..9f995d1f --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueLocationRequest.java @@ -0,0 +1,14 @@ +package woowacourse.touroot.travelogue.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +public record TravelogueLocationRequest( + @Schema(description = "여행기 장소 위도", example = "37.5175896") + @NotNull(message = "여행기 장소 위도는 비어있을 수 없습니다.") + String lat, + @Schema(description = "여행기 장소 경도", example = "127.0867236") + @NotNull(message = "여행기 장소 경도는 비어있을 수 없습니다.") + String lng +) { +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TraveloguePhotoRequest.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TraveloguePhotoRequest.java new file mode 100644 index 00000000..9e07e8a0 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TraveloguePhotoRequest.java @@ -0,0 +1,17 @@ +package woowacourse.touroot.travelogue.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import woowacourse.touroot.travelogue.domain.TraveloguePhoto; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; + +public record TraveloguePhotoRequest( + @Schema(description = "여행기 장소 사진 Key", example = "photo.png") + @NotNull(message = "여행기 장소 사진 Key 값은 비어있을 수 없습니다.") + String key +) { + + public TraveloguePhoto toTraveloguePhoto(int order, TraveloguePlace traveloguePlace) { + return new TraveloguePhoto(order, key, traveloguePlace); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TraveloguePlaceRequest.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TraveloguePlaceRequest.java new file mode 100644 index 00000000..fe6c92a1 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TraveloguePlaceRequest.java @@ -0,0 +1,34 @@ +package woowacourse.touroot.travelogue.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import woowacourse.touroot.place.domain.Place; +import woowacourse.touroot.travelogue.domain.TravelogueDay; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; + +public record TraveloguePlaceRequest( + @Schema(description = "여행기 장소 이름", example = "선릉 캠퍼스") + @NotNull(message = "여행기 장소 이름은 비어있을 수 없습니다.") + String name, + @Schema(description = "여행기 장소 위치 정보") + @NotNull(message = "여행기 장소 위치 정보는 비어있을 수 없습니다.") + @Valid + TravelogueLocationRequest location, + @Schema(description = "여행기 장소 설명", example = "성담 빌딩에 위치한 선릉 캠퍼스입니다.") + String description, + @Schema(description = "여행기 장소 사진") + @NotNull(message = "여행기 장소 사진은 비어있을 수 없습니다.") + @Valid + List photos +) { + + public TraveloguePlace toTraveloguePlace(int order, Place place, TravelogueDay travelogueDay) { + return new TraveloguePlace(order, description, place, travelogueDay); + } + + public Place toPlace() { + return new Place(name, location.lat(), location.lng()); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueRequest.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueRequest.java new file mode 100644 index 00000000..1450b8c8 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/request/TravelogueRequest.java @@ -0,0 +1,25 @@ +package woowacourse.touroot.travelogue.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import woowacourse.touroot.travelogue.domain.Travelogue; + +public record TravelogueRequest( + @Schema(description = "여행기 제목", example = "서울 강남 여행기") + @NotNull(message = "여행기 제목은 비어있을 수 없습니다.") + String title, + @Schema(description = "여행기 섬네일", example = "https://thumbnail.png") + @NotNull(message = "여행기 섬네일은 비어있을 수 없습니다.") + String thumbnail, + @Schema(description = "여행기 일자 목록") + @NotNull(message = "여행기 일자 목록은 비어있을 수 없습니다.") + @Valid + List days +) { + + public Travelogue toTravelogue() { + return new Travelogue(title, thumbnail); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueDayResponse.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueDayResponse.java new file mode 100644 index 00000000..bc81a312 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueDayResponse.java @@ -0,0 +1,22 @@ +package woowacourse.touroot.travelogue.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Builder; +import woowacourse.touroot.travelogue.domain.TravelogueDay; + +@Builder +public record TravelogueDayResponse( + @Schema(description = "여행기 일자 ID", example = "1") + Long id, + @Schema(description = "여행기 장소 목록") + List places +) { + + public static TravelogueDayResponse of(TravelogueDay day, List places) { + return TravelogueDayResponse.builder() + .id(day.getId()) + .places(places) + .build(); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueLocationResponse.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueLocationResponse.java new file mode 100644 index 00000000..e3a18728 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueLocationResponse.java @@ -0,0 +1,21 @@ +package woowacourse.touroot.travelogue.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; + +@Builder +public record TravelogueLocationResponse( + @Schema(description = "여행기 장소 위도", example = "37.5175896") + String lat, + @Schema(description = "여행기 장소 설명", example = "127.0867236") + String lng +) { + + public static TravelogueLocationResponse from(TraveloguePlace place) { + return TravelogueLocationResponse.builder() + .lat(place.getLatitude()) + .lng(place.getLongitude()) + .build(); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TraveloguePlaceResponse.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TraveloguePlaceResponse.java new file mode 100644 index 00000000..ef8f20eb --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TraveloguePlaceResponse.java @@ -0,0 +1,29 @@ +package woowacourse.touroot.travelogue.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.Builder; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; + +@Builder +public record TraveloguePlaceResponse( + @Schema(description = "여행기 장소 ID", example = "1") + Long id, + @Schema(description = "여행기 장소 이름", example = "선릉 캠퍼스") + String name, + @Schema(description = "여행기 장소 설명", example = "성담 빌딩에 위치한 선릉 캠퍼스입니다.") + String description, + TravelogueLocationResponse location, + List photoUrls +) { + + public static TraveloguePlaceResponse of(TraveloguePlace place, List photoUrls) { + return TraveloguePlaceResponse.builder() + .id(place.getId()) + .name(place.getName()) + .description(place.getDescription()) + .location(TravelogueLocationResponse.from(place)) + .photoUrls(photoUrls) + .build(); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/dto/TravelogueResponse.java b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueResponse.java similarity index 64% rename from backend/src/main/java/woowacourse/touroot/travelogue/dto/TravelogueResponse.java rename to backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueResponse.java index e9f44219..cfb554a4 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/dto/TravelogueResponse.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/dto/response/TravelogueResponse.java @@ -1,10 +1,11 @@ -package woowacourse.touroot.travelogue.dto; +package woowacourse.touroot.travelogue.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.Builder; -import woowacourse.touroot.travelogue.domain.day.dto.TravelogueDayResponse; +import woowacourse.touroot.travelogue.domain.Travelogue; @Builder public record TravelogueResponse( @@ -19,6 +20,16 @@ public record TravelogueResponse( String thumbnail, @Schema(description = "여행기 일자 목록") @NotNull(message = "여행기 일자 목록은 비어있을 수 없습니다.") + @Valid List days ) { + + public static TravelogueResponse of(Travelogue travelogue, List days) { + return TravelogueResponse.builder() + .id(travelogue.getId()) + .title(travelogue.getTitle()) + .thumbnail(travelogue.getThumbnail()) + .days(days) + .build(); + } } diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/repository/TravelogueDayRepository.java b/backend/src/main/java/woowacourse/touroot/travelogue/repository/TravelogueDayRepository.java similarity index 68% rename from backend/src/main/java/woowacourse/touroot/travelogue/domain/day/repository/TravelogueDayRepository.java rename to backend/src/main/java/woowacourse/touroot/travelogue/repository/TravelogueDayRepository.java index d480bf83..b1fe0a89 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/day/repository/TravelogueDayRepository.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/repository/TravelogueDayRepository.java @@ -1,11 +1,11 @@ -package woowacourse.touroot.travelogue.domain.day.repository; +package woowacourse.touroot.travelogue.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import woowacourse.touroot.travelogue.domain.Travelogue; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; +import woowacourse.touroot.travelogue.domain.TravelogueDay; public interface TravelogueDayRepository extends JpaRepository { - + List findByTravelogue(Travelogue travelogue); } diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/repository/TraveloguePhotoRepository.java b/backend/src/main/java/woowacourse/touroot/travelogue/repository/TraveloguePhotoRepository.java similarity index 55% rename from backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/repository/TraveloguePhotoRepository.java rename to backend/src/main/java/woowacourse/touroot/travelogue/repository/TraveloguePhotoRepository.java index a63e83a3..90f508a2 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/photo/repository/TraveloguePhotoRepository.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/repository/TraveloguePhotoRepository.java @@ -1,9 +1,9 @@ -package woowacourse.touroot.travelogue.domain.photo.repository; +package woowacourse.touroot.travelogue.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import woowacourse.touroot.travelogue.domain.photo.domain.TraveloguePhoto; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; +import woowacourse.touroot.travelogue.domain.TraveloguePhoto; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; public interface TraveloguePhotoRepository extends JpaRepository { diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/repsitory/TraveloguePlaceRepository.java b/backend/src/main/java/woowacourse/touroot/travelogue/repository/TraveloguePlaceRepository.java similarity index 54% rename from backend/src/main/java/woowacourse/touroot/travelogue/domain/place/repsitory/TraveloguePlaceRepository.java rename to backend/src/main/java/woowacourse/touroot/travelogue/repository/TraveloguePlaceRepository.java index d6e886c4..f28af54d 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/domain/place/repsitory/TraveloguePlaceRepository.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/repository/TraveloguePlaceRepository.java @@ -1,11 +1,11 @@ -package woowacourse.touroot.travelogue.domain.place.repsitory; +package woowacourse.touroot.travelogue.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; +import woowacourse.touroot.travelogue.domain.TravelogueDay; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; public interface TraveloguePlaceRepository extends JpaRepository { - + List findByTravelogueDay(TravelogueDay travelogueDay); } diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueDayService.java b/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueDayService.java new file mode 100644 index 00000000..cbc3ba1e --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueDayService.java @@ -0,0 +1,48 @@ +package woowacourse.touroot.travelogue.service; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import woowacourse.touroot.global.exception.BadRequestException; +import woowacourse.touroot.travelogue.domain.Travelogue; +import woowacourse.touroot.travelogue.domain.TravelogueDay; +import woowacourse.touroot.travelogue.dto.request.TravelogueDayRequest; +import woowacourse.touroot.travelogue.dto.request.TraveloguePlaceRequest; +import woowacourse.touroot.travelogue.repository.TravelogueDayRepository; + +@RequiredArgsConstructor +@Service +public class TravelogueDayService { + + private final TravelogueDayRepository travelogueDayRepository; + + @Transactional + public Map> createDays( + List requests, + Travelogue travelogue + ) { + Map> daysWithPlaceRequests = new LinkedHashMap<>(); + + for (int i = 0; i < requests.size(); i++) { + TravelogueDayRequest request = requests.get(i); + TravelogueDay travelogueDay = request.toTravelogueDay(i, travelogue); + daysWithPlaceRequests.put(travelogueDayRepository.save(travelogueDay), request.places()); + } + + return daysWithPlaceRequests; + } + + @Transactional(readOnly = true) + public List findDaysByTravelogue(Travelogue travelogue) { + return travelogueDayRepository.findByTravelogue(travelogue); + } + + @Transactional(readOnly = true) + public TravelogueDay findDayById(Long id) { + return travelogueDayRepository.findById(id) + .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 일자입니다.")); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueFacadeService.java b/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueFacadeService.java index d2418d36..3f261cbc 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueFacadeService.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueFacadeService.java @@ -2,17 +2,23 @@ import java.util.Comparator; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import woowacourse.touroot.travelogue.domain.Travelogue; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; -import woowacourse.touroot.travelogue.domain.day.dto.TravelogueDayResponse; -import woowacourse.touroot.travelogue.domain.day.service.TravelogueDayService; -import woowacourse.touroot.travelogue.domain.photo.service.TraveloguePhotoService; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; -import woowacourse.touroot.travelogue.domain.place.dto.TraveloguePlaceResponse; -import woowacourse.touroot.travelogue.domain.place.service.TraveloguePlaceService; -import woowacourse.touroot.travelogue.dto.TravelogueResponse; +import woowacourse.touroot.travelogue.domain.TravelogueDay; +import woowacourse.touroot.travelogue.domain.TraveloguePhoto; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; +import woowacourse.touroot.travelogue.dto.request.TravelogueDayRequest; +import woowacourse.touroot.travelogue.dto.request.TraveloguePhotoRequest; +import woowacourse.touroot.travelogue.dto.request.TraveloguePlaceRequest; +import woowacourse.touroot.travelogue.dto.request.TravelogueRequest; +import woowacourse.touroot.travelogue.dto.response.TravelogueDayResponse; +import woowacourse.touroot.travelogue.dto.response.TraveloguePlaceResponse; +import woowacourse.touroot.travelogue.dto.response.TravelogueResponse; @RequiredArgsConstructor @Service @@ -23,15 +29,50 @@ public class TravelogueFacadeService { private final TraveloguePlaceService traveloguePlaceService; private final TraveloguePhotoService traveloguePhotoService; + public TravelogueResponse createTravelogue(TravelogueRequest request) { + Travelogue travelogue = travelogueService.createTravelogue(request); + + return TravelogueResponse.of(travelogue, createDays(request.days(), travelogue)); + } + + private List createDays(List requests, Travelogue travelogue) { + Map> days = travelogueDayService.createDays(requests, travelogue); + + return days.keySet() + .stream() + .map(day -> TravelogueDayResponse.of(day, createPlaces(days.get(day), day))) + .toList(); + } + + private List createPlaces(List requests, TravelogueDay day) { + Map> places = traveloguePlaceService.createPlaces(requests, day); + + return places.keySet() + .stream() + .map(place -> TraveloguePlaceResponse.of(place, createPhotos(places.get(place), place))) + .toList(); + } + + private List createPhotos(List requests, TraveloguePlace place) { + List photos = traveloguePhotoService.createPhotos(requests, place); + + return photos.stream() + .map(TraveloguePhoto::getKey) + .toList(); + } + public TravelogueResponse findTravelogueById(Long id) { Travelogue travelogue = travelogueService.getTravelogueById(id); - return TravelogueResponse.builder() - .id(travelogue.getId()) - .title(travelogue.getTitle()) - .thumbnail(travelogue.getThumbnail()) - .days(findDaysOfTravelogue(travelogue)) - .build(); + return TravelogueResponse.of(travelogue, findDaysOfTravelogue(travelogue)); + } + + public Page findTravelogues(final Pageable pageable) { + Page travelogues = travelogueService.findAll(pageable); + + return new PageImpl<>(travelogues.stream() + .map(travelogue -> TravelogueResponse.of(travelogue, findDaysOfTravelogue(travelogue))) + .toList()); } private List findDaysOfTravelogue(Travelogue travelogue) { @@ -39,37 +80,19 @@ private List findDaysOfTravelogue(Travelogue travelogue) return travelogueDays.stream() .sorted(Comparator.comparing(TravelogueDay::getOrder)) - .map(this::getTravelogueDayResponse) + .map(day -> TravelogueDayResponse.of(day, findPlacesOfTravelogueDay(day))) .toList(); } - private TravelogueDayResponse getTravelogueDayResponse(TravelogueDay day) { - return TravelogueDayResponse.builder() - .id(day.getId()) - .places(findPlacesOfTravelogueDay(day)) - .build(); - } - private List findPlacesOfTravelogueDay(TravelogueDay travelogueDay) { List places = traveloguePlaceService.findTraveloguePlaceByDay(travelogueDay); return places.stream() .sorted(Comparator.comparing(TraveloguePlace::getOrder)) - .map(this::getTraveloguePlaceResponse) + .map(place -> TraveloguePlaceResponse.of(place, findPhotoUrlsOfTraveloguePlace(place))) .toList(); } - private TraveloguePlaceResponse getTraveloguePlaceResponse(TraveloguePlace place) { - return TraveloguePlaceResponse.builder() - .id(place.getId()) - .name(place.getName()) - .description(place.getDescription()) - .lat(place.getLatitude()) - .lng(place.getLongitude()) - .photoUrls(findPhotoUrlsOfTraveloguePlace(place)) - .build(); - } - private List findPhotoUrlsOfTraveloguePlace(TraveloguePlace place) { return traveloguePhotoService.findPhotoUrlsByPlace(place); } diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/service/TraveloguePhotoService.java b/backend/src/main/java/woowacourse/touroot/travelogue/service/TraveloguePhotoService.java new file mode 100644 index 00000000..24b11db7 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/service/TraveloguePhotoService.java @@ -0,0 +1,42 @@ +package woowacourse.touroot.travelogue.service; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import woowacourse.touroot.travelogue.domain.TraveloguePhoto; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; +import woowacourse.touroot.travelogue.dto.request.TraveloguePhotoRequest; +import woowacourse.touroot.travelogue.repository.TraveloguePhotoRepository; + +@RequiredArgsConstructor +@Service +public class TraveloguePhotoService { + + private final TraveloguePhotoRepository traveloguePhotoRepository; + + @Transactional + public List createPhotos(List requests, TraveloguePlace place) { + List photos = new ArrayList<>(); + + for (int i = 0; i < requests.size(); i++) { + TraveloguePhotoRequest request = requests.get(i); + TraveloguePhoto photo = request.toTraveloguePhoto(i, place); + photos.add(traveloguePhotoRepository.save(photo)); + } + + return photos; + } + + @Transactional(readOnly = true) + public List findPhotoUrlsByPlace(TraveloguePlace traveloguePlace) { + List photos = traveloguePhotoRepository.findByTraveloguePlace(traveloguePlace); + + return photos.stream() + .sorted(Comparator.comparing(TraveloguePhoto::getOrder)) + .map(TraveloguePhoto::getKey) + .toList(); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/service/TraveloguePlaceService.java b/backend/src/main/java/woowacourse/touroot/travelogue/service/TraveloguePlaceService.java new file mode 100644 index 00000000..fcb72b29 --- /dev/null +++ b/backend/src/main/java/woowacourse/touroot/travelogue/service/TraveloguePlaceService.java @@ -0,0 +1,61 @@ +package woowacourse.touroot.travelogue.service; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import woowacourse.touroot.global.exception.BadRequestException; +import woowacourse.touroot.place.domain.Place; +import woowacourse.touroot.place.repository.PlaceRepository; +import woowacourse.touroot.travelogue.domain.TravelogueDay; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; +import woowacourse.touroot.travelogue.dto.request.TraveloguePhotoRequest; +import woowacourse.touroot.travelogue.dto.request.TraveloguePlaceRequest; +import woowacourse.touroot.travelogue.repository.TraveloguePlaceRepository; + +@RequiredArgsConstructor +@Service +public class TraveloguePlaceService { + + private final PlaceRepository placeRepository; + private final TraveloguePlaceRepository traveloguePlaceRepository; + + @Transactional + public Map> createPlaces( + List requests, + TravelogueDay day + ) { + Map> places = new LinkedHashMap<>(); + + for (int i = 0; i < requests.size(); i++) { + TraveloguePlaceRequest request = requests.get(i); + Place place = getPlace(request); + + TraveloguePlace traveloguePlace = request.toTraveloguePlace(i, place, day); + places.put(traveloguePlaceRepository.save(traveloguePlace), request.photos()); + } + + return places; + } + + private Place getPlace(TraveloguePlaceRequest request) { + return placeRepository.findByNameAndLatitudeAndLongitude( + request.name(), + request.location().lat(), + request.location().lng() + ).orElseGet(() -> placeRepository.save(request.toPlace())); + } + + @Transactional(readOnly = true) + public List findTraveloguePlaceByDay(TravelogueDay travelogueDay) { + return traveloguePlaceRepository.findByTravelogueDay(travelogueDay); + } + + @Transactional(readOnly = true) + public TraveloguePlace findTraveloguePlaceById(Long id) { + return traveloguePlaceRepository.findById(id) + .orElseThrow(() -> new BadRequestException("존재하지 않는 여행 장소입니다.")); + } +} diff --git a/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueService.java b/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueService.java index 41fe3571..e9d805bb 100644 --- a/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueService.java +++ b/backend/src/main/java/woowacourse/touroot/travelogue/service/TravelogueService.java @@ -1,10 +1,13 @@ package woowacourse.touroot.travelogue.service; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import woowacourse.touroot.global.exception.BadRequestException; import woowacourse.touroot.travelogue.domain.Travelogue; +import woowacourse.touroot.travelogue.dto.request.TravelogueRequest; import woowacourse.touroot.travelogue.repository.TravelogueRepository; @RequiredArgsConstructor @@ -13,9 +16,19 @@ public class TravelogueService { private final TravelogueRepository travelogueRepository; + @Transactional + public Travelogue createTravelogue(TravelogueRequest request) { + Travelogue travelogue = request.toTravelogue(); + return travelogueRepository.save(travelogue); + } + @Transactional(readOnly = true) public Travelogue getTravelogueById(Long id) { return travelogueRepository.findById(id) .orElseThrow(() -> new BadRequestException("존재하지 않는 여행기입니다.")); } + + public Page findAll(Pageable pageable) { + return travelogueRepository.findAll(pageable); + } } diff --git a/backend/src/test/java/woowacourse/touroot/travelogue/controller/TravelogueControllerTest.java b/backend/src/test/java/woowacourse/touroot/travelogue/controller/TravelogueControllerTest.java index adfa3748..20d04067 100644 --- a/backend/src/test/java/woowacourse/touroot/travelogue/controller/TravelogueControllerTest.java +++ b/backend/src/test/java/woowacourse/touroot/travelogue/controller/TravelogueControllerTest.java @@ -43,4 +43,4 @@ void findTravelogue() { .statusCode(200) .body("title", is("여행기 1")); } -} \ No newline at end of file +} diff --git a/backend/src/test/java/woowacourse/touroot/utils/TestFixture.java b/backend/src/test/java/woowacourse/touroot/utils/TestFixture.java index 7f2aaac9..18cd9122 100644 --- a/backend/src/test/java/woowacourse/touroot/utils/TestFixture.java +++ b/backend/src/test/java/woowacourse/touroot/utils/TestFixture.java @@ -1,16 +1,17 @@ package woowacourse.touroot.utils; +import java.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import woowacourse.touroot.place.domain.Place; import woowacourse.touroot.place.repository.PlaceRepository; import woowacourse.touroot.travelogue.domain.Travelogue; -import woowacourse.touroot.travelogue.domain.day.domain.TravelogueDay; -import woowacourse.touroot.travelogue.domain.day.repository.TravelogueDayRepository; -import woowacourse.touroot.travelogue.domain.photo.domain.TraveloguePhoto; -import woowacourse.touroot.travelogue.domain.photo.repository.TraveloguePhotoRepository; -import woowacourse.touroot.travelogue.domain.place.domain.TraveloguePlace; -import woowacourse.touroot.travelogue.domain.place.repsitory.TraveloguePlaceRepository; +import woowacourse.touroot.travelogue.domain.TravelogueDay; +import woowacourse.touroot.travelogue.domain.TraveloguePhoto; +import woowacourse.touroot.travelogue.domain.TraveloguePlace; +import woowacourse.touroot.travelogue.repository.TravelogueDayRepository; +import woowacourse.touroot.travelogue.repository.TraveloguePhotoRepository; +import woowacourse.touroot.travelogue.repository.TraveloguePlaceRepository; import woowacourse.touroot.travelogue.repository.TravelogueRepository; import woowacourse.touroot.travelplan.domain.TravelPlan; import woowacourse.touroot.travelplan.domain.TravelPlanDay; @@ -19,8 +20,6 @@ import woowacourse.touroot.travelplan.repository.TravelPlanPlaceRepository; import woowacourse.touroot.travelplan.repository.TravelPlanRepository; -import java.time.LocalDate; - @Component public class TestFixture { @@ -65,7 +64,7 @@ public static TraveloguePlace getTraveloguePlace(Integer order, String descripti } public static TraveloguePhoto getTraveloguePhoto(String key, Integer order, TraveloguePlace traveloguePlace) { - return new TraveloguePhoto(key, order, traveloguePlace); + return new TraveloguePhoto(order, key, traveloguePlace); } public static TravelPlan getTravelPlan(String title, LocalDate startDate) {