Skip to content

Commit

Permalink
Merge pull request #24 from Findy-org/feat/마커-삭제
Browse files Browse the repository at this point in the history
[FINDY-34] feat: 마커 삭제
  • Loading branch information
Hoya324 authored Nov 17, 2024
2 parents 802d7de + aa18a4a commit 8831532
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ public enum ErrorCode {
// 403 error,
AUTHENTICATION_EXCEPTION_ERROR(FORBIDDEN, "Authentication Content-Type not supported: %s"),
FORBIDDEN_BOOKMARK_ACCESS(FORBIDDEN, "해당 즐겨찾기에 접근할 권한이 없습니다."),
FORBIDDEN_MARKER_ACCESS(FORBIDDEN, "해당 마커에 접근할 권한이 없습니다."),

// 404 error
NOT_FOUND_EMAIL(NOT_FOUND, "해당 이메일이 존재하지 않습니다."),
NOT_FOUND_BOOKMARK_BY_ID(NOT_FOUND, "해당 id : %s의 즐겨찾기가 존재하지 않습니다."),
NOT_FOUND_MARKER_BY_ID(NOT_FOUND, "해당 id : %s의 마커가 존재하지 않습니다."),
NOT_FOUND_USER(NOT_FOUND, "해당 이메일을 가진 유저가 존재하지 않습니다."),
NOT_FOUND_USER_BY_ID(NOT_FOUND, "해당 id : %s를 가진 유저가 존재하지 않습니다."),
BAD_REQUEST_CATEGORY_NOT_FOUND_ERROR(NOT_FOUND, "해당 input : %s을 가진 카테고리가 없습니다."),
Expand Down
13 changes: 11 additions & 2 deletions src/main/java/org/findy/findy_be/marker/api/MarkerController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import org.findy.findy_be.common.dto.pagination.response.SliceResponse;
import org.findy.findy_be.common.meta.LoginUser;
import org.findy.findy_be.marker.api.swagger.MarkerAPIPresentation;
import org.findy.findy_be.marker.application.delete.DeleteMarker;
import org.findy.findy_be.marker.application.find.FindAllPagedMarkers;
import org.findy.findy_be.marker.application.register.RegisterMarker;
import org.findy.findy_be.marker.dto.request.RegisterSearchedMarkerRequest;
import org.findy.findy_be.place.dto.response.PlaceResponse;
import org.findy.findy_be.place.dto.response.MarkerPlaceResponse;
import org.findy.findy_be.user.domain.User;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,6 +29,7 @@ public class MarkerController implements MarkerAPIPresentation {

private final RegisterMarker registerMarker;
private final FindAllPagedMarkers findAllPagedMarkers;
private final DeleteMarker deleteMarker;

@PostMapping("/{bookmarkId}")
public void registerMarker(@LoginUser User user, @PathVariable("bookmarkId") Long bookmarkId,
Expand All @@ -35,8 +38,14 @@ public void registerMarker(@LoginUser User user, @PathVariable("bookmarkId") Lon
}

@GetMapping("/{bookmarkId}")
public SliceResponse<PlaceResponse> getMarkers(@LoginUser User user, @PathVariable("bookmarkId") Long bookmarkId,
public SliceResponse<MarkerPlaceResponse> getMarkers(@LoginUser User user,
@PathVariable("bookmarkId") Long bookmarkId,
@ModelAttribute PagedRequest request) {
return findAllPagedMarkers.invoke(user.getUserId(), bookmarkId, request.cursor(), request.size());
}

@DeleteMapping("/{id}")
public void deleteMarker(@LoginUser User user, @PathVariable("id") Long markerId) {
deleteMarker.invoke(user.getUserId(), markerId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.findy.findy_be.common.meta.CustomApiResponses;
import org.findy.findy_be.common.meta.LoginUser;
import org.findy.findy_be.marker.dto.request.RegisterSearchedMarkerRequest;
import org.findy.findy_be.place.dto.response.PlaceResponse;
import org.findy.findy_be.place.dto.response.MarkerPlaceResponse;
import org.findy.findy_be.user.domain.User;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -40,6 +40,17 @@ void registerMarker(@LoginUser User user, @PathVariable Long bookmarkId,
@CustomApiResponse(error = "IllegalArgumentException", status = 400, message = "잘못된 요청입니다.", description = "잘못된 쿼리 파라미터가 포함된 경우"),
@CustomApiResponse(error = "InternalServerError", status = 500, message = "내부 서버 오류가 발생했습니다.", description = "서버 내부에서 예기치 않은 오류가 발생한 경우")
})
SliceResponse<PlaceResponse> getMarkers(@LoginUser User user, @PathVariable("bookmarkId") Long bookmarkId,
SliceResponse<MarkerPlaceResponse> getMarkers(@LoginUser User user, @PathVariable("bookmarkId") Long bookmarkId,
@ModelAttribute PagedRequest request);

@Operation(summary = "마커 삭제", description = "마커를 삭제하는 API", responses = {
@ApiResponse(responseCode = "200", description = "마커 삭제 성공")
})
@CustomApiResponses({
@CustomApiResponse(error = "IllegalArgumentException", status = 400, message = "잘못된 요청입니다.", description = "잘못된 쿼리 파라미터가 포함된 경우"),
@CustomApiResponse(error = "ForbiddenAccessException", status = 403, message = "해당 마커에 접근할 권한이 없습니다.", description = "권한이 없는 유저가 즐겨찾기에 접근할 경우"),
@CustomApiResponse(error = "EntityNotFoundException", status = 404, message = "해당 id : {id}의 마커가 존재하지 않습니다.", description = "존재하지 않는 마커에 접근할 경우"),
@CustomApiResponse(error = "InternalServerError", status = 500, message = "내부 서버 오류가 발생했습니다.", description = "서버 내부에서 예기치 않은 오류가 발생한 경우")
})
void deleteMarker(@LoginUser User user, @PathVariable("id") Long markerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.findy.findy_be.marker.application.delete;

public interface DeleteMarker {

void invoke(String userId, Long markerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.findy.findy_be.marker.application.delete;

import static org.findy.findy_be.common.exception.ErrorCode.*;

import org.findy.findy_be.marker.domain.Marker;
import org.findy.findy_be.marker.repository.MarkerRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;

@Service
@Transactional
@RequiredArgsConstructor
public class DeleteMarkerService implements DeleteMarker {

private final MarkerRepository markerRepository;

public void invoke(String userId, Long markerId) {

Marker marker = markerRepository.findById(markerId)
.orElseThrow(
() -> new EntityNotFoundException(String.format(NOT_FOUND_MARKER_BY_ID.getMessage(), markerId)));
validateMarkerUser(userId, marker);
markerRepository.delete(marker);
}

private static void validateMarkerUser(final String userId, final Marker marker) {
if (!marker.getBookmark().getUser().getUserId().equals(userId)) {
throw new IllegalArgumentException(FORBIDDEN_MARKER_ACCESS.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.findy.findy_be.marker.application.find;

import org.findy.findy_be.common.dto.pagination.response.SliceResponse;
import org.findy.findy_be.place.dto.response.PlaceResponse;
import org.findy.findy_be.place.dto.response.MarkerPlaceResponse;

public interface FindAllPagedMarkers {
SliceResponse<PlaceResponse> invoke(final String userId, final Long bookmarkId, final Long cursor, final int size);
SliceResponse<MarkerPlaceResponse> invoke(final String userId, final Long bookmarkId, final Long cursor,
final int size);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import java.util.List;

import org.findy.findy_be.common.dto.pagination.response.SliceResponse;
import org.findy.findy_be.place.domain.Place;
import org.findy.findy_be.place.dto.response.PlaceResponse;
import org.findy.findy_be.place.dto.response.MarkerPlaceResponse;
import org.findy.findy_be.place.repository.PlaceRepository;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -21,15 +20,14 @@ public class FindAllPagedMarkersService implements FindAllPagedMarkers {

private final PlaceRepository placeRepository;

public SliceResponse<PlaceResponse> invoke(final String userId, final Long bookmarkId, final Long cursor,
public SliceResponse<MarkerPlaceResponse> invoke(final String userId, final Long bookmarkId, final Long cursor,
final int size) {
Pageable pageable = PageRequest.of(0, size);
Slice<Place> placeSlice = placeRepository.findPlacesByUserIdAndBookmarkId(userId, bookmarkId, pageable, cursor);
Slice<MarkerPlaceResponse> placeSlice = placeRepository.findPlacesByUserIdAndBookmarkId(userId, bookmarkId,
pageable, cursor);

List<PlaceResponse> data = placeSlice.stream()
.map(PlaceResponse::from)
.toList();
Long nextCursor = placeSlice.hasNext() ? data.get(data.size() - 1).placeId() : null;
List<MarkerPlaceResponse> data = placeSlice.getContent();
Long nextCursor = placeSlice.hasNext() ? data.get(data.size() - 1).markerId() : null;

return new SliceResponse<>(data, cursor != null ? cursor.intValue() : 0, size, placeSlice.hasNext(),
nextCursor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import lombok.Builder;

@Builder
public record PlaceResponse(
Long placeId,
public record MarkerPlaceResponse(
Long markerId,
String title,
String address,
Category category
) {
public static PlaceResponse from(final Place place) {
return PlaceResponse.builder()
.placeId(place.getId())
public static MarkerPlaceResponse of(final Long markerId, final Place place) {
return MarkerPlaceResponse.builder()
.markerId(markerId)
.title(place.getTitle())
.address(place.getAddress())
.category(place.getCategory())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.findy.findy_be.place.repository;

import org.findy.findy_be.place.domain.Place;
import org.findy.findy_be.place.dto.response.MarkerPlaceResponse;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

public interface PlaceRepositoryCustom {

Slice<Place> findPlacesByUserIdAndBookmarkId(String userId, Long bookmarkId, Pageable pageable, Long cursor);
Slice<MarkerPlaceResponse> findPlacesByUserIdAndBookmarkId(String userId, Long bookmarkId, Pageable pageable,
Long cursor);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package org.findy.findy_be.place.repository;

import java.util.List;
import java.util.Objects;

import org.findy.findy_be.bookmark.domain.QBookmark;
import org.findy.findy_be.marker.domain.QMarker;
import org.findy.findy_be.place.domain.Place;
import org.findy.findy_be.place.domain.QPlace;
import org.findy.findy_be.place.dto.response.MarkerPlaceResponse;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;

import com.querydsl.core.Tuple;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;
Expand All @@ -22,14 +24,14 @@ public class PlaceRepositoryCustomImpl implements PlaceRepositoryCustom {
private final JPAQueryFactory queryFactory;

@Override
public Slice<Place> findPlacesByUserIdAndBookmarkId(String userId, Long bookmarkId, Pageable pageable,
public Slice<MarkerPlaceResponse> findPlacesByUserIdAndBookmarkId(String userId, Long bookmarkId, Pageable pageable,
Long cursor) {
QBookmark bookmark = QBookmark.bookmark;
QMarker marker = QMarker.marker;
QPlace place = QPlace.place;

List<Place> places = queryFactory
.select(place)
List<Tuple> results = queryFactory
.select(marker.id, place)
.from(bookmark)
.join(bookmark.markers, marker)
.join(marker.place, place)
Expand All @@ -40,11 +42,18 @@ public Slice<Place> findPlacesByUserIdAndBookmarkId(String userId, Long bookmark
.limit(pageable.getPageSize() + 1)
.fetch();

boolean hasNext = places.size() > pageable.getPageSize();
List<MarkerPlaceResponse> responses = results.stream()
.map(tuple -> MarkerPlaceResponse.of(
tuple.get(marker.id),
Objects.requireNonNull(tuple.get(place))
))
.toList();

boolean hasNext = responses.size() > pageable.getPageSize();
if (hasNext) {
places = places.subList(0, pageable.getPageSize());
responses = responses.subList(0, pageable.getPageSize());
}

return new SliceImpl<>(places, pageable, hasNext);
return new SliceImpl<>(responses, pageable, hasNext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.assertj.core.api.Assertions;
import org.findy.findy_be.auth.oauth.domain.SocialProviderType;
import org.findy.findy_be.auth.oauth.domain.UserPrincipal;
import org.findy.findy_be.bookmark.domain.Bookmark;
Expand All @@ -25,6 +27,7 @@
import org.findy.findy_be.user.domain.RoleType;
import org.findy.findy_be.user.domain.User;
import org.findy.findy_be.user.repository.UserRepository;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -198,6 +201,21 @@ void setUp() {
.andExpect(jsonPath("$.nextCursor").doesNotExist());
}

@DisplayName("[성공] 마커 삭제 요청")
@Test
void 마커_삭제_요청() throws Exception {
// given
initPlacesForBookmark(testBookmark, 1);
Long markerId = markerRepository.findAll().get(0).getId();

// when
DeleteMarker(markerId);

// then
Optional<Marker> resultMarker = markerRepository.findById(markerId);
Assertions.assertThat(resultMarker.isEmpty()).isTrue();
}

private ResultActions GetBookmarks(final Long bookmarkId, final PagedRequest pagedRequest) throws Exception {
return mvc.perform((get("/api/markers/{bookmarkId}", bookmarkId)
.param("cursor", pagedRequest.cursor() == null ? "" : pagedRequest.cursor().toString())
Expand All @@ -215,6 +233,11 @@ private ResultActions PostSearchedPlace(final Long bookmarkId,
.andDo(print());
}

private @NotNull ResultActions DeleteMarker(Long id) throws Exception {
return mvc.perform(delete("/api/markers/{id}", id))
.andDo(print());
}

private void initPlacesForBookmark(Bookmark bookmark, int count) {
List<Place> places = IntStream.range(1, count + 1)
.mapToObj(i -> new RegisterSearchedMarkerRequest(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.findy.findy_be.marker.application.delete;

import static org.findy.findy_be.common.exception.ErrorCode.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.util.Optional;

import org.findy.findy_be.bookmark.domain.Bookmark;
import org.findy.findy_be.common.MockTest;
import org.findy.findy_be.marker.domain.Marker;
import org.findy.findy_be.marker.repository.MarkerRepository;
import org.findy.findy_be.place.domain.Place;
import org.findy.findy_be.user.domain.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

class DeleteMarkerServiceTest extends MockTest {

@Mock
private MarkerRepository markerRepository;

@InjectMocks
private DeleteMarkerService deleteMarkerService;

private User testUser;
private Bookmark bookmark;
private Place place;
private Marker marker;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);

testUser = mock(User.class);
when(testUser.getUserId()).thenReturn("N49sfgdahdKz_fp-223424er1N3D6kd");

bookmark = mock(Bookmark.class);
when(bookmark.getUser()).thenReturn(testUser);

place = mock(Place.class);

marker = Marker.createForCustomBookmark(bookmark, place);
}

@DisplayName("[성공] 마커 삭제")
@Test
public void 마커_삭제() throws Exception {
// given
Long markerId = 1L;
String userId = testUser.getUserId();
when(markerRepository.findById(markerId)).thenReturn(Optional.of(marker));

// when
deleteMarkerService.invoke(userId, markerId);

// then
verify(markerRepository, times(1)).delete(marker);
}

@DisplayName("[실패] 다른 유저의 마커 삭제 시도")
@Test
void 마커_삭제_실패_다른유저() {
// given
Long markerId = 1L;
String userId = "differentUserId";
when(markerRepository.findById(markerId)).thenReturn(Optional.of(marker));

// when & then
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> deleteMarkerService.invoke(userId, markerId));

assertEquals(FORBIDDEN_MARKER_ACCESS.getMessage(), exception.getMessage());
}
}
Loading

0 comments on commit 8831532

Please sign in to comment.