diff --git a/backend/.gitignore b/backend/.gitignore index c2065bc..3bd829e 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +### SpringBoot ### +*.yml \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/UserRole.java b/backend/src/main/java/com/example/ecommercewebservice/config/UserRole.java similarity index 75% rename from backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/UserRole.java rename to backend/src/main/java/com/example/ecommercewebservice/config/UserRole.java index 1862e6c..14b362f 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/UserRole.java +++ b/backend/src/main/java/com/example/ecommercewebservice/config/UserRole.java @@ -1,4 +1,4 @@ -package com.example.ecommercewebservice.domain.user.entity; +package com.example.ecommercewebservice.config; import lombok.Getter; diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/redis/RedisCommon.java b/backend/src/main/java/com/example/ecommercewebservice/config/redis/RedisCommon.java similarity index 96% rename from backend/src/main/java/com/example/ecommercewebservice/domain/redis/RedisCommon.java rename to backend/src/main/java/com/example/ecommercewebservice/config/redis/RedisCommon.java index 815a2ba..6a95493 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/redis/RedisCommon.java +++ b/backend/src/main/java/com/example/ecommercewebservice/config/redis/RedisCommon.java @@ -1,4 +1,4 @@ -package com.example.ecommercewebservice.domain.redis; +package com.example.ecommercewebservice.config.redis; import com.google.gson.Gson; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/admin/controller/AdminController.java b/backend/src/main/java/com/example/ecommercewebservice/domain/admin/controller/AdminController.java index 811cd4e..9d2c9fb 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/admin/controller/AdminController.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/admin/controller/AdminController.java @@ -3,7 +3,7 @@ import com.example.ecommercewebservice.domain.user.dto.UserRoleUpdateRequest; import com.example.ecommercewebservice.domain.user.service.UserService; import com.example.ecommercewebservice.global.security.annotation.RoleRequired; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/controller/UserController.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/controller/UserController.java index 57f12a0..91e5804 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/controller/UserController.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/controller/UserController.java @@ -1,22 +1,24 @@ package com.example.ecommercewebservice.domain.user.controller; -import com.example.ecommercewebservice.domain.user.dto.LoginRequest; -import com.example.ecommercewebservice.domain.user.dto.LoginResponse; -import com.example.ecommercewebservice.domain.user.dto.SignupRequest; -import com.example.ecommercewebservice.domain.user.dto.SignupResponse; +import com.example.ecommercewebservice.config.UserRole; +import com.example.ecommercewebservice.domain.user.dto.response.UserProfileResponse; +import com.example.ecommercewebservice.domain.user.dto.request.UserUpdateRequestDto; +import com.example.ecommercewebservice.domain.user.dto.response.UserResponseDto; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginRequest; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginResponse; +import com.example.ecommercewebservice.domain.user.dto.signUp.SignupRequest; +import com.example.ecommercewebservice.domain.user.dto.signUp.SignupResponse; import com.example.ecommercewebservice.domain.user.entity.User; import com.example.ecommercewebservice.domain.user.service.UserService; import com.example.ecommercewebservice.global.constant.MessageConstants; import com.example.ecommercewebservice.global.dto.RsData; +import com.example.ecommercewebservice.global.security.annotation.RoleRequired; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; import java.util.Map; @@ -44,7 +46,7 @@ public ResponseEntity> signup(@Valid @RequestBody SignupR public ResponseEntity> login(@Valid @RequestBody LoginRequest loginRequest) { // 로그인 로직 구현 - 예외는 GlobalExceptionHandler에서 처리 LoginResponse loginResponse = userService.login(loginRequest); - + // 성공 시 - 로그인 응답 반환 RsData rsData = new RsData<>(String.valueOf(HttpStatus.OK.value()), MessageConstants.LOGIN_SUCCESS, loginResponse); return ResponseEntity.ok(rsData); @@ -60,4 +62,32 @@ public ResponseEntity> logout(@RequestHeader("Authorization" } return ResponseEntity.badRequest().body(Map.of("error", "유효하지 않은 인증 정보입니다.")); } + + // 사용자 프로필(정보) 조회 + @GetMapping("/me") + @RoleRequired(UserRole.USER) // USER 권한이 있는 사용자만 접근 가능 + public ResponseEntity> getMyProfile(@AuthenticationPrincipal User user) { // @AuthenticationPrincipal 어노테이션을 사용하여 현재 로그인한 사용자 정보를 가져와서 User에 주입, 메서드 파라미터에만 사용할 수 있다. + UserProfileResponse profile = userService.getMyProfile(user); + RsData rsData = new RsData<>( + String.valueOf(HttpStatus.OK.value()), + "프로필 조회 성공", + profile + ); + return ResponseEntity.ok(rsData); + } + + // 사용자 프로필(정보) 수정 + @PutMapping("/me") + @RoleRequired(UserRole.USER) + public ResponseEntity> updateProfile( + @AuthenticationPrincipal User user, + @Valid @RequestBody UserUpdateRequestDto request) { + UserResponseDto response = userService.updateProfile(user.getUserId(), request); + RsData rsData = new RsData<>( + String.valueOf(HttpStatus.OK.value()), + "프로필 수정 성공", + response + ); + return ResponseEntity.ok(rsData); + } } diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/UserRoleUpdateRequest.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/UserRoleUpdateRequest.java index 69407e4..8e4ece0 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/UserRoleUpdateRequest.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/UserRoleUpdateRequest.java @@ -1,11 +1,25 @@ package com.example.ecommercewebservice.domain.user.dto; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import lombok.Getter; import lombok.NoArgsConstructor; +/** + * 사용자 권한 업데이트 요청 DTO + * + * 사용 위치: + * - AdminController - 관리자가 사용자 권한을 변경할 때 사용 + * - UserService.updateUserRole() - 사용자 권한 변경 비즈니스 로직에서 사용 + * + * 용도: + * - 관리자가 다른 사용자의 역할/권한을 변경하기 위한 요청 데이터를 담는 객체 + * - 사용자 권한 관리를 위한 관리자 전용 기능에서 사용 + */ @Getter @NoArgsConstructor public class UserRoleUpdateRequest { + /** + * 변경할 사용자 역할 (ROLE_USER, ROLE_ADMIN 등) + */ private UserRole role; } \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/request/AddressUpdateRequestDto.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/request/AddressUpdateRequestDto.java new file mode 100644 index 0000000..6f8c981 --- /dev/null +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/request/AddressUpdateRequestDto.java @@ -0,0 +1,41 @@ +package com.example.ecommercewebservice.domain.user.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 사용자 주소 수정 요청을 위한 DTO + * + * 사용 위치: + * - UserUpdateRequestDto.addresses - 사용자 프로필 수정 시 주소 정보를 담는 객체 + * - UserService.updateAddresses() - 주소 정보 업데이트 로직에서 사용 + * + * 용도: + * - 사용자의 주소 정보를 추가, 수정, 삭제하기 위한 요청 데이터를 담는 객체 + * - addressId가 null이면 새 주소 추가, 있으면 기존 주소 수정 + * - 기본 주소는 반드시 하나만 존재해야 함 + */ +@Getter +@NoArgsConstructor +public class AddressUpdateRequestDto { + private Long addressId; // null이면 새 주소 추가, 있으면 수정 또는 삭제 + + @NotBlank(message = "수취인 이름은 필수입니다.") + private String recipient; + + @NotBlank(message = "우편번호는 필수입니다.") + @Pattern(regexp = "^\\d{5}$", message = "우편번호는 5자리 숫자여야 합니다.") + private String postalCode; + + @NotBlank(message = "주소는 필수입니다.") + private String address; + + @NotBlank(message = "전화번호는 필수입니다.") + @Pattern(regexp = "^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", + message = "올바른 전화번호 형식이 아닙니다. (예: 010-1234-5678)") + private String phone; + + private boolean isDefault; +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/request/UserUpdateRequestDto.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/request/UserUpdateRequestDto.java new file mode 100644 index 0000000..49dc337 --- /dev/null +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/request/UserUpdateRequestDto.java @@ -0,0 +1,35 @@ +package com.example.ecommercewebservice.domain.user.dto.request; + +import jakarta.validation.constraints.Pattern; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 사용자 프로필 수정 요청을 위한 DTO + * + * 사용 위치: + * - UserController.updateProfile() - PUT /api/users/me 엔드포인트에서 사용 + * - UserService.updateProfile() - 프로필 수정 비즈니스 로직에서 사용 + * + * 용도: + * - 사용자의 비밀번호, 이름, 전화번호, 주소 정보를 수정하기 위한 요청 데이터를 담는 객체 + * - 모든 필드는 선택적으로 수정 가능 + * - 주소 정보는 추가, 수정, 삭제가 가능 + */ +@Getter +@NoArgsConstructor +public class UserUpdateRequestDto { + @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$", + message = "비밀번호는 최소 8자 이상이며, 영문자, 숫자, 특수문자를 포함해야 합니다.") + private String password; + + private String username; + + @Pattern(regexp = "^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", + message = "올바른 전화번호 형식이 아닙니다. (예: 010-1234-5678)") + private String phoneNumber; + + private List addresses; +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/AddressResponse.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/AddressResponse.java new file mode 100644 index 0000000..3d670f6 --- /dev/null +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/AddressResponse.java @@ -0,0 +1,80 @@ +package com.example.ecommercewebservice.domain.user.dto.response; + +import com.example.ecommercewebservice.domain.user.entity.Address; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 주소 정보 응답 DTO + * + * 사용 위치: + * - AddressController - 주소 관련 API 응답으로 사용 + * - UserService.getAddresses() - 사용자의 주소 목록 조회 시 사용 + * + * 용도: + * - 사용자의 주소 정보를 클라이언트에 반환하기 위한 객체 + * - 주소 정보만을 독립적으로 반환할 때 사용 + * - UserResponseDto.AddressResponseDto와 유사하지만 독립적인 응답으로 사용 + */ +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AddressResponse { + /** + * 주소 식별자 + */ + private Long addressId; + + /** + * 수령인 이름 + */ + private String recipient; + + /** + * 우편번호 + */ + private String postalCode; + + /** + * 상세 주소 + */ + private String address; + + /** + * 배송지 연락처 + */ + private String phoneNumber; + + /** + * 기본 배송지 여부 + */ + private boolean isDefault; + + /** + * 주소 등록 일시 + */ + private LocalDateTime createdAt; + + /** + * Address 엔티티를 AddressResponse DTO로 변환 + * + * @param address 변환할 Address 엔티티 + * @return 생성된 AddressResponse 객체 + */ + public static AddressResponse from(Address address) { + return AddressResponse.builder() + .addressId(address.getAddressId()) + .recipient(address.getRecipient()) + .postalCode(address.getPostalCode()) + .address(address.getAddress()) + .phoneNumber(address.getPhoneNumber()) + .isDefault(address.isDefault()) + .createdAt(address.getCreatedAt()) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/UserProfileResponse.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/UserProfileResponse.java new file mode 100644 index 0000000..8bf3083 --- /dev/null +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/UserProfileResponse.java @@ -0,0 +1,39 @@ +package com.example.ecommercewebservice.domain.user.dto.response; + +import com.example.ecommercewebservice.domain.user.entity.User; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserProfileResponse { + private Long userId; + private String email; + private String username; + private String phoneNumber; + private List addresses; + private String role; + private LocalDateTime createdAt; + + public static UserProfileResponse from(User user) { + return UserProfileResponse.builder() + .userId(user.getUserId()) + .email(user.getEmail()) + .username(user.getActualUsername()) + .phoneNumber(user.getPhoneNumber()) + .addresses(user.getAddresses().stream() + .map(AddressResponse::from) + .collect(Collectors.toList())) + .role(user.getRoles().getFirst()) + .createdAt(user.getCreatedAt()) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/UserResponseDto.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/UserResponseDto.java new file mode 100644 index 0000000..d7a6e3b --- /dev/null +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/response/UserResponseDto.java @@ -0,0 +1,82 @@ +package com.example.ecommercewebservice.domain.user.dto.response; + +import com.example.ecommercewebservice.domain.user.entity.Address; +import com.example.ecommercewebservice.domain.user.entity.User; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 사용자 프로필 응답을 위한 DTO + * + * 사용 위치: + * - UserController.updateProfile() - PUT /api/users/me 엔드포인트의 응답으로 사용 + * - UserService.updateProfile() - 프로필 수정 후 응답 데이터를 생성할 때 사용 + * + * 용도: + * - 사용자의 프로필 정보를 클라이언트에 반환하기 위한 객체 + * - 무한 순환 참조를 방지하기 위해 AddressResponseDto를 내부 클래스로 정의 + * - from() 메서드를 통해 User 엔티티를 DTO로 변환 + */ +@Getter +@Builder +public class UserResponseDto { + private Long userId; + private String email; + private String username; + private String phoneNumber; + private List addresses; + private List roles; + private LocalDateTime updatedAt; + + /** + * 주소 정보 응답을 위한 내부 DTO + * + * 사용 위치: + * - UserResponseDto.addresses - 사용자 프로필 응답의 주소 정보를 담는 객체 + * + * 용도: + * - 주소 정보만을 포함하는 간단한 DTO + * - User 엔티티와의 무한 순환 참조를 방지 + */ + @Getter + @Builder + public static class AddressResponseDto { + private Long addressId; + private String recipient; + private String postalCode; + private String address; + private String phoneNumber; + private boolean isDefault; + private LocalDateTime createdAt; + + public static AddressResponseDto from(Address address) { + return AddressResponseDto.builder() + .addressId(address.getAddressId()) + .recipient(address.getRecipient()) + .postalCode(address.getPostalCode()) + .address(address.getAddress()) + .phoneNumber(address.getPhoneNumber()) + .isDefault(address.isDefault()) + .createdAt(address.getCreatedAt()) + .build(); + } + } + + public static UserResponseDto from(User user) { + return UserResponseDto.builder() + .userId(user.getUserId()) + .email(user.getEmail()) + .username(user.getUsername()) + .phoneNumber(user.getPhoneNumber()) + .addresses(user.getAddresses().stream() + .map(AddressResponseDto::from) + .collect(Collectors.toList())) + .roles(user.getRoles()) + .updatedAt(LocalDateTime.now()) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/LoginRequest.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signIn/LoginRequest.java similarity index 93% rename from backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/LoginRequest.java rename to backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signIn/LoginRequest.java index a0541c2..ddcb9f7 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/LoginRequest.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signIn/LoginRequest.java @@ -1,4 +1,4 @@ -package com.example.ecommercewebservice.domain.user.dto; +package com.example.ecommercewebservice.domain.user.dto.signIn; import com.example.ecommercewebservice.global.constant.MessageConstants; import jakarta.validation.constraints.Email; diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/LoginResponse.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signIn/LoginResponse.java similarity index 86% rename from backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/LoginResponse.java rename to backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signIn/LoginResponse.java index 7d94a29..d7b4094 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/LoginResponse.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signIn/LoginResponse.java @@ -1,14 +1,12 @@ -package com.example.ecommercewebservice.domain.user.dto; +package com.example.ecommercewebservice.domain.user.dto.signIn; import com.example.ecommercewebservice.domain.user.entity.User; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - @Getter @NoArgsConstructor @AllArgsConstructor diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/SignupRequest.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signUp/SignupRequest.java similarity index 72% rename from backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/SignupRequest.java rename to backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signUp/SignupRequest.java index 9f8bf34..f496bfd 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/SignupRequest.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signUp/SignupRequest.java @@ -1,4 +1,4 @@ -package com.example.ecommercewebservice.domain.user.dto; +package com.example.ecommercewebservice.domain.user.dto.signUp; import com.example.ecommercewebservice.global.constant.MessageConstants; import jakarta.validation.constraints.Email; @@ -57,15 +57,20 @@ public class SignupRequest { private String phoneNumber; /** - * 회원가입에 사용되는 주소 - * 사용자 계정의 추가 정보로 사용됨 + * 회원가입 시 기본 배송지 정보 */ + @NotBlank(message = MessageConstants.RECIPIENT_REQUIRED) + private String recipient; // 수령인 이름 + + @NotBlank(message = MessageConstants.POSTAL_CODE_REQUIRED) + @Pattern(regexp = "^\\d{5}$", message = MessageConstants.INVALID_POSTAL_CODE) + private String postalCode; // 우편번호 + + @NotBlank(message = MessageConstants.ADDRESS_REQUIRED) @Size(max = 200, message = MessageConstants.INVALID_ADDRESS_LENGTH) - private String address; + private String address; // 주소 - /** - * 회원가입에 사용되는 프로필 이미지 URL - * 사용자 계정의 추가 정보로 사용됨 - */ -// private String profileImage; + @NotBlank(message = MessageConstants.ADDRESS_PHONE_REQUIRED) + @Pattern(regexp = "^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", message = MessageConstants.INVALID_PHONE_NUMBER) + private String addressPhoneNumber; // 배송지 전화번호 } \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/SignupResponse.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signUp/SignupResponse.java similarity index 88% rename from backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/SignupResponse.java rename to backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signUp/SignupResponse.java index 73c2d8f..c237ffd 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/SignupResponse.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/dto/signUp/SignupResponse.java @@ -1,4 +1,4 @@ -package com.example.ecommercewebservice.domain.user.dto; +package com.example.ecommercewebservice.domain.user.dto.signUp; import com.example.ecommercewebservice.domain.user.entity.User; import lombok.AllArgsConstructor; @@ -19,7 +19,6 @@ public class SignupResponse { private String username; private String role; private String phoneNumber; - private String address; @CreatedDate private LocalDateTime createdAt; @@ -30,7 +29,6 @@ public static SignupResponse from(User user) { .email(user.getEmail()) .username(user.getActualUsername()) .phoneNumber(user.getPhoneNumber()) - .address(user.getAddress()) .role(user.getRoles().getFirst()) // 첫 번째 역할을 사용 .createdAt(user.getCreatedAt()) .build(); diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/Address.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/Address.java new file mode 100644 index 0000000..732e91c --- /dev/null +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/Address.java @@ -0,0 +1,52 @@ +package com.example.ecommercewebservice.domain.user.entity; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "addresses") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@EntityListeners(AuditingEntityListener.class) +public class Address { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long addressId; // 배송지 ID + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; // 사용자 + + @Column(nullable = false) + private String recipient; // 수령인 이름 + + @Column(nullable = false) + private String postalCode; // 우편번호 + + @Column(nullable = false) + private String address; // 주소 + + @Column(nullable = false) + private String phoneNumber; // 전화번호 + + @Column(nullable = false) + private boolean isDefault; // 기본 배송지 여부 + + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; // 생성일시 + + public void update(String recipient, String postalCode, String address, String phone, boolean isDefault) { + this.recipient = recipient; + this.postalCode = postalCode; + this.address = address; + this.phoneNumber = phone; + this.isDefault = isDefault; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/User.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/User.java index 56ad4a7..76146fd 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/User.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/entity/User.java @@ -43,7 +43,17 @@ public class User implements UserDetails { private String phoneNumber; - private String address; + /** + * 배송지 목록 + * CascadeType.ALL: 부모 엔티티의 상태 변경을 자식 엔티티에 전파 + * orphanRemoval = true: 부모와의 관계가 끊어진 자식 엔티티를 자동으로 삭제 + * [CascadeType vs orphanRemoval] + * 1. 부모 엔티티의 상태 변경이 자식에게 전파되고 + * 2. 관계가 끊어진 자식 엔티티가 자동으로 삭제되어 + */ + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) // orphanRemoval = true: 자식 엔티티가 부모 엔티티와의 관계를 끊으면 자동으로 삭제 + @Builder.Default // 이것이 없다면 @Builder가 생성하는 빌더는 이 초기값을 무시하기 때문에 null이 된다. + private List
addresses = new ArrayList<>(); // 빌더 패턴을 사용할 때 addresses 필드가 명시적으로 설정되지 않으면 new ArrayList<>()를 기본값으로 사용 // private String profileImage; @@ -128,4 +138,16 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } + + public void updatePassword(String password) { + this.password = password; + } + + public void updateUsername(String username) { + this.username = username; + } + + public void updatePhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } } \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/repository/UserRepository.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/repository/UserRepository.java index 01f9583..04d6fbe 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/repository/UserRepository.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/repository/UserRepository.java @@ -2,6 +2,8 @@ import com.example.ecommercewebservice.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -30,4 +32,7 @@ public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); boolean existsByUsername(String username); + + @Query("SELECT u FROM User u LEFT JOIN FETCH u.addresses WHERE u.email = :email") + Optional findByEmailWithAddresses(@Param("email") String email); } \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserService.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserService.java index 20cd3ce..518d376 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserService.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserService.java @@ -1,11 +1,17 @@ package com.example.ecommercewebservice.domain.user.service; -import com.example.ecommercewebservice.domain.user.dto.LoginRequest; -import com.example.ecommercewebservice.domain.user.dto.LoginResponse; -import com.example.ecommercewebservice.domain.user.dto.SignupRequest; -import com.example.ecommercewebservice.domain.user.dto.UserRoleUpdateRequest; +import com.example.ecommercewebservice.domain.user.dto.response.UserProfileResponse; +import com.example.ecommercewebservice.domain.user.dto.request.AddressUpdateRequestDto; +import com.example.ecommercewebservice.domain.user.dto.request.UserUpdateRequestDto; +import com.example.ecommercewebservice.domain.user.dto.response.UserResponseDto; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginRequest; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginResponse; +import com.example.ecommercewebservice.domain.user.dto.signUp.SignupRequest; import com.example.ecommercewebservice.domain.user.entity.User; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; public interface UserService { /** @@ -39,4 +45,17 @@ public interface UserService { * @param role 새로운 역할 */ void updateUserRole(Long userId, UserRole role); + + /** + * 현재 로그인한 사용자의 프로필 정보 조회 + * + * @param user 현재 로그인한 사용자 + * @return UserProfileResponse 사용자 프로필 정보 + */ + UserProfileResponse getMyProfile(User user); + + @Transactional + UserResponseDto updateProfile(Long userId, UserUpdateRequestDto request); + + void updateAddresses(User user, List addressDtos); } diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserServiceImpl.java b/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserServiceImpl.java index 75ae8ea..dc7a110 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserServiceImpl.java +++ b/backend/src/main/java/com/example/ecommercewebservice/domain/user/service/UserServiceImpl.java @@ -1,10 +1,15 @@ package com.example.ecommercewebservice.domain.user.service; -import com.example.ecommercewebservice.domain.user.dto.LoginRequest; -import com.example.ecommercewebservice.domain.user.dto.LoginResponse; -import com.example.ecommercewebservice.domain.user.dto.SignupRequest; +import com.example.ecommercewebservice.domain.user.dto.response.UserProfileResponse; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginRequest; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginResponse; +import com.example.ecommercewebservice.domain.user.dto.signUp.SignupRequest; +import com.example.ecommercewebservice.domain.user.dto.request.UserUpdateRequestDto; +import com.example.ecommercewebservice.domain.user.dto.request.AddressUpdateRequestDto; +import com.example.ecommercewebservice.domain.user.dto.response.UserResponseDto; +import com.example.ecommercewebservice.domain.user.entity.Address; import com.example.ecommercewebservice.domain.user.entity.User; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import com.example.ecommercewebservice.domain.user.repository.UserRepository; import com.example.ecommercewebservice.global.exception.BusinessException; import com.example.ecommercewebservice.global.exception.ErrorCode; @@ -14,12 +19,14 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.Collections; +import java.util.List; +import java.util.ArrayList; @Service @Slf4j @@ -51,18 +58,31 @@ public User signup(SignupRequest signupRequest) { throw new BusinessException(ErrorCode.USERNAME_ALREADY_EXISTS); } - // 사용자 엔티티 생성 및 저장 + // 사용자 엔티티 생성 User user = User.builder() .email(signupRequest.getEmail()) .password(passwordEncoder.encode(signupRequest.getPassword())) .phoneNumber(signupRequest.getPhoneNumber()) - .address(signupRequest.getAddress()) .username(signupRequest.getUsername()) .roles(Collections.singletonList(UserRole.USER.getRole())) .build(); + // 기본 배송지 생성 + Address defaultAddress = Address.builder() + .user(user) + .recipient(signupRequest.getRecipient()) + .postalCode(signupRequest.getPostalCode()) + .address(signupRequest.getAddress()) + .phoneNumber(signupRequest.getAddressPhoneNumber()) + .isDefault(true) + .build(); + + // 사용자와 배송지 저장 + user.getAddresses().add(defaultAddress); + User savedUser = userRepository.save(user); + log.info("새로운 사용자의 회원가입: {}", signupRequest.getEmail()); - return userRepository.save(user); + return savedUser; } /** @@ -127,4 +147,84 @@ public void updateUserRole(Long userId, UserRole role) { user.getRoles().clear(); user.getRoles().add(role.getRole()); } + + // 현재 로그인한 사용자의 프로필 정보 조회 + @Override + @Transactional(readOnly = true) + public UserProfileResponse getMyProfile(User user) { + log.info("사용자 프로필 조회: {}", user.getEmail()); + return UserProfileResponse.from(user); // User를 UserProfileResponse로 변환 + } + + @Override + @Transactional + public UserResponseDto updateProfile(Long userId, UserUpdateRequestDto request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); + + // 선택적 필드 업데이트 + if (request.getPassword() != null) { + user.updatePassword(passwordEncoder.encode(request.getPassword())); + } + if (request.getUsername() != null) { + user.updateUsername(request.getUsername()); + } + if (request.getPhoneNumber() != null) { + user.updatePhoneNumber(request.getPhoneNumber()); + } + + // 주소 업데이트 + if (request.getAddresses() != null) { + updateAddresses(user, request.getAddresses()); + } + + return UserResponseDto.from(user); + } + + public void updateAddresses(User user, List addressDtos) { + List
existingAddresses = user.getAddresses(); + List
newAddresses = new ArrayList<>(); + + // 기본 주소 개수 확인 + long defaultAddressCount = addressDtos.stream() + .filter(AddressUpdateRequestDto::isDefault) + .count(); + if (defaultAddressCount != 1) { + throw new BusinessException(ErrorCode.INVALID_DEFAULT_ADDRESS); + } + + for (AddressUpdateRequestDto dto : addressDtos) { + if (dto.getAddressId() == null) { + // 새 주소 추가 + Address newAddress = Address.builder() + .recipient(dto.getRecipient()) + .postalCode(dto.getPostalCode()) + .address(dto.getAddress()) + .phoneNumber(dto.getPhone()) + .isDefault(dto.isDefault()) + .user(user) + .build(); + newAddresses.add(newAddress); + } else { + // 기존 주소 수정 + Address existingAddress = existingAddresses.stream() + .filter(address -> address.getAddressId().equals(dto.getAddressId())) + .findFirst() + .orElseThrow(() -> new BusinessException(ErrorCode.ADDRESS_NOT_FOUND)); + + existingAddress.update( + dto.getRecipient(), + dto.getPostalCode(), + dto.getAddress(), + dto.getPhone(), + dto.isDefault() + ); + newAddresses.add(existingAddress); + } + } + + // 기존 주소 목록을 새로운 주소 목록으로 교체 + existingAddresses.clear(); + existingAddresses.addAll(newAddresses); + } } diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/config/security/SecurityConfig.java b/backend/src/main/java/com/example/ecommercewebservice/global/config/security/SecurityConfig.java index a4a8c78..ff4571b 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/config/security/SecurityConfig.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/config/security/SecurityConfig.java @@ -117,6 +117,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web .ignoring() - .requestMatchers("/h2-console/**", "/static/**", "/css/**", "/js/**", "/images/**"); + .requestMatchers("/css/**", "/js/**", "/images/**", "/webjars/**"); } } \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/constant/MessageConstants.java b/backend/src/main/java/com/example/ecommercewebservice/global/constant/MessageConstants.java index f1a5569..59a3411 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/constant/MessageConstants.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/constant/MessageConstants.java @@ -28,4 +28,11 @@ public class MessageConstants { public static final String TOKEN_EXPIRED = "만료된 토큰입니다."; public static final String INVALID_TOKEN = "유효하지 않은 토큰입니다."; public static final String ACCESS_DENIED = "접근 권한이 없습니다."; + + // Address validation messages + public static final String RECIPIENT_REQUIRED = "수령인 이름은 필수입니다."; + public static final String POSTAL_CODE_REQUIRED = "우편번호는 필수입니다."; + public static final String INVALID_POSTAL_CODE = "유효하지 않은 우편번호 형식입니다."; + public static final String ADDRESS_REQUIRED = "주소는 필수입니다."; + public static final String ADDRESS_PHONE_REQUIRED = "배송지 전화번호는 필수입니다."; } \ No newline at end of file diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/dto/RsData.java b/backend/src/main/java/com/example/ecommercewebservice/global/dto/RsData.java index ab4417c..d905b24 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/dto/RsData.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/dto/RsData.java @@ -6,18 +6,50 @@ import lombok.Getter; import lombok.ToString; +/** + * 범용 응답 데이터 전송 객체 + * + * 사용 위치: + * - 모든 컨트롤러에서 API 응답을 표준화하기 위해 사용 + * - 서비스 계층에서 결과 및 메시지를 포함한 응답 생성 시 사용 + * + * 용도: + * - 응답 코드, 메시지, 데이터를 포함하는 표준화된 응답 형식 제공 + * - API 응답 형식의 일관성 보장 + * - 클라이언트가 응답 상태를 쉽게 확인할 수 있도록 함 + */ @AllArgsConstructor @Getter @JsonInclude(JsonInclude.Include.NON_NULL) public class RsData { + /** + * 응답 상태 코드 (예: "200-SUCCESS", "400-BAD_REQUEST") + * HTTP 상태 코드와 상세 코드를 포함 + */ private String code; + + /** + * 응답 메시지 + * 사용자에게 표시할 메시지 또는 오류 설명 + */ private String msg; + + /** + * 응답 데이터 + * API 응답에 포함될 실제 데이터 객체 + */ private T data; public RsData(String code, String msg) { this(code, msg, null); } + /** + * HTTP 상태 코드 추출 + * code 문자열에서 첫 번째 부분을 추출하여 HTTP 상태 코드로 변환 + * + * @return HTTP 상태 코드 (예: 200, 400, 500) + */ @JsonIgnore public int getStatusCode() { String statusCodeStr = code.split("-")[0]; diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/exception/ErrorCode.java b/backend/src/main/java/com/example/ecommercewebservice/global/exception/ErrorCode.java index de89d0b..0495ed0 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/exception/ErrorCode.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/exception/ErrorCode.java @@ -34,7 +34,14 @@ public enum ErrorCode { ACCESS_DENIED(HttpStatus.FORBIDDEN, "A002", "접근 권한이 없습니다."), EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "A003", "만료된 토큰입니다."), INVALID_CREDENTIALS(HttpStatus.UNAUTHORIZED, "A004", "인증 정보가 올바르지 않습니다."), - INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "A005", "유효하지 않은 토큰입니다."); + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "A005", "유효하지 않은 토큰입니다."), + + // User Profile Update + INVALID_DEFAULT_ADDRESS(HttpStatus.BAD_REQUEST, "U004", "기본 주소는 반드시 하나만 지정해야 합니다."), + ADDRESS_NOT_FOUND(HttpStatus.NOT_FOUND, "U005", "주소를 찾을 수 없습니다."), + INVALID_PHONE_NUMBER_FORMAT(HttpStatus.BAD_REQUEST, "U006", "올바른 전화번호 형식이 아닙니다."), + INVALID_PASSWORD_FORMAT(HttpStatus.BAD_REQUEST, "U007", "비밀번호는 최소 8자 이상이며, 영문자, 숫자, 특수문자를 포함해야 합니다."), + INVALID_POSTAL_CODE_FORMAT(HttpStatus.BAD_REQUEST, "U008", "우편번호는 5자리 숫자여야 합니다."); private final HttpStatus status; private final String code; diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/security/annotation/RoleRequired.java b/backend/src/main/java/com/example/ecommercewebservice/global/security/annotation/RoleRequired.java index f51c490..f0dd5be 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/security/annotation/RoleRequired.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/security/annotation/RoleRequired.java @@ -1,6 +1,6 @@ package com.example.ecommercewebservice.global.security.annotation; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/security/aspect/RoleRequiredAspect.java b/backend/src/main/java/com/example/ecommercewebservice/global/security/aspect/RoleRequiredAspect.java index 35d632b..d38f491 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/security/aspect/RoleRequiredAspect.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/security/aspect/RoleRequiredAspect.java @@ -1,6 +1,6 @@ package com.example.ecommercewebservice.global.security.aspect; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import com.example.ecommercewebservice.global.security.annotation.RoleRequired; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; diff --git a/backend/src/main/java/com/example/ecommercewebservice/global/util/JwtTokenProvider.java b/backend/src/main/java/com/example/ecommercewebservice/global/util/JwtTokenProvider.java index fdd5804..c083717 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/global/util/JwtTokenProvider.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/util/JwtTokenProvider.java @@ -1,7 +1,12 @@ package com.example.ecommercewebservice.global.util; +import com.example.ecommercewebservice.domain.user.entity.User; +import com.example.ecommercewebservice.domain.user.repository.UserRepository; +import com.example.ecommercewebservice.global.util.repository.TokenRepository; import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -9,12 +14,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; -import io.jsonwebtoken.io.Decoders; - -import com.example.ecommercewebservice.domain.token.repository.TokenRepository; import java.security.Key; import java.util.Arrays; @@ -22,7 +23,6 @@ import java.util.Date; import java.util.List; import java.util.stream.Collectors; -import jakarta.servlet.http.HttpServletRequest; /** * JWT 토큰의 생성, 검증, 파싱 등을 담당하는 유틸리티 클래스 @@ -36,6 +36,7 @@ public class JwtTokenProvider { private long tokenValidityInMilliseconds; private String issuer; private TokenRepository tokenRepository; + private UserRepository userRepository; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER_PREFIX = "Bearer "; @@ -59,7 +60,8 @@ public JwtTokenProvider( @Value("${jwt.secret}") String secret, @Value("${jwt.token-validity-in-seconds}") long tokenValidityInSeconds, @Value("${jwt.issuer}") String issuer, - TokenRepository tokenRepository) { + TokenRepository tokenRepository, + UserRepository userRepository) { // Base64로 인코딩된 시크릿 키를 디코딩하여 사용 byte[] keyBytes = Decoders.BASE64.decode(secret); @@ -67,7 +69,8 @@ public JwtTokenProvider( this.tokenValidityInMilliseconds = tokenValidityInSeconds * 1000; this.issuer = issuer; this.tokenRepository = tokenRepository; - + this.userRepository = userRepository; + log.info("JWT Token Provider initialized with issuer: {}", issuer); } @@ -119,9 +122,11 @@ public Authentication getAuthentication(String token) { .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); - UserDetails principal = new User(claims.getSubject(), "", authorities); + // Load the actual User entity from database with addresses + User user = userRepository.findByEmailWithAddresses(claims.getSubject()) + .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + claims.getSubject())); - return new UsernamePasswordAuthenticationToken(principal, token, authorities); + return new UsernamePasswordAuthenticationToken(user, token, authorities); } /** diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/token/repository/RedisTokenRepository.java b/backend/src/main/java/com/example/ecommercewebservice/global/util/repository/RedisTokenRepository.java similarity index 96% rename from backend/src/main/java/com/example/ecommercewebservice/domain/token/repository/RedisTokenRepository.java rename to backend/src/main/java/com/example/ecommercewebservice/global/util/repository/RedisTokenRepository.java index ab28a2a..4a82b2a 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/token/repository/RedisTokenRepository.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/util/repository/RedisTokenRepository.java @@ -1,6 +1,6 @@ -package com.example.ecommercewebservice.domain.token.repository; +package com.example.ecommercewebservice.global.util.repository; -import com.example.ecommercewebservice.domain.redis.RedisCommon; +import com.example.ecommercewebservice.config.redis.RedisCommon; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/com/example/ecommercewebservice/domain/token/repository/TokenRepository.java b/backend/src/main/java/com/example/ecommercewebservice/global/util/repository/TokenRepository.java similarity index 93% rename from backend/src/main/java/com/example/ecommercewebservice/domain/token/repository/TokenRepository.java rename to backend/src/main/java/com/example/ecommercewebservice/global/util/repository/TokenRepository.java index 7bf5ac2..6fae2ca 100644 --- a/backend/src/main/java/com/example/ecommercewebservice/domain/token/repository/TokenRepository.java +++ b/backend/src/main/java/com/example/ecommercewebservice/global/util/repository/TokenRepository.java @@ -1,4 +1,4 @@ -package com.example.ecommercewebservice.domain.token.repository; +package com.example.ecommercewebservice.global.util.repository; /** * JWT 토큰 관리를 위한 저장소 인터페이스 diff --git a/backend/src/test/java/com/example/ecommercewebservice/domain/user/controller/UserControllerTest.java b/backend/src/test/java/com/example/ecommercewebservice/domain/user/controller/UserControllerTest.java index 9fd599e..47e7a72 100644 --- a/backend/src/test/java/com/example/ecommercewebservice/domain/user/controller/UserControllerTest.java +++ b/backend/src/test/java/com/example/ecommercewebservice/domain/user/controller/UserControllerTest.java @@ -1,11 +1,10 @@ package com.example.ecommercewebservice.domain.user.controller; -import com.example.ecommercewebservice.domain.user.dto.LoginRequest; -import com.example.ecommercewebservice.domain.user.dto.SignupRequest; +import com.example.ecommercewebservice.domain.user.dto.signIn.LoginRequest; +import com.example.ecommercewebservice.domain.user.dto.signUp.SignupRequest; import com.example.ecommercewebservice.domain.user.entity.User; -import com.example.ecommercewebservice.domain.user.entity.UserRole; +import com.example.ecommercewebservice.config.UserRole; import com.example.ecommercewebservice.domain.user.repository.UserRepository; -import com.example.ecommercewebservice.domain.user.service.UserService; import com.example.ecommercewebservice.global.util.JwtTokenProvider; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; @@ -84,10 +83,13 @@ void signup_success() throws Exception { // given SignupRequest signupRequest = new SignupRequest(); signupRequest.setEmail("newuser@example.com"); + signupRequest.setUsername("newUser"); signupRequest.setPassword("NewPass123!"); - signupRequest.setUsername("newuser"); signupRequest.setPhoneNumber("010-1234-5678"); + signupRequest.setRecipient("newUser"); + signupRequest.setPostalCode("11111"); signupRequest.setAddress("서울시 강남구"); + signupRequest.setAddressPhoneNumber("010-1111-1111"); // when ResultActions result = mockMvc.perform(post("/api/users/signup") @@ -98,8 +100,12 @@ void signup_success() throws Exception { result.andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.msg").value("회원가입이 완료되었습니다.")) + .andExpect(jsonPath("$.data.userId").exists()) .andExpect(jsonPath("$.data.email").value("newuser@example.com")) - .andExpect(jsonPath("$.data.username").value("newuser")); + .andExpect(jsonPath("$.data.username").value("newUser")) + .andExpect(jsonPath("$.data.phoneNumber").value("010-1234-5678")) + .andExpect(jsonPath("$.data.role").value("USER")) + .andExpect(jsonPath("$.data.createdAt").exists()); } @Test