Skip to content

Commit 237bfee

Browse files
committed
Merge branch 'dev' into ci/env
2 parents 2660981 + 9b0f428 commit 237bfee

15 files changed

Lines changed: 1216 additions & 25 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package store.lastdance.controller.auth;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import jakarta.servlet.http.HttpServletRequest;
7+
import jakarta.servlet.http.HttpServletResponse;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RestController;
15+
import store.lastdance.dto.user.UserResponseDTO;
16+
import store.lastdance.security.oauth.CustomOAuth2User;
17+
import store.lastdance.service.auth.AuthService;
18+
import store.lastdance.service.user.UserV2QueryService;
19+
20+
import java.util.UUID;
21+
22+
@RestController
23+
@RequestMapping("/api/v2/auth")
24+
@RequiredArgsConstructor
25+
@Tag(name = "Authentication", description = "인증 관련 API")
26+
public class AuthV2Controller {
27+
28+
private final AuthService authService;
29+
private final UserV2QueryService userV2QueryService;
30+
31+
@PostMapping("/refresh")
32+
@Operation(summary = "토큰 갱신", description = "리프레시 토큰으로 새 액세스 토큰 발급")
33+
public ResponseEntity<Void> refreshToken(HttpServletRequest request, HttpServletResponse response) {
34+
authService.refreshToken(request, response);
35+
return ResponseEntity.ok().build();
36+
}
37+
38+
@PostMapping("/logout")
39+
@Operation(summary = "로그아웃", description = "토큰 무효화 및 로그아웃 처리")
40+
public ResponseEntity<Void> logout(HttpServletRequest request, HttpServletResponse response) {
41+
authService.logout(request, response);
42+
return ResponseEntity.ok().build();
43+
}
44+
45+
@GetMapping("/me")
46+
@Operation(summary = "내 정보 조회", description = "현재 로그인한 사용자 정보 조회")
47+
public ResponseEntity<UserResponseDTO> getMe(
48+
@Parameter(hidden = true) @AuthenticationPrincipal CustomOAuth2User principal
49+
) {
50+
UUID userId = principal.getUserId();
51+
UserResponseDTO user = userV2QueryService.getUserWithProfileImage(userId);
52+
return ResponseEntity.ok(user);
53+
}
54+
}

src/main/java/store/lastdance/controller/user/UserController.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1313
import org.springframework.web.bind.annotation.*;
1414
import org.springframework.web.multipart.MultipartFile;
15+
import store.lastdance.converter.UserConverter;
1516
import store.lastdance.domain.user.User;
1617
import store.lastdance.dto.response.ApiResponse;
1718
import store.lastdance.dto.user.UserResponseDTO;
@@ -28,6 +29,7 @@
2829
public class UserController {
2930

3031
private final UserService userService;
32+
private final UserConverter userConverter;
3133

3234
@PatchMapping("/me")
3335
@Operation(summary = "내 정보 수정", description = "닉네임 수정")
@@ -37,7 +39,7 @@ public ResponseEntity<ApiResponse<UserResponseDTO>> updateMyInfo(
3739
) {
3840
UUID userId = oAuth2User.getUserId();
3941
User updatedUser = userService.updateMyInfo(userId, requestDTO);
40-
UserResponseDTO dto = UserResponseDTO.from(updatedUser);
42+
UserResponseDTO dto = userConverter.toResponseDTO(updatedUser);
4143
return ResponseEntity.ok(ApiResponse.success(dto));
4244
}
4345

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package store.lastdance.controller.user;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import jakarta.servlet.http.HttpServletRequest;
7+
import jakarta.servlet.http.HttpServletResponse;
8+
import jakarta.validation.Valid;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.web.bind.annotation.*;
14+
import org.springframework.web.multipart.MultipartFile;
15+
import store.lastdance.converter.UserConverter;
16+
import store.lastdance.domain.user.User;
17+
import store.lastdance.dto.response.ApiResponse;
18+
import store.lastdance.dto.user.UserResponseDTO;
19+
import store.lastdance.dto.user.UserUpdateRequestDTO;
20+
import store.lastdance.security.oauth.CustomOAuth2User;
21+
import store.lastdance.service.user.UserV2QueryService;
22+
import store.lastdance.service.user.UserV2Service;
23+
24+
import java.util.UUID;
25+
26+
@RestController
27+
@RequestMapping("/api/v2/users")
28+
@RequiredArgsConstructor
29+
@Tag(name = "User", description = "사용자 관리 API")
30+
public class UserV2Controller {
31+
32+
private final UserV2Service userV2Service;
33+
private final UserV2QueryService userV2QueryService;
34+
private final UserConverter userConverter;
35+
36+
@PatchMapping("/me")
37+
@Operation(summary = "내 정보 수정", description = "닉네임 수정")
38+
public ResponseEntity<ApiResponse<UserResponseDTO>> updateMyInfo(
39+
@Parameter(hidden = true) @AuthenticationPrincipal CustomOAuth2User oAuth2User,
40+
@Valid @RequestBody UserUpdateRequestDTO requestDTO
41+
) {
42+
UUID userId = oAuth2User.getUserId();
43+
User updatedUser = userV2Service.updateMyInfo(userId, requestDTO);
44+
UserResponseDTO dto = userConverter.toResponseDTO(updatedUser);
45+
return ResponseEntity.ok(ApiResponse.success(dto));
46+
}
47+
48+
@PostMapping(value = "/me/profile-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
49+
@Operation(summary = "프로필 이미지 업로드", description = "JPG, PNG 형식만 지원, 최대 5MB")
50+
public ResponseEntity<ApiResponse<UserResponseDTO>> uploadProfileImage(
51+
@Parameter(hidden = true) @AuthenticationPrincipal CustomOAuth2User oAuth2User,
52+
@RequestParam("file") MultipartFile file
53+
) {
54+
UUID userId = oAuth2User.getUserId();
55+
UserResponseDTO dto = userV2Service.updateProfileImage(userId, file);
56+
return ResponseEntity.ok(ApiResponse.success(dto));
57+
}
58+
59+
@DeleteMapping("/me/profile-image")
60+
@Operation(summary = "프로필 이미지 삭제", description = "기본 이미지로 변경")
61+
public ResponseEntity<ApiResponse<UserResponseDTO>> deleteProfileImage(
62+
@Parameter(hidden = true) @AuthenticationPrincipal CustomOAuth2User oAuth2User
63+
) {
64+
UUID userId = oAuth2User.getUserId();
65+
UserResponseDTO dto = userV2Service.deleteProfileImage(userId);
66+
return ResponseEntity.ok(ApiResponse.success(dto));
67+
}
68+
69+
@GetMapping("/nickname/check")
70+
@Operation(summary = "닉네임 중복 확인", description = "사용 가능 여부 체크")
71+
public ResponseEntity<ApiResponse<Boolean>> checkNickname(
72+
@RequestParam("nickname") String nickname,
73+
@Parameter(hidden = true) @AuthenticationPrincipal CustomOAuth2User oAuth2User
74+
) {
75+
UUID userId = oAuth2User.getUserId();
76+
boolean isAvailable = userV2QueryService.isNicknameAvailable(userId, nickname);
77+
return ResponseEntity.ok(ApiResponse.success(isAvailable));
78+
}
79+
80+
@DeleteMapping("/me")
81+
@Operation(summary = "계정 비활성화", description = "계정 비활성화 및 토큰 무효화")
82+
public ResponseEntity<ApiResponse<String>> deactivateAccount(
83+
@Parameter(hidden = true) @AuthenticationPrincipal CustomOAuth2User oAuth2User,
84+
@Parameter(hidden = true) HttpServletRequest request,
85+
@Parameter(hidden = true) HttpServletResponse response
86+
) {
87+
UUID userId = oAuth2User.getUserId();
88+
userV2Service.deactivateUser(userId, request, response);
89+
return ResponseEntity.ok(ApiResponse.success("계정이 비활성화되었습니다."));
90+
}
91+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package store.lastdance.converter;
2+
3+
import org.springframework.stereotype.Component;
4+
import store.lastdance.domain.user.User;
5+
import store.lastdance.dto.user.UserResponseDTO;
6+
7+
@Component
8+
public class UserConverter {
9+
10+
public UserResponseDTO toResponseDTO(User user) {
11+
if (user == null) {
12+
return null;
13+
}
14+
15+
String profileImageUrl = (user.getProfileImageFile() != null)
16+
? user.getProfileImageFile().getFilePath()
17+
: null;
18+
19+
return new UserResponseDTO(
20+
user.getUserId(),
21+
user.getEmail(),
22+
user.getUsername(),
23+
user.getNickname(),
24+
profileImageUrl,
25+
user.getProvider().name(),
26+
user.getIsActive(),
27+
user.getIsBanned(),
28+
user.getUserBudget()
29+
);
30+
}
31+
}

src/main/java/store/lastdance/dto/user/UserResponseDTO.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package store.lastdance.dto.user;
22

33
import io.swagger.v3.oas.annotations.media.Schema;
4-
import store.lastdance.domain.user.User;
54

65
import java.util.UUID;
76

@@ -34,22 +33,4 @@ public record UserResponseDTO(
3433
@Schema(description = "월 예산", example = "1000000")
3534
Integer monthlyBudget
3635
) {
37-
public static UserResponseDTO from(User user) {
38-
String profileImageUrl = null;
39-
if (user.getProfileImageFile() != null) {
40-
profileImageUrl = user.getProfileImageFile().getFilePath();
41-
}
42-
43-
return new UserResponseDTO(
44-
user.getUserId(),
45-
user.getEmail(),
46-
user.getUsername(),
47-
user.getNickname(),
48-
profileImageUrl,
49-
user.getProvider().name(),
50-
user.getIsActive(),
51-
user.getIsBanned(),
52-
user.getUserBudget()
53-
);
54-
}
5536
}

src/main/java/store/lastdance/service/user/UserServiceImpl.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.stereotype.Service;
99
import org.springframework.transaction.annotation.Transactional;
1010
import org.springframework.web.multipart.MultipartFile;
11+
import store.lastdance.converter.UserConverter;
1112
import store.lastdance.domain.common.ImageFile;
1213
import store.lastdance.domain.user.User;
1314
import store.lastdance.dto.user.UserResponseDTO;
@@ -28,6 +29,7 @@ public class UserServiceImpl implements UserService {
2829
private final UserRepository userRepository;
2930
private final ImageService imageService;
3031
private final ApplicationEventPublisher eventPublisher;
32+
private final UserConverter userConverter;
3133

3234
@Override
3335
public User findByActiveUser(UUID userId) {
@@ -49,7 +51,7 @@ public User findByUserId(UUID userId) {
4951
public UserResponseDTO getUserWithProfileImage(UUID userId) {
5052
User user = userRepository.findByIdWithProfileImage(userId)
5153
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
52-
return UserResponseDTO.from(user);
54+
return userConverter.toResponseDTO(user);
5355
}
5456

5557
@Override
@@ -112,7 +114,7 @@ public UserResponseDTO updateProfileImage(UUID userId, MultipartFile file) {
112114
throw e;
113115
}
114116

115-
return UserResponseDTO.from(user);
117+
return userConverter.toResponseDTO(user);
116118
}
117119

118120
@Override
@@ -129,7 +131,7 @@ public UserResponseDTO deleteProfileImage(UUID userid) {
129131
}
130132

131133
userRepository.save(user);
132-
return UserResponseDTO.from(user);
134+
return userConverter.toResponseDTO(user);
133135
}
134136

135137
@Override
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package store.lastdance.service.user;
2+
3+
import store.lastdance.domain.user.User;
4+
import store.lastdance.dto.user.UserResponseDTO;
5+
6+
import java.util.UUID;
7+
8+
9+
public interface UserV2QueryService {
10+
User findByActiveUser(UUID userid);
11+
12+
User findByUserId(UUID userId);
13+
14+
UserResponseDTO getUserWithProfileImage(UUID userId);
15+
16+
boolean isNicknameAvailable(UUID userId, String nickname);
17+
18+
void validateUserExists(UUID userId);
19+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package store.lastdance.service.user;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Service;
6+
import org.springframework.transaction.annotation.Transactional;
7+
import store.lastdance.converter.UserConverter;
8+
import store.lastdance.domain.user.User;
9+
import store.lastdance.dto.user.UserResponseDTO;
10+
import store.lastdance.exception.CustomException;
11+
import store.lastdance.exception.ErrorCode;
12+
import store.lastdance.repository.user.UserRepository;
13+
14+
import java.util.UUID;
15+
16+
@Service
17+
@Slf4j
18+
@RequiredArgsConstructor
19+
@Transactional(readOnly = true)
20+
public class UserV2QueryServiceImpl implements UserV2QueryService {
21+
22+
private final UserRepository userRepository;
23+
private final UserConverter userConverter;
24+
25+
@Override
26+
public User findByActiveUser(UUID userId) {
27+
User user = userRepository.findById(userId)
28+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
29+
30+
if (!user.getIsActive()) {
31+
throw new CustomException(ErrorCode.USER_INACTIVE);
32+
}
33+
34+
return user;
35+
}
36+
37+
@Override
38+
public User findByUserId(UUID userId) {
39+
return userRepository.findById(userId)
40+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
41+
}
42+
43+
@Override
44+
public UserResponseDTO getUserWithProfileImage(UUID userId) {
45+
User user = userRepository.findByIdWithProfileImage(userId)
46+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
47+
return userConverter.toResponseDTO(user);
48+
}
49+
50+
@Override
51+
public boolean isNicknameAvailable(UUID userId, String nickname) {
52+
if (nickname == null || nickname.trim().isEmpty()) {
53+
return false;
54+
}
55+
return !userRepository.existsByNicknameAndUserIdNot(nickname.trim(), userId);
56+
}
57+
58+
@Override
59+
public void validateUserExists(UUID userId) {
60+
if (!userRepository.existsById(userId)) {
61+
log.error("User with ID {} does not exist", userId);
62+
throw new CustomException(ErrorCode.USER_NOT_FOUND);
63+
}
64+
65+
if (!userRepository.findById(userId)
66+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND))
67+
.getIsActive()) {
68+
throw new CustomException(ErrorCode.USER_INACTIVE);
69+
}
70+
71+
log.info("User with ID {} exists", userId);
72+
}
73+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package store.lastdance.service.user;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import org.springframework.web.multipart.MultipartFile;
6+
import store.lastdance.domain.user.User;
7+
import store.lastdance.dto.user.UserResponseDTO;
8+
import store.lastdance.dto.user.UserUpdateRequestDTO;
9+
10+
import java.util.UUID;
11+
12+
public interface UserV2Service {
13+
14+
User updateMyInfo(UUID userId, UserUpdateRequestDTO requestDTO);
15+
16+
UserResponseDTO updateProfileImage(UUID userid, MultipartFile file);
17+
18+
UserResponseDTO deleteProfileImage(UUID userid);
19+
20+
void deactivateUser(UUID userId, HttpServletRequest request, HttpServletResponse response);
21+
22+
}

0 commit comments

Comments
 (0)