Skip to content

Commit

Permalink
[Feature] - 여행 계획 공유 기능 구현 (woowacourse-teams#203)
Browse files Browse the repository at this point in the history
* refactor: TravelPlan 공유를 위한 URL 생성에 사용되는 Key(UUID) 필드 추가

* refactor: TravelPlan 생성 과정과 응답 DTO에 UUID 필드 추가

* refactor: TravelPlan UUID 필드 추가에 따른 테스트 코드 수정

* feat: 공유된 여행 계획 조회 기능 구현

* feat: Swagger DTO 필드 example 작성

* fix: 공유된 여행 계획 조회 엔드포인트 수정

* test: 여행 계획 공유 테스트 작성

* refactor: 여행 계획 공유 키 생성 로직 DTO에서 서비스로 이동 개선

* feat: 로그인 되지 않은 유저도 공유된 여행 계획을 조회할 수 있도록 기능 추가

---------

Co-authored-by: hangillee <skfcb10@naver.com>
  • Loading branch information
Libienz and hangillee committed Aug 20, 2024
1 parent 7e3522c commit 58e7457
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import kr.touroot.authentication.infrastructure.JwtTokenProvider;
import kr.touroot.global.auth.dto.HttpRequestInfo;
import kr.touroot.global.exception.dto.ExceptionResponse;
Expand All @@ -17,9 +19,6 @@
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

@RequiredArgsConstructor
@Slf4j
@Component
Expand All @@ -39,6 +38,7 @@ public class JwtAuthFilter extends OncePerRequestFilter {
new HttpRequestInfo(HttpMethod.GET, "/v3/api-docs/**"),
new HttpRequestInfo(HttpMethod.GET, "/api/v1/travelogues/**"),
new HttpRequestInfo(HttpMethod.GET, "/api/v1/login/**"),
new HttpRequestInfo(HttpMethod.GET, "/api/v1/travel-plans/shared/**"),
new HttpRequestInfo(HttpMethod.OPTIONS, "/**")
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.UUID;
import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.global.exception.dto.ExceptionResponse;
import kr.touroot.travelplan.dto.request.TravelPlanCreateRequest;
Expand All @@ -16,9 +18,12 @@
import kr.touroot.travelplan.service.TravelPlanService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;
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;

@Tag(name = "여행 계획")
@RequiredArgsConstructor
Expand Down Expand Up @@ -80,4 +85,24 @@ public ResponseEntity<TravelPlanResponse> readTravelPlan(
TravelPlanResponse data = travelPlanService.readTravelPlan(id, memberAuth);
return ResponseEntity.ok(data);
}

@Operation(summary = "공유된 여행 계획 상세 조회")
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "여행 계획 상세 조회가 정상적으로 성공했을 때"
),
@ApiResponse(
responseCode = "400",
description = "존재하지 않은 여행 계획을 조회할 때",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
),
})
@GetMapping("shared/{shareKey}")
public ResponseEntity<TravelPlanResponse> readSharedTravelPlan(
@Parameter(description = "여행 계획 공유 키") @PathVariable UUID shareKey
) {
TravelPlanResponse data = travelPlanService.readTravelPlan(shareKey);
return ResponseEntity.ok(data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDate;
import java.util.UUID;
import kr.touroot.global.entity.BaseEntity;
import kr.touroot.global.exception.BadRequestException;
import kr.touroot.member.domain.Member;
Expand All @@ -23,6 +24,7 @@ public class TravelPlan extends BaseEntity {

private static final int TITLE_MIN_LENGTH = 1;
private static final int TITLE_MAX_LENGTH = 20;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -33,20 +35,24 @@ public class TravelPlan extends BaseEntity {
@Column(nullable = false)
private LocalDate startDate;

@Column(nullable = false)
private UUID shareKey;

@JoinColumn(name = "author_id", nullable = false)
@ManyToOne(fetch = FetchType.LAZY)
private Member author;

public TravelPlan(Long id, String title, LocalDate startDate, Member author) {
public TravelPlan(Long id, String title, LocalDate startDate, UUID shareKey, Member author) {
validate(title, startDate, author);
this.id = id;
this.title = title;
this.startDate = startDate;
this.shareKey = shareKey;
this.author = author;
}

public TravelPlan(String title, LocalDate startDate, Member author) {
this(null, title, startDate, author);
public TravelPlan(String title, LocalDate startDate, UUID shareKey, Member author) {
this(null, title, startDate, shareKey, author);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.util.List;

import java.util.UUID;
import kr.touroot.member.domain.Member;
import kr.touroot.travelplan.domain.TravelPlan;
import lombok.Builder;
Expand All @@ -27,7 +27,7 @@ public record TravelPlanCreateRequest(
List<PlanDayCreateRequest> days
) {

public TravelPlan toTravelPlan(Member author) {
return new TravelPlan(title, startDate, author);
public TravelPlan toTravelPlan(Member author, UUID shareKey) {
return new TravelPlan(title, startDate, shareKey, author);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import io.swagger.v3.oas.annotations.media.Schema;

public record TravelPlanCreateResponse(
@Schema(description = "생성된 여행 계획 id")
@Schema(description = "생성된 여행 계획 id", example = "1")
Long id
) {
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package kr.touroot.travelplan.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import kr.touroot.place.domain.Place;
import kr.touroot.travelplan.domain.TravelPlanPlace;
import lombok.Builder;

@Builder
public record TravelPlanPlaceResponse(
@Schema(description = "여행 장소 이름") String placeName,
@Schema(description = "여행 장소 이름", example = "잠실한강공원") String placeName,
@Schema(description = "여행 장소 위치") TravelPlanPositionResponse position,
@Schema(description = "여행 장소 설명") String description
@Schema(description = "여행 장소 설명", example = "신나는 여행 장소") String description
) {

public static TravelPlanPlaceResponse from(TravelPlanPlace planPlace) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

@Builder
public record TravelPlanPositionResponse(
@Schema(description = "여행 장소 위도") String lat,
@Schema(description = "여행 계획 경도") String lng
@Schema(description = "여행 장소 위도", example = "37.5175896") String lat,
@Schema(description = "여행 계획 경도", example = "127.0867236") String lng
) {

public static TravelPlanPositionResponse from(Place place) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import kr.touroot.travelplan.domain.TravelPlan;
import lombok.Builder;

@Builder
public record TravelPlanResponse(
@Schema(description = "여행 계획 id") Long id,
@Schema(description = "여행 계획 제목") String title,
@Schema(description = "여행 시작일") LocalDate startDate,
@Schema(description = "여행 계획 날짜별 정보") List<TravelPlanDayResponse> days
@Schema(description = "여행 계획 id", example = "1") Long id,
@Schema(description = "여행 계획 제목", example = "신나는 잠실 한강 여행") String title,
@Schema(description = "여행 시작일", example = "2024-11-16") LocalDate startDate,
@Schema(description = "여행 계획 날짜별 정보") List<TravelPlanDayResponse> days,
@Schema(description = "여행 계획 공유 share Key") UUID shareKey
) {

public static TravelPlanResponse of(TravelPlan travelPlan, List<TravelPlanDayResponse> days) {
Expand All @@ -20,6 +22,7 @@ public static TravelPlanResponse of(TravelPlan travelPlan, List<TravelPlanDayRes
.title(travelPlan.getTitle())
.startDate(travelPlan.getStartDate())
.days(days)
.shareKey(travelPlan.getShareKey())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kr.touroot.travelplan.repository;

import java.util.Optional;
import java.util.UUID;
import kr.touroot.member.domain.Member;
import kr.touroot.travelplan.domain.TravelPlan;
import org.springframework.data.domain.Page;
Expand All @@ -9,4 +11,6 @@
public interface TravelPlanRepository extends JpaRepository<TravelPlan, Long> {

Page<TravelPlan> findAllByAuthor(Member member, Pageable pageable);

Optional<TravelPlan> findByShareKey(UUID shareKey);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package kr.touroot.travelplan.service;

import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.global.exception.BadRequestException;
import kr.touroot.global.exception.ForbiddenException;
Expand All @@ -26,9 +29,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Comparator;
import java.util.List;

@RequiredArgsConstructor
@Service
public class TravelPlanService {
Expand All @@ -42,7 +42,7 @@ public class TravelPlanService {
@Transactional
public TravelPlanCreateResponse createTravelPlan(TravelPlanCreateRequest request, MemberAuth memberAuth) {
Member author = getMemberByMemberAuth(memberAuth);
TravelPlan travelPlan = request.toTravelPlan(author);
TravelPlan travelPlan = request.toTravelPlan(author, UUID.randomUUID());
validStartDate(travelPlan);

TravelPlan savedTravelPlan = travelPlanRepository.save(travelPlan);
Expand Down Expand Up @@ -95,6 +95,13 @@ public TravelPlanResponse readTravelPlan(Long planId, MemberAuth memberAuth) {
return TravelPlanResponse.of(travelPlan, getTravelPlanDayResponses(travelPlan));
}

@Transactional(readOnly = true)
public TravelPlanResponse readTravelPlan(UUID shareKey) {
TravelPlan travelPlan = getTravelPlanByShareKey(shareKey);

return TravelPlanResponse.of(travelPlan, getTravelPlanDayResponses(travelPlan));
}

private void validateAuthor(TravelPlan travelPlan, Member member) {
if (!travelPlan.isAuthor(member)) {
throw new ForbiddenException("여행 계획은 작성자만 조회할 수 있습니다.");
Expand All @@ -106,6 +113,11 @@ private TravelPlan getTravelPlanById(Long planId) {
.orElseThrow(() -> new BadRequestException("존재하지 않는 여행 계획입니다."));
}

private TravelPlan getTravelPlanByShareKey(UUID shareKey) {
return travelPlanRepository.findByShareKey(shareKey)
.orElseThrow(() -> new BadRequestException("존재하지 않는 여행 계획입니다."));
}

private List<TravelPlanDayResponse> getTravelPlanDayResponses(TravelPlan travelPlan) {
List<TravelPlanDay> planDays = travelPlanDayRepository.findByPlan(travelPlan);

Expand Down
Loading

0 comments on commit 58e7457

Please sign in to comment.