-
Notifications
You must be signed in to change notification settings - Fork 1
[REFACTOR] 유저 장소 저장 관련 api 개선 및 리뷰 등록 칼럼 변경 #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e9022b1
a547bc6
b02143d
472715d
d83625c
e1d0611
bd998c7
4ef97f0
4ca773c
fc54965
bc4e2fc
a21f386
9ba53f5
22e27a9
479c238
be3014f
aed685b
a57f7ec
595590f
35e49b0
8e11d3c
c87b8a3
21f0cdc
9c73722
bbb4827
c45ad40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,18 @@ | ||
| package com.wayble.server.user.controller; | ||
|
|
||
| import com.wayble.server.common.exception.ApplicationException; | ||
|
|
||
| import com.wayble.server.common.response.CommonResponse; | ||
| import com.wayble.server.user.dto.UserPlaceListResponseDto; | ||
| import com.wayble.server.user.dto.UserPlaceRemoveRequestDto; | ||
| import com.wayble.server.user.dto.UserPlaceRequestDto; | ||
| import com.wayble.server.user.exception.UserErrorCase; | ||
| import com.wayble.server.user.dto.UserPlaceSummaryDto; | ||
| import com.wayble.server.user.service.UserPlaceService; | ||
| import com.wayble.server.wayblezone.dto.WaybleZoneListResponseDto; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
@@ -41,19 +43,80 @@ public CommonResponse<String> saveUserPlace( | |
| } | ||
|
|
||
| @GetMapping | ||
| @Operation(summary = "내 장소 리스트 요약 조회", description = "장소 관련 목록(리스트)만 반환합니다(개수 포함).") | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "조회 성공"), | ||
| @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없음"), | ||
| @ApiResponse(responseCode = "403", description = "권한이 없습니다.") | ||
| }) | ||
| public CommonResponse<List<UserPlaceSummaryDto>> getMyPlaceSummaries( | ||
| @RequestParam(name = "sort", defaultValue = "latest") String sort | ||
| ) { | ||
| Long userId = extractUserId(); | ||
| List<UserPlaceSummaryDto> summaries = userPlaceService.getMyPlaceSummaries(userId, sort); | ||
| return CommonResponse.success(summaries); | ||
| } | ||
|
|
||
|
|
||
| @GetMapping("/zones") | ||
| @Operation(summary = "특정 장소 내 웨이블존 목록 조회(페이징)", | ||
| description = "placeId로 해당 장소 내부의 웨이블존 카드 목록을 반환합니다. (page는 1부터 시작.)") | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "조회 성공"), | ||
| @ApiResponse(responseCode = "404", description = "유저/장소를 찾을 수 없음"), | ||
| @ApiResponse(responseCode = "403", description = "권한이 없습니다.") | ||
| }) | ||
| public CommonResponse<Page<WaybleZoneListResponseDto>> getZonesInPlace( | ||
| @RequestParam Long placeId, | ||
| @RequestParam(defaultValue = "1") Integer page, | ||
| @RequestParam(defaultValue = "20") Integer size | ||
| ) { | ||
| Long userId = extractUserId(); | ||
| Page<WaybleZoneListResponseDto> zones = userPlaceService.getZonesInPlace(userId, placeId, page, size); | ||
| return CommonResponse.success(zones); | ||
| } | ||
|
|
||
| @DeleteMapping | ||
| @Operation( | ||
| summary = "내가 저장한 장소 목록 조회", | ||
| description = "유저가 저장한 모든 장소 및 해당 웨이블존 정보를 조회합니다." | ||
| summary = "장소에서 웨이블존 제거", | ||
| description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다." | ||
| ) | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "장소 목록 조회 성공"), | ||
| @ApiResponse(responseCode = "404", description = "해당 유저를 찾을 수 없음"), | ||
| @ApiResponse(responseCode = "200", description = "제거 성공"), | ||
| @ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"), | ||
| @ApiResponse(responseCode = "403", description = "권한이 없습니다.") | ||
| }) | ||
| public CommonResponse<List<UserPlaceListResponseDto>> getUserPlaces( | ||
| ) { | ||
| Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); | ||
| List<UserPlaceListResponseDto> places = userPlaceService.getUserPlaces(userId); | ||
| return CommonResponse.success(places); | ||
| public CommonResponse<String> removeZoneFromPlace(@RequestBody @Valid UserPlaceRemoveRequestDto request) { | ||
| Long userId = extractUserId(); | ||
| userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); | ||
| return CommonResponse.success("제거되었습니다."); | ||
| } | ||
|
|
||
|
|
||
| // SecurityContext에서 userId 추출하는 로직 | ||
| private Long extractUserId() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ReviewController랑 여기에 모두 동일한 로직을 넣은 이유도 궁금합니다!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 작성한 pr에 대한 코드를 짤때 생각보다 api 자체를 많이 바꿔야해서 개발 후에 테스트에 좀 더 신경을 썼었는데, 제가 테스트를 여러번 할때에 Authentication.getPrincipal() 타입이 일관되게 들어오지 않는것 같더라구요. 그렇게된다면 컨트롤러에서 에러가 날수도 있다고 생각해서 아예 컨트롤러쪽에 토큰을 추출하는 메서드를 추가하였습니다. 하지만 말씀해주신것처럼 토큰 정책이 바뀌면 컨트롤러마다 수정해야 하는 리스크가 있다는것에 동의합니다! 중복 제거 및 공통화에 대해서 리팩토링 해보겠습니다 감사합니다 ㅎㅎ |
||
| Authentication auth = SecurityContextHolder.getContext().getAuthentication(); | ||
| if (auth == null) { | ||
| throw new IllegalStateException("인증 정보가 없습니다."); | ||
| } | ||
|
|
||
| Object p = auth.getPrincipal(); | ||
|
|
||
| if (p instanceof Long l) { return l; } | ||
| if (p instanceof Integer i) { return i.longValue(); } | ||
| if (p instanceof String s) { | ||
| try { | ||
| return Long.parseLong(s); | ||
| } catch (NumberFormatException e) { | ||
| // 숫자 변환 실패 시 출력 | ||
| System.err.println("Principal 문자열을 Long으로 변환할 수 없습니다: " + s); | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| return Long.parseLong(auth.getName()); | ||
| } catch (Exception e) { | ||
| throw new IllegalStateException("인증 정보에서 userId를 추출할 수 없습니다. Principal=" + p, e); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package com.wayble.server.user.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record UserPlaceRemoveRequestDto( | ||
| @NotNull Long placeId, | ||
| @NotNull Long waybleZoneId | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.wayble.server.user.dto; | ||
|
|
||
| import lombok.Builder; | ||
|
|
||
| @Builder | ||
| public record UserPlaceSummaryDto( | ||
| Long placeId, | ||
| String title, | ||
| String color, | ||
| int savedCount | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.wayble.server.user.dto; | ||
|
|
||
| import com.wayble.server.wayblezone.dto.WaybleZoneListResponseDto; | ||
| import lombok.Builder; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Builder | ||
| public record UserPlaceZonesResponseDto( | ||
| Long placeId, | ||
| String title, | ||
| String color, | ||
| int savedCount, | ||
| List<WaybleZoneListResponseDto> zones | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,12 @@ | |
| import com.wayble.server.user.entity.UserPlace; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| public interface UserPlaceRepository extends JpaRepository<UserPlace, Long> { | ||
| Optional<UserPlace> findByUser_IdAndTitle(Long userId, String title); | ||
| List<UserPlace> findAllByUser_IdOrderByCreatedAtDesc(Long userId); // 사용자 리스트 전체(최신순) | ||
| Optional<UserPlace> findByIdAndUser_Id(Long placeId, Long userId); | ||
| List<UserPlace> findAllByUser_IdOrderByTitleAsc(Long userId); | ||
|
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain소프트 삭제(soft-delete) 행 필터링 여부 확인 필요 Admin 쪽에서 다음 스크립트로 UserPlace 엔티티에 @SQLDelete/@SQLRestriction이 적용되어 있는지 확인해 주세요. 필요 시, UserPlace 엔티티에 아래와 같이 추가하는 것을 권장합니다(참고용): @SQLDelete(sql = "UPDATE user_place SET deleted_at = now() WHERE id = ?")
@SQLRestriction("deleted_at IS NULL")🏁 Script executed: #!/bin/bash
# UserPlace 소프트삭제 설정 존재 여부 탐색
rg -n -C2 'class\s+UserPlace\b' --type=java
rg -n -C2 '@SQLDelete|@SQLRestriction' --type=java | rg -n -C1 'UserPlace|user_place'Length of output: 557 소프트 삭제 필터링 적용 필요: UserPlace 엔티티에 @SQLDelete/@SQLRestriction 추가 현재 수정 사항:
@SQLDelete(sql = "UPDATE user_place SET deleted_at = NOW() WHERE id = ?")
@SQLRestriction("deleted_at IS NULL")
@Table(name = "user_place") // 기존 어노테이션
public class UserPlace extends BaseEntity {
// ...
}위 설정으로 Hibernate가 소프트 삭제 시 🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토큰에서 id 추출하는 과정을 이렇게 상세하게 구현한 이유가 궁금합니다...!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
밑에 답변 참고해주세요!