Skip to content

Commit

Permalink
[BE] 발자국 산책 상태 변경 기능 구현 (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehtjsv2 committed Aug 6, 2024
1 parent 0c3e8c8 commit 0d65c2e
Show file tree
Hide file tree
Showing 12 changed files with 425 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
import com.woowacourse.friendogly.common.ApiResponse;
import com.woowacourse.friendogly.footprint.dto.request.FindNearFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest;
import com.woowacourse.friendogly.footprint.dto.response.FindMyLatestFootprintTimeAndPetExistenceResponse;
import com.woowacourse.friendogly.footprint.dto.response.FindNearFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.FindOneFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.SaveFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.UpdateWalkStatusResponse;
import com.woowacourse.friendogly.footprint.service.FootprintCommandService;
import com.woowacourse.friendogly.footprint.service.FootprintQueryService;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -75,4 +78,13 @@ public ApiResponse<FindMyLatestFootprintTimeAndPetExistenceResponse> findMyLates
= footprintQueryService.findMyLatestFootprintTimeAndPetExistence(memberId);
return ApiResponse.ofSuccess(response);
}

@PatchMapping("/walk-status")
public ApiResponse<UpdateWalkStatusResponse> updateWalkStatus(
@Auth Long memberId,
@Valid @RequestBody UpdateWalkStatusRequest request
) {
UpdateWalkStatusResponse walkStatusResponse = footprintCommandService.updateWalkStatus(memberId, request);
return ApiResponse.ofSuccess(walkStatusResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.woowacourse.friendogly.footprint.domain;

import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE;
import static com.woowacourse.friendogly.footprint.domain.WalkStatus.AFTER;
import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING;

import com.woowacourse.friendogly.member.domain.Member;
import jakarta.persistence.Column;
Expand Down Expand Up @@ -59,11 +60,15 @@ public Footprint(
Member member,
Location location
) {
this.member = member;
this.location = location;
this.walkStatus = BEFORE;
this.createdAt = LocalDateTime.now();
this.isDeleted = false;
this(
member,
location,
WalkStatus.BEFORE,
null,
null,
LocalDateTime.now(),
false
);
}

public Footprint(
Expand All @@ -88,6 +93,10 @@ public boolean isNear(Location location) {
return this.location.isWithin(location, NEAR_RADIUS_AS_METER);
}

public boolean isInsideBoundary(Location location) {
return this.location.isWithin(location, RADIUS_AS_METER);
}

public boolean isCreatedBy(Long memberId) {
return this.member.getId()
.equals(memberId);
Expand All @@ -106,4 +115,23 @@ public LocalDateTime findChangedWalkStatusTime() {
}
return endWalkTime;
}

public void updateWalkStatusWithCurrentLocation(Location currentLocation) {
if (walkStatus.isBefore() && isInsideBoundary(currentLocation)) {
startWalk();
}
if (walkStatus.isOngoing() && !isInsideBoundary(currentLocation)) {
endWalk();
}
}

private void startWalk() {
walkStatus = ONGOING;
startWalkTime = LocalDateTime.now();
}

private void endWalk() {
walkStatus = AFTER;
endWalkTime = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.woowacourse.friendogly.footprint.dto.request;

import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;

public record UpdateWalkStatusRequest(

@NotNull
@DecimalMin(value = "-90.0", message = "위도는 -90도 이상 90도 이하로 입력해 주세요.")
@DecimalMax(value = "90.0", message = "위도는 -90도 이상 90도 이하로 입력해 주세요.")
double latitude,

@NotNull
@DecimalMin(value = "-180.0", message = "경도는 -180도 이상 180도 이하로 입력해 주세요.")
@DecimalMax(value = "180.0", message = "경도는 -180도 이상 180도 이하로 입력해 주세요.")
double longitude
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.woowacourse.friendogly.footprint.dto.response;

import com.woowacourse.friendogly.footprint.domain.WalkStatus;

public record UpdateWalkStatusResponse(WalkStatus walkStatus) {

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.woowacourse.friendogly.footprint.repository;

import com.woowacourse.friendogly.exception.FriendoglyException;
import com.woowacourse.friendogly.footprint.domain.Footprint;
import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -13,4 +14,9 @@ public interface FootprintRepository extends JpaRepository<Footprint, Long> {
boolean existsByMemberIdAndCreatedAtAfter(Long memberId, LocalDateTime createdAt);

Optional<Footprint> findTopOneByMemberIdOrderByCreatedAtDesc(Long memberId);

default Footprint getTopOneByMemberIdOrderByCreatedAtDesc(Long memberId) {
return findTopOneByMemberIdOrderByCreatedAtDesc(memberId)
.orElseThrow(() -> new FriendoglyException("발자국이 존재하지 않습니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.woowacourse.friendogly.footprint.domain.Footprint;
import com.woowacourse.friendogly.footprint.domain.Location;
import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest;
import com.woowacourse.friendogly.footprint.dto.response.SaveFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.UpdateWalkStatusResponse;
import com.woowacourse.friendogly.footprint.repository.FootprintRepository;
import com.woowacourse.friendogly.member.domain.Member;
import com.woowacourse.friendogly.member.repository.MemberRepository;
Expand Down Expand Up @@ -77,4 +79,14 @@ private void validateRecentFootprintExists(Long memberId) {
throw new FriendoglyException(String.format("마지막 발자국을 찍은 뒤 %d초가 경과되지 않았습니다.", FOOTPRINT_COOLDOWN_SECOND));
}
}

public UpdateWalkStatusResponse updateWalkStatus(Long memberId, UpdateWalkStatusRequest request) {
Footprint footprint = footprintRepository.getTopOneByMemberIdOrderByCreatedAtDesc(memberId);
if (footprint.isDeleted()) {
throw new FriendoglyException("가장 최근 발자국이 삭제된 상태입니다.");
}

footprint.updateWalkStatusWithCurrentLocation(new Location(request.latitude(), request.longitude()));
return new UpdateWalkStatusResponse(footprint.getWalkStatus());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE;
import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpHeaders.LOCATION;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
Expand All @@ -20,12 +23,15 @@

import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.epages.restdocs.apispec.Schema;
import com.woowacourse.friendogly.exception.FriendoglyException;
import com.woowacourse.friendogly.footprint.controller.FootprintController;
import com.woowacourse.friendogly.footprint.dto.request.SaveFootprintRequest;
import com.woowacourse.friendogly.footprint.dto.request.UpdateWalkStatusRequest;
import com.woowacourse.friendogly.footprint.dto.response.FindMyLatestFootprintTimeAndPetExistenceResponse;
import com.woowacourse.friendogly.footprint.dto.response.FindNearFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.FindOneFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.SaveFootprintResponse;
import com.woowacourse.friendogly.footprint.dto.response.UpdateWalkStatusResponse;
import com.woowacourse.friendogly.footprint.dto.response.detail.PetDetail;
import com.woowacourse.friendogly.footprint.service.FootprintCommandService;
import com.woowacourse.friendogly.footprint.service.FootprintQueryService;
Expand All @@ -38,6 +44,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.restdocs.payload.JsonFieldType;

@WebMvcTest(FootprintController.class)
public class FootprintApiDocsTest extends RestDocsTest {
Expand Down Expand Up @@ -251,6 +258,78 @@ void findMyLatestFootprintTimeAndPetExistence() throws Exception {
.andExpect(status().isOk());
}

@DisplayName("발자국 산책 상태 변경")
@Test
void updateWalkStatus_200() throws Exception {
UpdateWalkStatusRequest request = new UpdateWalkStatusRequest(37.5136533, 127.0983182);
UpdateWalkStatusResponse response = new UpdateWalkStatusResponse(ONGOING);

given(footprintCommandService.updateWalkStatus(any(), any()))
.willReturn(response);

mockMvc
.perform(patch("/footprints/walk-status")
.content(objectMapper.writeValueAsString(request))
.contentType(APPLICATION_JSON)
.header(AUTHORIZATION, 1L))
.andExpect(status().isOk())
.andDo(print())
.andDo(document("footprints/walk-status/200",
getDocumentRequest(),
getDocumentResponse(),
resource(ResourceSnippetParameters.builder()
.tag("Footprint API")
.summary("발자국 산책 상태 변경 API")
.requestHeaders(
headerWithName(AUTHORIZATION).description("로그인한 회원 ID")
)
.responseFields(
fieldWithPath("isSuccess").description("응답 성공 여부"),
fieldWithPath("data.walkStatus").description("발자국 산책 상태")
)
.requestSchema(Schema.schema("updateWalkStatusResponse"))
.build()
)
));
}

@DisplayName("발자국 산책 상태 변경실패 - 발자국이 없을 경우")
@Test
void updateWalkStatus_400() throws Exception {
UpdateWalkStatusRequest request = new UpdateWalkStatusRequest(37.5136533, 127.0983182);
UpdateWalkStatusResponse response = new UpdateWalkStatusResponse(ONGOING);

when(footprintCommandService.updateWalkStatus(any(), any()))
.thenThrow(new FriendoglyException("예외 메세지"));

mockMvc
.perform(patch("/footprints/walk-status")
.content(objectMapper.writeValueAsString(request))
.contentType(APPLICATION_JSON)
.header(AUTHORIZATION, 1L))
.andExpect(status().isBadRequest())
.andDo(print())
.andDo(document("footprints/walk-status/400",
getDocumentRequest(),
getDocumentResponse(),
resource(ResourceSnippetParameters.builder()
.tag("Footprint API")
.summary("발자국 산책 상태 변경 API")
.requestHeaders(
headerWithName(AUTHORIZATION).description("로그인한 회원 ID")
)
.responseFields(
fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"),
fieldWithPath("data.errorCode").type(JsonFieldType.STRING).description("에러 코드"),
fieldWithPath("data.errorMessage").type(JsonFieldType.STRING).description("에러메세지"),
fieldWithPath("data.detail").type(JsonFieldType.ARRAY).description("에러 디테일")
)
.requestSchema(Schema.schema("updateWalkStatusResponse"))
.build()
)
));
}

@Override
protected Object controller() {
return new FootprintController(footprintCommandService, footprintQueryService);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.woowacourse.friendogly.footprint.controller;

import static com.woowacourse.friendogly.footprint.domain.WalkStatus.AFTER;
import static com.woowacourse.friendogly.footprint.domain.WalkStatus.BEFORE;
import static com.woowacourse.friendogly.footprint.domain.WalkStatus.ONGOING;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
Expand All @@ -19,6 +22,7 @@
import io.restassured.http.ContentType;
import java.time.LocalDate;
import org.junit.jupiter.api.AfterEach;
import java.time.LocalDateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -118,11 +122,6 @@ void setUp() {
);
}

@AfterEach
void setDown() {
footprintRepository.deleteAll();
}

@DisplayName("발자국을 정상적으로 생성한다. (201)")
@Test
void save() {
Expand Down Expand Up @@ -253,4 +252,64 @@ void findMyLatestFootprintTime_MyFootprintDoesNotExist() {
.statusCode(HttpStatus.OK.value())
.body("data.changedWalkStatusTime", nullValue());
}

@DisplayName("발자국 범위밖에서 안으로 들어오면 산책중으로 상태가 변한다 (200)")
@Test
void updateWalkStatus_toOngoing() {
footprintRepository.save(
new Footprint(
member1,
new Location(0,0),
BEFORE,
null,
null,
LocalDateTime.now(),
false
)
);

float latitude = 0.0F;
float longitude = 0.0F;

SaveFootprintRequest request = new SaveFootprintRequest(latitude, longitude);

RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(HttpHeaders.AUTHORIZATION, member1.getId())
.body(request)
.when().patch("/footprints/walk-status")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.body("data.walkStatus", is(ONGOING.toString()));
}

@DisplayName("발자국 범위안에서 밖으로 나가면 산책후로 상태가 변한다 (200)")
@Test
void updateWalkStatus_toAfter(){
footprintRepository.save(
new Footprint(
member1,
new Location(0,0),
ONGOING,
LocalDateTime.now(),
null,
LocalDateTime.now().minusHours(1),
false
)
);

float latitude = 37.0F;
float longitude = 127.0F;

SaveFootprintRequest request = new SaveFootprintRequest(latitude, longitude);

RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(HttpHeaders.AUTHORIZATION, member1.getId())
.body(request)
.when().patch("/footprints/walk-status")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.body("data.walkStatus",is(AFTER.toString()));
}
}
Loading

0 comments on commit 0d65c2e

Please sign in to comment.