Skip to content

Commit

Permalink
[Feature] - 마이 페이지 API 구현 (woowacourse-teams#195)
Browse files Browse the repository at this point in the history
* feat: 프로필 조회 API 구현

* feat: 내 여행기 조회 API 구현

* feat: 내 여행계획 조회 API 구현

* feat: 내 여행계획/여행기 조회 응답에 id 추가

* test: 여행 계획 기간 조회 테스트 작성
  • Loading branch information
eunjungL authored and hangillee committed Aug 20, 2024
1 parent b954dd2 commit 7e3522c
Show file tree
Hide file tree
Showing 13 changed files with 212 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package kr.touroot.global.auth.dto;

import io.swagger.v3.oas.annotations.Hidden;
import jakarta.validation.constraints.NotNull;

@Hidden
public record MemberAuth(@NotNull Long memberId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package kr.touroot.member.controller;

import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.member.dto.MyTravelPlanResponse;
import kr.touroot.member.dto.MyTraveloguesResponse;
import kr.touroot.member.dto.ProfileResponse;
import kr.touroot.member.service.MyPageFacadeService;
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;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "마이 페이지")
@RequiredArgsConstructor
@RestController
@RequestMapping("api/v1/member/me")
public class MyPageController {

private final MyPageFacadeService myPageFacadeService;

@GetMapping("/profile")
public ResponseEntity<ProfileResponse> readProfile(@NotNull MemberAuth memberAuth) {
ProfileResponse data = myPageFacadeService.readProfile(memberAuth);
return ResponseEntity.ok(data);
}

@PageableAsQueryParam
@GetMapping("/travelogues")
public ResponseEntity<Page<MyTraveloguesResponse>> readTravelogues(
@NotNull MemberAuth memberAuth,
@Parameter(hidden = true)
@PageableDefault(size = 5, sort = "id", direction = Sort.Direction.DESC)
Pageable pageable
) {
Page<MyTraveloguesResponse> data = myPageFacadeService.readTravelogues(memberAuth, pageable);
return ResponseEntity.ok(data);
}

@PageableAsQueryParam
@GetMapping("/travel-plans")
public ResponseEntity<Page<MyTravelPlanResponse>> readTravelPlans(
@NotNull MemberAuth memberAuth,
@Parameter(hidden = true)
@PageableDefault(size = 5, sort = "id", direction = Sort.Direction.DESC)
Pageable pageable
) {
Page<MyTravelPlanResponse> data = myPageFacadeService.readTravelPlans(memberAuth, pageable);
return ResponseEntity.ok(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.touroot.member.dto;

import kr.touroot.travelplan.domain.TravelPlan;
import lombok.Builder;

import java.time.LocalDate;

@Builder
public record MyTravelPlanResponse(long id, String title, LocalDate startDate, LocalDate endDate) {

public static MyTravelPlanResponse of(TravelPlan travelPlan, int period) {
return MyTravelPlanResponse.builder()
.id(travelPlan.getId())
.title(travelPlan.getTitle())
.startDate(travelPlan.getStartDate())
.endDate(travelPlan.getStartDate().plusDays(period))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kr.touroot.member.dto;

import kr.touroot.travelogue.domain.Travelogue;
import lombok.Builder;

import java.time.format.DateTimeFormatter;

@Builder
public record MyTraveloguesResponse(long id, String title, String thumbnailUrl, String createdAt) {

public static MyTraveloguesResponse from(Travelogue travelogue) {
String createdAt = travelogue.getCreatedAt()
.toLocalDate()
.format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));

return MyTraveloguesResponse.builder()
.id(travelogue.getId())
.title(travelogue.getTitle())
.createdAt(createdAt)
.thumbnailUrl(travelogue.getThumbnail())
.build();
}
}
15 changes: 15 additions & 0 deletions backend/src/main/java/kr/touroot/member/dto/ProfileResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kr.touroot.member.dto;

import kr.touroot.member.domain.Member;
import lombok.Builder;

@Builder
public record ProfileResponse(String profileImageUrl, String nickname) {

public static ProfileResponse from(Member member) {
return ProfileResponse.builder()
.profileImageUrl(member.getProfileImageUrl())
.nickname(member.getNickname())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package kr.touroot.member.service;

import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.member.domain.Member;
import kr.touroot.member.dto.MyTravelPlanResponse;
import kr.touroot.member.dto.MyTraveloguesResponse;
import kr.touroot.member.dto.ProfileResponse;
import kr.touroot.travelogue.domain.Travelogue;
import kr.touroot.travelogue.service.TravelogueService;
import kr.touroot.travelplan.domain.TravelPlan;
import kr.touroot.travelplan.service.TravelPlanService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class MyPageFacadeService {

private final MemberService memberService;
private final TravelogueService travelogueService;
private final TravelPlanService travelPlanService;

public ProfileResponse readProfile(MemberAuth memberAuth) {
Member member = memberService.getById(memberAuth.memberId());
return ProfileResponse.from(member);
}

public Page<MyTraveloguesResponse> readTravelogues(MemberAuth memberAuth, Pageable pageable) {
Member member = memberService.getById(memberAuth.memberId());
Page<Travelogue> travelogues = travelogueService.findAllByMember(member, pageable);

return travelogues.map(MyTraveloguesResponse::from);
}

public Page<MyTravelPlanResponse> readTravelPlans(MemberAuth memberAuth, Pageable pageable) {
Member member = memberService.getById(memberAuth.memberId());
Page<TravelPlan> travelPlans = travelPlanService.getAllByAuthor(member, pageable);

return travelPlans.map(this::getMyTravelPlanResponse);
}

private MyTravelPlanResponse getMyTravelPlanResponse(TravelPlan travelPlan) {
int period = travelPlanService.calculateTravelPeriod(travelPlan);
return MyTravelPlanResponse.of(travelPlan, period);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package kr.touroot.travelogue.repository;

import kr.touroot.member.domain.Member;
import kr.touroot.travelogue.domain.Travelogue;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TravelogueRepository extends JpaRepository<Travelogue, Long> {

Page<Travelogue> findAllByAuthor(Member author, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ public Travelogue getTravelogueById(Long id) {
public Page<Travelogue> findAll(Pageable pageable) {
return travelogueRepository.findAll(pageable);
}

public Page<Travelogue> findAllByMember(Member member, Pageable pageable) {
return travelogueRepository.findAllByAuthor(member, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package kr.touroot.travelplan.repository;

import kr.touroot.member.domain.Member;
import kr.touroot.travelplan.domain.TravelPlan;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TravelPlanRepository extends JpaRepository<TravelPlan, Long> {

Page<TravelPlan> findAllByAuthor(Member member, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import kr.touroot.travelplan.repository.TravelPlanPlaceRepository;
import kr.touroot.travelplan.repository.TravelPlanRepository;
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;

Expand Down Expand Up @@ -121,4 +123,13 @@ private List<TravelPlanPlaceResponse> getTravelPlanPlaceResponses(TravelPlanDay
.map(TravelPlanPlaceResponse::from)
.toList();
}

public Page<TravelPlan> getAllByAuthor(Member member, Pageable pageable) {
return travelPlanRepository.findAllByAuthor(member, pageable);
}

public int calculateTravelPeriod(TravelPlan travelPlan) {
return travelPlanDayRepository.findByPlan(travelPlan)
.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ void readTravelPlanWithNonExist() {
@Test
void readTravelPlanWithNotAuthor() {
// given
long id = testHelper.initTravelPlanTestData(member);
long id = testHelper.initTravelPlanTestData(member).getId();
Member notAuthor = testHelper.initMemberTestData();
String notAuthorAccessToken = jwtTokenProvider.createToken(notAuthor.getId());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static TravelPlanPlace getTravelPlanPlace(String description, int order,
return new TravelPlanPlace(description, order, day, place);
}

public long initTravelPlanTestData(Member author) {
public TravelPlan initTravelPlanTestData(Member author) {
TravelPlan travelPlan = getTravelPlan("여행계획", LocalDate.MAX, author);
TravelPlanDay travelPlanDay = getTravelPlanDay(0, travelPlan);
Place place = getPlace("장소", "37.5175896", "127.0867236", "");
Expand All @@ -68,7 +68,9 @@ public long initTravelPlanTestData(Member author) {
travelPlanRepository.save(travelPlan);
travelPlanDayRepository.save(travelPlanDay);
placeRepository.save(place);
return travelPlanPlaceRepository.save(travelPlanPlace).getId();
travelPlanPlaceRepository.save(travelPlanPlace);

return travelPlan;
}

public Member initMemberTestData() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import kr.touroot.global.exception.BadRequestException;
import kr.touroot.global.exception.ForbiddenException;
import kr.touroot.member.domain.Member;
import kr.touroot.travelplan.domain.TravelPlan;
import kr.touroot.travelplan.dto.request.PlanDayCreateRequest;
import kr.touroot.travelplan.dto.request.PlanPlaceCreateRequest;
import kr.touroot.travelplan.dto.request.PlanPositionCreateRequest;
Expand Down Expand Up @@ -107,7 +108,7 @@ void createTravelPlanWithInvalidStartDate() {
@Test
void readTravelPlan() {
// given
Long id = testHelper.initTravelPlanTestData(author);
Long id = testHelper.initTravelPlanTestData(author).getId();

// when
TravelPlanResponse actual = travelPlanService.readTravelPlan(id, memberAuth);
Expand All @@ -133,12 +134,25 @@ void readTravelPlanWitNonExist() {
@Test
void readTravelPlanWithNotAuthor() {
// given
Long id = testHelper.initTravelPlanTestData(author);
Long id = testHelper.initTravelPlanTestData(author).getId();
MemberAuth notAuthor = new MemberAuth(testHelper.initMemberTestData().getId());

// when & then
assertThatThrownBy(() -> travelPlanService.readTravelPlan(id, notAuthor))
.isInstanceOf(ForbiddenException.class)
.hasMessage("여행 계획은 작성자만 조회할 수 있습니다.");
}

@DisplayName("여행 계획 서비스는 여행 계획 일자를 계산해 반환한다.")
@Test
void calculateTravelPeriod() {
// given
TravelPlan travelPlan = testHelper.initTravelPlanTestData(author);

// when
int actual = travelPlanService.calculateTravelPeriod(travelPlan);

// then
assertThat(actual).isEqualTo(1);
}
}

0 comments on commit 7e3522c

Please sign in to comment.