Skip to content

Commit

Permalink
feat: 검수할 장소 등록/ 조회 API 구현 (#404)
Browse files Browse the repository at this point in the history
* fix : ManagerAuthInterceptor 디코딩 수정

* feat: 인터셉터에 검수중 장소 조회 path 등록

* feat: 검수할 장소 등록 기능 추가

* feat: 검수할 장소 등록 API 구현

* feat: 검수할 장소 목록 조회 기능 구현

* feat: 검수할 장소 목록 조회 API 구현

* fix: 서비스 객체 빈 등록 및 인터셉터 추가

* test: TemporaryPlaceBuilder 및 TemporaryPlaceFixture 작성

* test: ManagerAuthInterceptorTest 디코딩 문제 수정

* feat: TemporaryPlace예외 처리 추가 및 적용

* test: 검수할 장소 등록 테스트 추가

* test: 필요없는 테스트 삭제

* test: 20미터에 이미 등록된 목적지가 있다면 예외응답 테스트

* refactor: 필요없는 메서드 삭제

* refactor: PlaceCheckService 패키지 변경 및 예외 수정

* test: 검수할 장소 목록 조회 기능 테스트

* refactor: 콘솔 로깅 디버그 레벨로 변경

* refactor: TemporaryPlace관련 조회 쿼리 n+1문제 리팩터링

* refactor: TemporaryPlaceResponse에 플레이어 정보 추가

* chore: 개행 제거

* test: TemporaryPlaceControllerTest 수정

* refactor: 서비스 메서드명 변경

* refactor: TemporaryPlaceResponse 필드명 변경

* refactor: 검수할 장소 조회 오래된 순으로 정렬 순서 변경

---------

Co-authored-by: dooboocookie <[email protected]>
  • Loading branch information
2 people authored and kokodak committed Oct 5, 2023
1 parent eb5e8a9 commit d7bb495
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 36 deletions.
3 changes: 0 additions & 3 deletions .idea/2023-naaga.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ private HandlerInterceptor mapAuthInterceptor() {
.excludeRequestPattern("/**/*.ico")
.excludeRequestPattern("/ranks")
.excludeRequestPattern("/**", HttpMethod.OPTIONS)
.excludeRequestPattern("/temporary-places", HttpMethod.GET)
.excludeRequestPattern("/places", HttpMethod.POST)
.excludeRequestPattern("/temporary-places/**", HttpMethod.DELETE);
}

private HandlerInterceptor mapManagerAuthInterceptor() {
return new RequestMatcherInterceptor(managerAuthInterceptor)
.includeRequestPattern("/temporary-places", HttpMethod.GET)
.includeRequestPattern("/places", HttpMethod.POST)
.includeRequestPattern("/temporary-places/**", HttpMethod.DELETE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import com.now.naaga.place.exception.PlaceException;
import com.now.naaga.place.exception.PlaceExceptionType;
import com.now.naaga.place.persistence.repository.PlaceRepository;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Transactional
@Service
public class PlaceCheckService {
Expand All @@ -20,7 +21,7 @@ public PlaceCheckService(final PlaceRepository placeRepository) {
@Transactional(readOnly = true)
public void checkOtherPlaceNearby(final Position position) {
List<Place> places = placeRepository.findPlaceByPositionAndDistance(position, 0.02);
if (places.size() > 0) {
if (!places.isEmpty()) {
throw new PlaceException(PlaceExceptionType.ALREADY_EXIST_NEARBY);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,4 @@ public ResponseEntity<PlaceResponse> createPlace(@RequestBody final CreatePlaceR
.location(URI.create("/places/" + place.getId()))
.body(response);
}

// TODO: 2023/10/03 장소 검수 API 구현 이후 삭제 예정
// @PostMapping
// public ResponseEntity<PlaceResponse> createPlace(@Auth final PlayerRequest playerRequest,
// @ModelAttribute final CreatePlaceRequest createPlaceRequest) {
// CreatePlaceCommand createPlaceCommand = CreatePlaceCommand.of(playerRequest, createPlaceRequest);
// final Place savedPlace = placeService.createPlace(createPlaceCommand);
// final PlaceResponse response = PlaceResponse.from(savedPlace);
// return ResponseEntity
// .status(HttpStatus.CREATED)
// .location(URI.create("/places/" + savedPlace.getId()))
// .body(response);
// }
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,65 @@
package com.now.naaga.temporaryplace.application;

import com.now.naaga.common.infrastructure.FileManager;
import com.now.naaga.place.domain.Position;
import com.now.naaga.player.application.PlayerService;
import com.now.naaga.player.domain.Player;
import com.now.naaga.temporaryplace.application.dto.CreateTemporaryPlaceCommand;
import com.now.naaga.temporaryplace.domain.TemporaryPlace;
import com.now.naaga.temporaryplace.repository.TemporaryPlaceRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.Comparator;
import java.util.List;

@Transactional
@Service
public class TemporaryPlaceService {

private final TemporaryPlaceRepository temporaryPlaceRepository;

public TemporaryPlaceService(final TemporaryPlaceRepository temporaryPlaceRepository) {
private final PlayerService playerService;

private final FileManager<MultipartFile> fileManager;

public TemporaryPlaceService(final TemporaryPlaceRepository temporaryPlaceRepository,
final PlayerService playerService,
final FileManager<MultipartFile> fileManager) {
this.temporaryPlaceRepository = temporaryPlaceRepository;
this.playerService = playerService;
this.fileManager = fileManager;
}

public TemporaryPlace createTemporaryPlace(final CreateTemporaryPlaceCommand createTemporaryPlaceCommand) {
final Position position = createTemporaryPlaceCommand.position();
final File uploadPath = fileManager.save(createTemporaryPlaceCommand.imageFile());
try {
final Long playerId = createTemporaryPlaceCommand.playerId();
final Player registeredPlayer = playerService.findPlayerById(playerId);
final TemporaryPlace temporaryPlace = new TemporaryPlace(
createTemporaryPlaceCommand.name(),
createTemporaryPlaceCommand.description(),
position,
fileManager.convertToUrlPath(uploadPath),
registeredPlayer);
return temporaryPlaceRepository.save(temporaryPlace);
} catch (final RuntimeException exception) {
uploadPath.delete();
throw exception;
}
}

public void deleteById(final Long id) {
temporaryPlaceRepository.deleteById(id);
}

@Transactional(readOnly = true)
public List<TemporaryPlace> findAllTemporaryPlace() {
final List<TemporaryPlace> temporaryPlaces = temporaryPlaceRepository.findAll();
temporaryPlaces.sort(Comparator.comparing(TemporaryPlace::getCreatedAt).reversed());
return temporaryPlaces;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.now.naaga.temporaryplace.application.dto;

import com.now.naaga.place.domain.Position;
import com.now.naaga.player.presentation.dto.PlayerRequest;
import com.now.naaga.temporaryplace.presentation.dto.CreateTemporaryPlaceRequest;
import org.springframework.web.multipart.MultipartFile;

public record CreateTemporaryPlaceCommand(Long playerId,
String name,
String description,
Position position,
MultipartFile imageFile) {

public static CreateTemporaryPlaceCommand of(final PlayerRequest playerRequest,
final CreateTemporaryPlaceRequest createTemporaryPlaceRequest) {
Position position = Position.of(createTemporaryPlaceRequest.latitude(), createTemporaryPlaceRequest.longitude());
return new CreateTemporaryPlaceCommand(
playerRequest.playerId(),
createTemporaryPlaceRequest.name(),
createTemporaryPlaceRequest.description(),
position,
createTemporaryPlaceRequest.imageFile());
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package com.now.naaga.temporaryplace.presentation;

import com.now.naaga.auth.presentation.annotation.Auth;
import com.now.naaga.player.presentation.dto.PlayerRequest;
import com.now.naaga.temporaryplace.application.TemporaryPlaceService;
import com.now.naaga.temporaryplace.application.dto.CreateTemporaryPlaceCommand;
import com.now.naaga.temporaryplace.domain.TemporaryPlace;
import com.now.naaga.temporaryplace.presentation.dto.CreateTemporaryPlaceRequest;
import com.now.naaga.temporaryplace.presentation.dto.TemporaryPlaceResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.net.URI;
import java.util.List;

@RequestMapping("/temporary-places")
@RestController
Expand All @@ -18,6 +24,27 @@ public TemporaryPlaceController(final TemporaryPlaceService temporaryPlaceServic
this.temporaryPlaceService = temporaryPlaceService;
}

@GetMapping
public ResponseEntity<List<TemporaryPlaceResponse>> findAllTemporaryPlace() {
final List<TemporaryPlace> temporaryPlaces = temporaryPlaceService.findAllTemporaryPlace();
final List<TemporaryPlaceResponse> response = TemporaryPlaceResponse.convertToResponses(temporaryPlaces);
return ResponseEntity
.status(HttpStatus.OK)
.body(response);
}

@PostMapping
public ResponseEntity<TemporaryPlaceResponse> createTemporaryPlace(@Auth final PlayerRequest playerRequest,
@ModelAttribute final CreateTemporaryPlaceRequest createTemporaryPlaceRequest) {
final CreateTemporaryPlaceCommand createTemporaryPlaceCommand = CreateTemporaryPlaceCommand.of(playerRequest, createTemporaryPlaceRequest);
final TemporaryPlace temporaryPlace = temporaryPlaceService.createTemporaryPlace(createTemporaryPlaceCommand);
final TemporaryPlaceResponse response = TemporaryPlaceResponse.from(temporaryPlace);
return ResponseEntity
.status(HttpStatus.CREATED)
.location(URI.create("/temporary-places/" + temporaryPlace.getId()))
.body(response);
}

@DeleteMapping("/{temporaryPlaceId}")
public ResponseEntity<Void> deleteTemporaryPlace(@PathVariable final Long temporaryPlaceId) {
temporaryPlaceService.deleteById(temporaryPlaceId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.now.naaga.temporaryplace.presentation.dto;

import org.springframework.web.multipart.MultipartFile;

public record CreateTemporaryPlaceRequest(String name,
String description,
Double latitude,
Double longitude,
MultipartFile imageFile) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.now.naaga.temporaryplace.presentation.dto;

import com.now.naaga.game.presentation.dto.CoordinateResponse;
import com.now.naaga.temporaryplace.domain.TemporaryPlace;

import java.util.List;
import java.util.stream.Collectors;

public record TemporaryPlaceResponse(Long id,
String name,
CoordinateResponse coordinate,
String imageUrl,
String description,
Long registeredPlayerId

) {

public static TemporaryPlaceResponse from(final TemporaryPlace savedTemporaryPlace) {
CoordinateResponse coordinateResponse = CoordinateResponse.of(savedTemporaryPlace.getPosition());
return new TemporaryPlaceResponse(
savedTemporaryPlace.getId(),
savedTemporaryPlace.getName(),
coordinateResponse,
savedTemporaryPlace.getImageUrl(),
savedTemporaryPlace.getDescription(),
savedTemporaryPlace.getRegisteredPlayer().getId()
);
}

public static List<TemporaryPlaceResponse> convertToResponses(final List<TemporaryPlace> temporaryPlaces) {
return temporaryPlaces.stream()
.map(TemporaryPlaceResponse::from)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@

import com.now.naaga.temporaryplace.domain.TemporaryPlace;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface TemporaryPlaceRepository extends JpaRepository<TemporaryPlace, Long> {

@Override
@Query("SELECT tp FROM TemporaryPlace tp JOIN FETCH tp.registeredPlayer")
List<TemporaryPlace> findAll();
}
4 changes: 2 additions & 2 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ logging:

spring:
config:
import: classpath:security/application-local.yml
import: classpath:security/application-prod.yml
activate:
on-profile: local
on-profile: prod

---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.now.naaga.common.fixture;

import com.now.naaga.temporaryplace.domain.TemporaryPlace;

import static com.now.naaga.common.fixture.PlayerFixture.PLAYER;
import static com.now.naaga.common.fixture.PositionFixture.잠실_루터회관_정문_좌표;

public class TemporaryPlaceFixture {

public static final String NAME = "temp_place_name";

public static final String DESCRIPTION = "temp_place_description";

public static final String IMAGE_URL = "temp_place_imageUrl";

public static TemporaryPlace TEMPORARY_PLACE() {
return new TemporaryPlace(NAME, DESCRIPTION, 잠실_루터회관_정문_좌표, IMAGE_URL, PLAYER());
}
}
Loading

0 comments on commit d7bb495

Please sign in to comment.