-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from Solucitation/refactor/search-history
Refactor/search history
- Loading branch information
Showing
8 changed files
with
355 additions
and
0 deletions.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
...java/com/solucitation/midpoint_backend/domain/history2/api/SearchHistoryControllerV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.api; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.solucitation.midpoint_backend.domain.history2.dto.PlaceDtoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.dto.SearchHistoryRequestDtoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.dto.SearchHistoryResponseDtoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.service.SearchHistoryServiceV2; | ||
import com.solucitation.midpoint_backend.domain.member.dto.ValidationErrorResponse; | ||
import com.solucitation.midpoint_backend.domain.member.entity.Member; | ||
import com.solucitation.midpoint_backend.domain.member.service.MemberService; | ||
import jakarta.validation.ConstraintViolation; | ||
import jakarta.validation.Validator; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
||
@Slf4j | ||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/search-history-v2") | ||
public class SearchHistoryControllerV2 { | ||
private final MemberService memberService; | ||
private final Validator validator; | ||
private final SearchHistoryServiceV2 searchHistoryService; | ||
|
||
/** | ||
* | ||
* @param authentication 인증 정보 | ||
* @param request 검색 기록 저장에 필요한 정보 (동 정보, 장소 리스트) | ||
* @return 장소를 성공적으로 저장하면 201 CREATED를 반환합니다. | ||
* 로그인을 하지 않고 시도 시 401 UNAUTHORIZED를 반환합니다. | ||
* 저장하려는 장소 정보에 문제가 있을 때 (필드가 공백이거나 null일 경우) 400 BAD_REQUEST를 반환합니다. | ||
* 기타 사유로 저장 실패 시 500 INTERNAL_SERVER_ERROR를 반환합니다. | ||
*/ | ||
@PostMapping("") | ||
public ResponseEntity<Object> saveHistory(Authentication authentication, | ||
@RequestBody SearchHistoryRequestDtoV2 request) { | ||
try { | ||
String neighborhood = request.getNeighborhood(); | ||
List<PlaceDtoV2> searchHistoryRequestDtos = request.getHistoryDto(); | ||
|
||
System.out.println("ssearchHistoryRequestDtos.size()= " +searchHistoryRequestDtos.size()); | ||
for (PlaceDtoV2 searchHistoryRequestDto : searchHistoryRequestDtos) { | ||
System.out.println("searchHistoryRequestDto = " + searchHistoryRequestDto.getPlaceId()); | ||
} | ||
|
||
// neighborhood 검증 | ||
if (neighborhood == null || neighborhood.trim().isEmpty()) { | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST) | ||
.body(Map.of("error", "EMPTY_FIELD", "message", "동 정보가 누락되었습니다.")); | ||
} | ||
|
||
// 인증 검증 | ||
if (authentication == null || !authentication.isAuthenticated()) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||
.body(Map.of("error", "UNAUTHORIZED", "message", "해당 서비스를 이용하기 위해서는 로그인이 필요합니다.")); | ||
} | ||
|
||
String memberEmail = authentication.getName(); | ||
Member member = memberService.getMemberByEmail(memberEmail); | ||
|
||
// 리스트 내의 모든 요소에 대한 검증 수행 | ||
for (PlaceDtoV2 place : searchHistoryRequestDtos) { | ||
Set<ConstraintViolation<PlaceDtoV2>> violations = validator.validate(place); | ||
if (!violations.isEmpty()) { | ||
List<ValidationErrorResponse.FieldError> fieldErrors = violations.stream() | ||
.map(violation -> new ValidationErrorResponse.FieldError(violation.getPropertyPath().toString(), violation.getMessage())) | ||
.collect(Collectors.toList()); | ||
ValidationErrorResponse errorResponse = new ValidationErrorResponse(fieldErrors); | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse); | ||
} | ||
} | ||
|
||
// 데이터 저장 | ||
searchHistoryService.save(neighborhood, memberEmail, searchHistoryRequestDtos); | ||
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "장소를 저장하였습니다.")); | ||
|
||
} catch (IllegalArgumentException e) { | ||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); | ||
} catch (RuntimeException e) { | ||
return ResponseEntity.status(HttpStatus.NOT_FOUND) | ||
.body(Map.of("error", "USER_NOT_FOUND", "message", "사용자를 찾을 수 없습니다.")); | ||
} catch (Exception e) { | ||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
.body(Map.of("error", e.getMessage(), "message", "장소 저장 중 오류가 발생하였습니다.")); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* 사용자가 지금까지 저장한 검색 정보를 전부 최신순부터 가져옵니다. | ||
* | ||
* @param authentication 인증 정보 | ||
* @return 검색 기록 조회가 성공하면 200 OK와 결과 리스트를 반환합니다. | ||
* 로그인을 하지 않고 시도 시 401 UNAUTHORIZED를 반환합니다. | ||
* 사용자를 찾을 수 없는 경우 404 NOT_FOUND를 반환합니다. | ||
* 기타 사유로 저장 실패 시 500 INTERNAL_SERVER_ERROR를 반환합니다. | ||
*/ | ||
@GetMapping("") | ||
public ResponseEntity<Object> getHistory(Authentication authentication) { | ||
try { | ||
if (authentication == null || !authentication.isAuthenticated()) { | ||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||
.body(Map.of("error", "UNAUTHORIZED", "message", "해당 서비스를 이용하기 위해서는 로그인이 필요합니다.")); | ||
} | ||
|
||
String memberEmail = authentication.getName(); | ||
Member member = memberService.getMemberByEmail(memberEmail); | ||
if (member == null) { | ||
return ResponseEntity.status(HttpStatus.NOT_FOUND) | ||
.body(Map.of("error", "USER_NOT_FOUND", "message", "사용자를 찾을 수 없습니다.")); | ||
} | ||
|
||
List<SearchHistoryResponseDtoV2> searchHistoryResponseDtos = searchHistoryService.getHistory(member); | ||
return ResponseEntity.ok(searchHistoryResponseDtos); | ||
} catch (Exception e) { | ||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
.body(Map.of("error", e.getMessage(), "message", "검색 기록 조회 중 오류가 발생하였습니다.")); | ||
} | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/com/solucitation/midpoint_backend/domain/history2/dto/PlaceDtoV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.dto; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class PlaceDtoV2 { | ||
@NotBlank(message = "장소ID가 누락되었습니다.") | ||
private String placeId; // 장소의 고유 ID | ||
|
||
@NotBlank(message = "장소명이 누락되었습니다.") | ||
private String placeName; // 장소의 이름 | ||
|
||
@NotBlank(message = "주소가 누락되었습니다.") | ||
private String placeAddress; // 장소의 주소 | ||
|
||
private String imageUrl; // 장소 이미지 | ||
} |
19 changes: 19 additions & 0 deletions
19
...java/com/solucitation/midpoint_backend/domain/history2/dto/SearchHistoryRequestDtoV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.dto; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class SearchHistoryRequestDtoV2 { | ||
@NotBlank | ||
private String neighborhood; | ||
|
||
private List<PlaceDtoV2> historyDto = new ArrayList<>(); | ||
} |
18 changes: 18 additions & 0 deletions
18
...ava/com/solucitation/midpoint_backend/domain/history2/dto/SearchHistoryResponseDtoV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class SearchHistoryResponseDtoV2 { | ||
private String neighborhood; | ||
private LocalDateTime serachTime; | ||
private List<PlaceDtoV2> places = new ArrayList<>(); | ||
} |
33 changes: 33 additions & 0 deletions
33
src/main/java/com/solucitation/midpoint_backend/domain/history2/entity/PlaceInfoV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.entity; | ||
|
||
import jakarta.persistence.*; | ||
import lombok.*; | ||
|
||
@Entity | ||
@Table(name = "place_info") | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@AllArgsConstructor | ||
@Builder | ||
public class PlaceInfoV2 { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(name = "place_info_id") | ||
private Long id; | ||
|
||
@Column(name="place_id", nullable = false) | ||
private String placeId; | ||
|
||
@ManyToOne | ||
@JoinColumn(name = "search_history_id", nullable = false) | ||
private SearchHistoryV2 searchHistory; | ||
|
||
@Column(name = "place_name", nullable = false) | ||
private String name; | ||
|
||
@Column(name = "place_address", nullable = false) | ||
private String address; | ||
|
||
@Column(name="place_image_url", nullable = false) | ||
private String imageUrl; | ||
} |
40 changes: 40 additions & 0 deletions
40
src/main/java/com/solucitation/midpoint_backend/domain/history2/entity/SearchHistoryV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.entity; | ||
|
||
import com.solucitation.midpoint_backend.domain.member.entity.Member; | ||
import jakarta.persistence.*; | ||
import lombok.*; | ||
import org.hibernate.annotations.CreationTimestamp; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static jakarta.persistence.FetchType.LAZY; | ||
|
||
@Entity | ||
@Table(name = "search_history") | ||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@AllArgsConstructor | ||
@Builder | ||
public class SearchHistoryV2 { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(name = "search_history_id") | ||
private Long id; | ||
|
||
@Column(name="neighborhood", nullable = false) | ||
private String neighborhood; // 동 정보 ex. 청파동 | ||
|
||
@ManyToOne(fetch = LAZY) | ||
@JoinColumn(name = "member_id", nullable = false) | ||
private Member member; | ||
|
||
@CreationTimestamp | ||
@Column(name = "search_date", nullable = false, updatable = false) | ||
private LocalDateTime searchDate; | ||
|
||
@OneToMany(mappedBy = "searchHistory", fetch = LAZY, cascade = CascadeType.ALL, orphanRemoval = true) | ||
@Builder.Default | ||
private List<PlaceInfoV2> placeList = new ArrayList<>(); | ||
} |
13 changes: 13 additions & 0 deletions
13
...m/solucitation/midpoint_backend/domain/history2/repository/SearchHistoryRepositoryV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.repository; | ||
|
||
import com.solucitation.midpoint_backend.domain.history2.entity.SearchHistoryV2; | ||
import com.solucitation.midpoint_backend.domain.member.entity.Member; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import java.util.List; | ||
|
||
@Repository | ||
public interface SearchHistoryRepositoryV2 extends JpaRepository<SearchHistoryV2, Long> { | ||
List<SearchHistoryV2> findByMemberOrderBySearchDateDesc(Member member); | ||
} |
78 changes: 78 additions & 0 deletions
78
...ava/com/solucitation/midpoint_backend/domain/history2/service/SearchHistoryServiceV2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.solucitation.midpoint_backend.domain.history2.service; | ||
|
||
import com.solucitation.midpoint_backend.domain.history2.dto.PlaceDtoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.dto.SearchHistoryRequestDtoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.dto.SearchHistoryResponseDtoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.entity.PlaceInfoV2; | ||
import com.solucitation.midpoint_backend.domain.history2.entity.SearchHistoryV2; | ||
import com.solucitation.midpoint_backend.domain.history2.repository.SearchHistoryRepositoryV2; | ||
import com.solucitation.midpoint_backend.domain.member.entity.Member; | ||
import com.solucitation.midpoint_backend.domain.member.service.MemberService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class SearchHistoryServiceV2 { | ||
@Value("${cloud.aws.s3.bucket}") | ||
private String bucket; | ||
|
||
private final String defaultImageUrl = String.format("https://%s.s3.%s.amazonaws.com/%s", bucket, "ap-northeast-2", "place_default_image.png"); | ||
private final SearchHistoryRepositoryV2 searchHistoryRepository; | ||
private final MemberService memberService; | ||
|
||
@Transactional | ||
public void save(String neighborhood, String memberEmail, List<PlaceDtoV2> placeDtos) { | ||
Member member = memberService.getMemberByEmail(memberEmail); | ||
|
||
SearchHistoryV2 searchHistory = SearchHistoryV2.builder() | ||
.member(member) | ||
.neighborhood(neighborhood) | ||
.build(); | ||
|
||
System.out.println("placeDtos.size() = " + placeDtos.size()); | ||
for (PlaceDtoV2 dto : placeDtos) { | ||
String imageUrl = dto.getImageUrl(); | ||
if (imageUrl == null || imageUrl.trim().isEmpty()) { // 이미지 url이 없거나 공백 | ||
imageUrl = defaultImageUrl; | ||
} | ||
|
||
PlaceInfoV2 placeInfo = PlaceInfoV2.builder() | ||
.name(dto.getPlaceName()) | ||
.placeId(dto.getPlaceId()) | ||
.address(dto.getPlaceAddress()) | ||
.imageUrl(imageUrl) | ||
.searchHistory(searchHistory) // searchHistory 필드 설정 | ||
.build(); | ||
searchHistory.getPlaceList().add(placeInfo); | ||
System.out.println("placeInfo = " + placeInfo); | ||
} | ||
searchHistoryRepository.save(searchHistory); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public List<SearchHistoryResponseDtoV2> getHistory(Member member) { | ||
List<SearchHistoryV2> histories = searchHistoryRepository.findByMemberOrderBySearchDateDesc(member); | ||
|
||
return histories.stream() | ||
.map(history -> new SearchHistoryResponseDtoV2( | ||
history.getNeighborhood(), | ||
history.getSearchDate(), | ||
history.getPlaceList().stream() | ||
.map(placeInfo -> new PlaceDtoV2( | ||
placeInfo.getPlaceId(), | ||
placeInfo.getName(), | ||
placeInfo.getAddress(), | ||
placeInfo.getImageUrl() | ||
)) | ||
.collect(Collectors.toList()) | ||
)) | ||
.collect(Collectors.toList()); | ||
} | ||
} |