Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e9022b1
[refactor] 장소 리스트 요약 정보를 저장하기 위한 UserPlace 엔티티 로직 개선
seung-in-Yoo Aug 13, 2025
a547bc6
[refactor] 웨이블존 매핑 레포지토리에 쿼리,메서드 추가
seung-in-Yoo Aug 13, 2025
b02143d
[refactor] 사용자별 리스트 조회 정렬 메서드 추가
seung-in-Yoo Aug 13, 2025
472715d
[feat] 웨이블존 리스트 요약 관련 Dto 생성
seung-in-Yoo Aug 13, 2025
d83625c
[feat] 특정 리스트 내부의 웨이블존 목록을 위한 Dto 생성
seung-in-Yoo Aug 13, 2025
e1d0611
[feat] 유저 장소 요청 관련 Dto에 color 필드 추가
seung-in-Yoo Aug 13, 2025
bd998c7
[refactor] 웨이블존 매핑 레포지토리 로직 수정
seung-in-Yoo Aug 13, 2025
4ef97f0
[refactor] 소유권 검증 및 단건 조회 관련 로직 추가
seung-in-Yoo Aug 13, 2025
4ca773c
[refactor] 유저 장소 관련 에러 케이스 추가
seung-in-Yoo Aug 13, 2025
fc54965
[refactor] 유저 웨이블존 장소 관련 레포지토리,서비스 로직 리팩토링 진행
seung-in-Yoo Aug 13, 2025
bc4e2fc
[refactor] 좋아요 감소 시 음수 방지 코드 추가
seung-in-Yoo Aug 13, 2025
a21f386
[refactor] 웨이블존 color 기본색 회색으로 설정
seung-in-Yoo Aug 13, 2025
9ba53f5
[feat] 삭제용 요청 Dto 추가
seung-in-Yoo Aug 13, 2025
22e27a9
[refactor] 유저 장소 관련 컨트롤러 로직 리팩토링
seung-in-Yoo Aug 13, 2025
479c238
Merge branch 'develop' of https://github.com/Wayble-Project/wayble-sp…
seung-in-Yoo Aug 15, 2025
be3014f
[refactor] 토큰 인증 필수로 변경
seung-in-Yoo Aug 15, 2025
aed685b
[refactor] 요청 DTO에서 userId 제거
seung-in-Yoo Aug 15, 2025
a57f7ec
[refactor] 리뷰 서비스 로직 userId 관련 개선
seung-in-Yoo Aug 15, 2025
595590f
[refactor] 토큰에서 userId 추출로 변경 (요청 Dto userId 삭제)
seung-in-Yoo Aug 15, 2025
35e49b0
[refactor] 사용하지 않는 userId 제거
seung-in-Yoo Aug 15, 2025
8e11d3c
[refactor] 페이지 인덱스 1‑ 기반 입력을 0‑ 기반으로 변환
seung-in-Yoo Aug 15, 2025
c87b8a3
[fix] 페이징에서 1페이지일때 서버에서는 0페이지에서 처리하도록 로직 수정 및 사용하지 않는 로직 제거
seung-in-Yoo Aug 15, 2025
21f0cdc
[refactor] 사용하지 않는 import 제거
seung-in-Yoo Aug 15, 2025
9c73722
[refactor] 카운트쿼리 로직 추가
seung-in-Yoo Aug 15, 2025
bbb4827
[refactor] 컨트롤러 로직 분리 (내 장소 리스트 요약 조회, 특정 장소 내 웨이블존 목록 조회(페이징)
seung-in-Yoo Aug 15, 2025
c45ad40
[refactor] 코드리뷰 반영
seung-in-Yoo Aug 16, 2025
12708cc
Merge pull request #154 from Wayble-Project/feature/seungin
seung-in-Yoo Aug 16, 2025
3865e0b
[fix] wayble marker 경사로, 엘리베이터, 없음 3가지로 수정
zyovn Aug 17, 2025
e09c69a
[fix] polyline 위도 경도 순서 변환
zyovn Aug 17, 2025
9ee9175
[style] 띄어쓰기 수정
zyovn Aug 17, 2025
8a57a53
Merge pull request #156 from Wayble-Project/feature/jeongbin
zyovn Aug 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ private List<double[]> createPolyLine(List<Long> path) {
// 좌표 중복 제거 (동일 좌표가 연속될 시, 추가 X)
if (edge != null && edge.geometry() != null && !edge.geometry().isEmpty()) {
for (double[] coords : edge.geometry()) {
deleteDuplicateCoords(polyline, coords);
double[] latLon = new double[]{coords[1], coords[0]};
deleteDuplicateCoords(polyline, latLon);
}
} else {
Node fromNode = graphInit.getNodeMap().get(from);
Node toNode = graphInit.getNodeMap().get(to);

double[] fromCoord = new double[]{fromNode.lon(), fromNode.lat()};
double[] toCoord = new double[]{toNode.lon(), toNode.lat()};
double[] fromCoord = new double[]{fromNode.lat(), fromNode.lon()};
double[] toCoord = new double[]{toNode.lat(), toNode.lon()};

deleteDuplicateCoords(polyline, fromCoord);
deleteDuplicateCoords(polyline, toCoord);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Pattern;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;


Expand All @@ -37,12 +38,10 @@ public class ReviewController {
})
public CommonResponse<String> registerReview(
@PathVariable Long waybleZoneId,
@RequestBody @Valid ReviewRegisterDto dto,

// TODO: 로그인 구현 후 Authorization 헤더 필수로 변경 필요
@RequestHeader(value = "Authorization", required = false) String authorizationHeader
@RequestBody @Valid ReviewRegisterDto dto
) {
reviewService.registerReview(waybleZoneId, dto, authorizationHeader);
Long userId = extractUserId(); // 토큰에서 유저 ID 추출
reviewService.registerReview(waybleZoneId, userId, dto);
return CommonResponse.success("리뷰가 등록되었습니다.");
}

Expand All @@ -58,4 +57,26 @@ public CommonResponse<List<ReviewResponseDto>> getReviews(
) {
return CommonResponse.success(reviewService.getReviews(waybleZoneId, sort));
}

private Long extractUserId() {
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) {
throw new IllegalStateException("principal에서 userId 파싱 실패");
}
}
try {
return Long.parseLong(auth.getName());
}
catch (Exception e) {
throw new IllegalStateException("인증 정보에서 userId를 추출할 수 없습니다.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@

@Schema(description = "리뷰 등록 요청 DTO")
public record ReviewRegisterDto(

@Schema(description = "작성자 ID", example = "1")
@NotNull(message = "작성자 ID는 필수입니다.")
Long userId,

@Schema(description = "리뷰 내용", example = "뷰가 좋고 접근성이 좋은 카페예요.")
@NotBlank(message = "리뷰 내용은 비어 있을 수 없습니다.")
String content,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public class ReviewService {
private final UserRepository userRepository;

@Transactional
public void registerReview(Long zoneId, ReviewRegisterDto dto, String token) {
public void registerReview(Long zoneId, Long userId, ReviewRegisterDto dto) {
WaybleZone zone = waybleZoneRepository.findById(zoneId)
.orElseThrow(() -> new ApplicationException(WaybleZoneErrorCase.WAYBLE_ZONE_NOT_FOUND));

User user = userRepository.findById(dto.userId())
User user = userRepository.findById(userId)
.orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND));

Review review = Review.of(user, zone, dto.content(), dto.rating());
Expand All @@ -44,14 +44,13 @@ public void registerReview(Long zoneId, ReviewRegisterDto dto, String token) {
zone.updateRating(newRating);
zone.addReviewCount(1);

if (dto.images() != null) {
if (dto.images() != null && !dto.images().isEmpty()) {
for (String imageUrl : dto.images()) {
reviewImageRepository.save(ReviewImage.of(review, imageUrl));
}
}
waybleZoneRepository.save(zone);

// visitDate 및 facilities 저장은 필요시 추가 구현
waybleZoneRepository.save(zone);
}

@Transactional(readOnly = true)
Expand Down
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.*;
Expand Down Expand Up @@ -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() {
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
Expand Up @@ -4,5 +4,6 @@

public record UserPlaceRequestDto(
@NotNull Long waybleZoneId,
@NotNull String title
@NotNull String title,
String color
) {}
11 changes: 11 additions & 0 deletions src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java
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
) {}
18 changes: 18 additions & 0 deletions src/main/java/com/wayble/server/user/entity/UserPlace.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;


@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@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 {

Expand All @@ -23,7 +27,21 @@ public class UserPlace extends BaseEntity {
@Column(name = "title", nullable = false)
private String title;

@Column(length = 20) @Builder.Default
private String color = "GRAY"; // 배지/아이콘 색 (정확히 무슨 색이 있는지 몰라서 일단 자유 문자열 + 기본: 회색)

@Column(name = "saved_count", nullable = false, columnDefinition = "int default 0")
@Builder.Default
private int savedCount = 0; // 리스트에 담긴 웨이블존 수


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

public void increaseCount() { this.savedCount++; }
public void decreaseCount() { if (this.savedCount > 0) this.savedCount--; }

public void updateTitle(String title) { this.title = title; }
public void updateColor(String color) { this.color = color; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public enum UserErrorCase implements ErrorCase {
INVALID_BIRTH_DATE(400, 1010, "생년월일 형식이 올바르지 않습니다."),
USER_INFO_NOT_EXISTS(404,1011, "유저 정보가 존재하지 않습니다."),
NICKNAME_REQUIRED(400, 1012,"nickname 파라미터는 필수입니다."),
NICKNAME_DUPLICATED(409,1013, "이미 사용 중인 닉네임입니다.");
NICKNAME_DUPLICATED(409,1013, "이미 사용 중인 닉네임입니다."),
PLACE_NOT_FOUND(404, 1014, "저장된 장소를 찾을 수 없습니다."),
PLACE_MAPPING_NOT_FOUND(404, 1015, "해당 장소에 해당 웨이블존이 없습니다.");

private final Integer httpStatusCode;
private final Integer errorCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.wayble.server.user.repository;

import com.wayble.server.user.entity.UserPlaceWaybleZoneMapping;
import com.wayble.server.wayblezone.entity.WaybleZone;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

Expand All @@ -11,4 +16,28 @@ public interface UserPlaceWaybleZoneMappingRepository extends JpaRepository<User

@EntityGraph(attributePaths = {"userPlace", "waybleZone"})
List<UserPlaceWaybleZoneMapping> findAllByUserPlace_User_Id(Long userId);
boolean existsByUserPlace_IdAndWaybleZone_Id(Long placeId, Long zoneId);
void deleteByUserPlace_IdAndWaybleZone_Id(Long placeId, Long zoneId);

// 리스트 내부 웨이블존 조회 (페이징 포함)
@Query(
value = """
select m.waybleZone
from UserPlaceWaybleZoneMapping m
where m.userPlace.id = :placeId
order by m.id desc
""",
countQuery = """
select count(m)
from UserPlaceWaybleZoneMapping m
where m.userPlace.id = :placeId
"""
)
Page<WaybleZone> findZonesByPlaceId(@Param("placeId") Long placeId, Pageable pageable);






}
Loading
Loading