diff --git a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java index ca7536d5..cf73650b 100644 --- a/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java +++ b/src/main/java/org/ezcode/codetest/application/problem/service/ProblemService.java @@ -134,7 +134,7 @@ public void removeProblem(Long problemId) { // 문제 이미지가 있으면 삭제 if (!findProblem.getImageUrl().isEmpty()) { for(String fileUrl : findProblem.getImageUrl()) { - s3Uploader.delete(fileUrl); + s3Uploader.delete(fileUrl, "problem"); } } @@ -163,7 +163,7 @@ private void updateProblemImage(Problem problem, MultipartFile newImage) { // 기존 이미지가 있다면 삭제 처리 if (!problem.getImageUrl().isEmpty()) { for(String fileUrl : problem.getImageUrl()) { - s3Uploader.delete(fileUrl); + s3Uploader.delete(fileUrl, "problem"); } problem.clearImages(); } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java index 8cfb5fab..368f393a 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/request/ResetPasswordRequest.java @@ -1,15 +1,19 @@ package org.ezcode.codetest.application.usermanagement.user.dto.request; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; @Schema(description = "비밀번호 리셋을 위한 입력") public record ResetPasswordRequest ( + @NotBlank(message = "임시 재설정 토큰은 필수입니다") @Schema(description = "비밀번호 리셋 유저의 토큰 - 유효 시간 10분") String tempResetToken, + @NotBlank(message = "새 비밀번호는 필수입니다") @Schema(description = "변경할 비밀번호", example = "myPassword@@!") String newPassword, + @NotBlank(message = "비밀번호 확인은 필수입니다") @Schema(description = "변경할 비밀번호 확인용 재입력", example = "myPassword@@!") String newPasswordConfirm ){ diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GrantAdminRoleResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GrantAdminRoleResponse.java new file mode 100644 index 00000000..60342ae7 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/GrantAdminRoleResponse.java @@ -0,0 +1,13 @@ +package org.ezcode.codetest.application.usermanagement.user.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@Schema(description = "Admin 권한을 부여") +@AllArgsConstructor +public class GrantAdminRoleResponse { + private final String message; + +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserProfileImageResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserProfileImageResponse.java new file mode 100644 index 00000000..4c59b546 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/dto/response/UserProfileImageResponse.java @@ -0,0 +1,11 @@ +package org.ezcode.codetest.application.usermanagement.user.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class UserProfileImageResponse { + private final String message; + +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java index dc1768ef..317c5617 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java @@ -4,6 +4,8 @@ import java.time.temporal.ChronoUnit; import java.util.List; +import org.ezcode.codetest.application.usermanagement.user.dto.response.GrantAdminRoleResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.UserProfileImageResponse; import org.ezcode.codetest.application.usermanagement.user.model.UsersByWeek; import org.ezcode.codetest.domain.submission.dto.WeeklySolveCount; import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest; @@ -13,16 +15,24 @@ import org.ezcode.codetest.application.usermanagement.user.dto.response.WithdrawUserResponse; import org.ezcode.codetest.domain.submission.service.SubmissionDomainService; import org.ezcode.codetest.domain.user.exception.AuthException; +import org.ezcode.codetest.domain.user.exception.UserException; import org.ezcode.codetest.domain.user.exception.code.AuthExceptionCode; +import org.ezcode.codetest.domain.user.exception.code.UserExceptionCode; import org.ezcode.codetest.domain.user.model.entity.AuthUser; import org.ezcode.codetest.domain.user.model.entity.User; import org.ezcode.codetest.domain.user.model.enums.AuthType; +import org.ezcode.codetest.domain.user.model.enums.UserRole; import org.ezcode.codetest.domain.user.service.MailService; import org.ezcode.codetest.domain.user.service.UserDomainService; +import org.ezcode.codetest.infrastructure.s3.S3Directory; +import org.ezcode.codetest.infrastructure.s3.S3Uploader; +import org.ezcode.codetest.infrastructure.s3.exception.S3Exception; +import org.ezcode.codetest.infrastructure.s3.exception.code.S3ExceptionCode; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,6 +45,7 @@ public class UserService { private final UserDomainService userDomainService; private final SubmissionDomainService submissionDomainService; private final RedisTemplate redisTemplate; + private final S3Uploader s3Uploader; @Transactional(readOnly = true) public UserInfoResponse getUserInfo(AuthUser authUser) { @@ -126,4 +137,57 @@ public void resetAllUsersTokensWeekly(LocalDateTime startDateTime, LocalDateTime userDomainService.resetReviewTokensForUsers(UsersByWeek.from(counts, weekLength)); } + @Transactional + public UserProfileImageResponse uploadUserProfileImage(AuthUser authUser, MultipartFile image) { + User user = userDomainService.getUserById(authUser.getId()); + if (user.getProfileImageUrl()!=null) { + s3Uploader.delete(user.getProfileImageUrl(), "profile"); + } + String profileImageUrl = uploadProfileImage(image); + + user.modifyProfileImage(profileImageUrl); + return new UserProfileImageResponse(profileImageUrl); + } + + private String uploadProfileImage(MultipartFile image) { + try { + return s3Uploader.upload(image, S3Directory.PROFILE.getDir()); + } catch (Exception e) { + log.error("프로필 이미지 업로드 실패 - image {}", image, e); + throw new S3Exception(S3ExceptionCode.S3_UPLOAD_FAILED); + } + } + + @Transactional + public UserProfileImageResponse deleteUserProfileImage(AuthUser authUser) { + User user = userDomainService.getUserById(authUser.getId()); + + String oldImageUrl = user.getProfileImageUrl(); + + // S3에서 기존 이미지 파일 삭제 + if (oldImageUrl != null) { + try { + s3Uploader.delete(oldImageUrl, "profile"); + user.modifyProfileImage(null); + } catch (Exception e) { + log.warn("프로필 이미지 삭제 실패 - url: {}", oldImageUrl, e); + } + } + + return new UserProfileImageResponse(null); + } + + @Transactional + public GrantAdminRoleResponse grantAdminRole(AuthUser authUser, Long userId) { + if (authUser.getId().equals(userId)) { + throw new UserException(UserExceptionCode.GRANT_ADMIN_SELF); + } + User user = userDomainService.getUserById(userId); + if (user.getRole().equals(UserRole.ADMIN)) { + throw new UserException(UserExceptionCode.ALREADY_ADMIN_USER); + } + user.modifyUserRole(UserRole.ADMIN); + + return new GrantAdminRoleResponse("ADMIN 권한을 부여합니다"); + } } diff --git a/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java b/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java index 924e4a0e..b24f7a7d 100644 --- a/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java +++ b/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java @@ -73,11 +73,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(new DispatcherTypeRequestMatcher(DispatcherType.ASYNC)).permitAll() .requestMatchers( SecurityPath.PUBLIC_PATH).permitAll() + .requestMatchers("/api/admin/**").hasRole("ADMIN") //어드민 권한 필요 (문제 생성, 관리 등) .requestMatchers(HttpMethod.GET, "/api/problems/*/discussions", "/api/problems/{problemId}/discussions/{discussionId}/replies", "/api/problems/{problemId}/discussions/{discussionId}/replies/**").permitAll() - .requestMatchers("/admin/**").hasRole("ADMIN") //어드민 권한 필요 (문제 생성, 관리 등) .anyRequest().authenticated() //나머지는 일반 인증 ) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) diff --git a/src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java b/src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java index 0ccaa66a..81e4c562 100644 --- a/src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java +++ b/src/main/java/org/ezcode/codetest/common/security/hander/CustomSuccessHandler.java @@ -117,13 +117,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .queryParam("refreshToken", refreshToken) .build().toUriString(); - //JSON 문자열로 바꿔서 클라이언트에게 응답 본문으로 전달 - OAuthResponse oAuthResponse = new OAuthResponse(accessToken, refreshToken); - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(oAuthResponse)); - // response.sendRedirect(targetUri); + response.sendRedirect(targetUri); } } \ No newline at end of file diff --git a/src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java b/src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java index 736b1034..a931dfe9 100644 --- a/src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/JwtFilter.java @@ -70,6 +70,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse tier ); + // 인가를 위한 권한 정보 저장 Collection authorities = List.of(new SimpleGrantedAuthority("ROLE_" + authUser.getRole().name())); @@ -78,6 +79,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse Authentication authToken; authToken = new UsernamePasswordAuthenticationToken(authUser, null, authorities); + // SecurityContextHolder(세션)에 토큰 담기 SecurityContextHolder.getContext().setAuthentication(authToken); diff --git a/src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java b/src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java index 7cf02b94..fc30e786 100644 --- a/src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/JwtUtil.java @@ -150,7 +150,7 @@ public boolean validateToken(String refreshToken) { } public String createEmailToken(Long userId, String email) { - if ( email == null ) { + if ( email == null || userId == null) { throw new IllegalArgumentException("토큰에 필요한 필수 매개변수가 null입니다."); } diff --git a/src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java b/src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java index c4e413f3..879dbdd2 100644 --- a/src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java +++ b/src/main/java/org/ezcode/codetest/common/security/util/SecurityPath.java @@ -8,6 +8,7 @@ public class SecurityPath { "/login/oauth2/**", //OAuth로그인 접근 "/api/auth/**", "/api/oauth2/**", + "/oauth/**", "/login", "/ezlogin", "/login/**", diff --git a/src/main/java/org/ezcode/codetest/domain/user/exception/code/AuthExceptionCode.java b/src/main/java/org/ezcode/codetest/domain/user/exception/code/AuthExceptionCode.java index e58718ef..80736baa 100644 --- a/src/main/java/org/ezcode/codetest/domain/user/exception/code/AuthExceptionCode.java +++ b/src/main/java/org/ezcode/codetest/domain/user/exception/code/AuthExceptionCode.java @@ -20,7 +20,8 @@ public enum AuthExceptionCode implements ResponseCode { PASSWORD_IS_SAME(false, HttpStatus.BAD_REQUEST, "기존 비밀번호와 같습니다. 새로운 비밀번호는 기존 비밀번호와 달라야합니다."), ALREADY_WITHDRAW_USER(false, HttpStatus.NOT_FOUND, "탈퇴된 회원입니다."), TOKEN_ENCODE_FAIL(false, HttpStatus.BAD_REQUEST, "토큰 인코딩에 실패했습니다."), - REDIRECT_URI_NOT_FOUND(false, HttpStatus.BAD_REQUEST, "redirect_uri를 찾을 수 없습니다"); + REDIRECT_URI_NOT_FOUND(false, HttpStatus.BAD_REQUEST, "redirect_uri를 찾을 수 없습니다"), + INVALID_AUTH_USER(false, HttpStatus.BAD_REQUEST, "ADMIN만 접근 가능합니다"); private final boolean success; private final HttpStatus status; diff --git a/src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java b/src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java index 9c3e0980..6e896edc 100644 --- a/src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java +++ b/src/main/java/org/ezcode/codetest/domain/user/exception/code/UserExceptionCode.java @@ -13,7 +13,9 @@ public enum UserExceptionCode implements ResponseCode { NOT_ENOUGH_TOKEN(false, HttpStatus.BAD_REQUEST, "리뷰 토큰이 부족합니다."), NOT_MATCH_CODE(false, HttpStatus.BAD_REQUEST, "이메일 인증 코드가 일치하지 않습니다."), NO_GITHUB_INFO(false, HttpStatus.BAD_REQUEST, "깃허브 정보가 없습니다."), - NO_GITHUB_REPO(false, HttpStatus.BAD_REQUEST, "해당하는 Repository를 찾을 수 없습니다."); + NO_GITHUB_REPO(false, HttpStatus.BAD_REQUEST, "해당하는 Repository를 찾을 수 없습니다."), + GRANT_ADMIN_SELF(false, HttpStatus.BAD_REQUEST, "본인에게 ADMIN 권한을 부여할 수 없습니다."), + ALREADY_ADMIN_USER(false, HttpStatus.BAD_REQUEST, "이미 ADMIN 권한을 가진 유저입니다."); private final boolean success; private final HttpStatus status; diff --git a/src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java b/src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java index e4630ca8..1942de19 100644 --- a/src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java +++ b/src/main/java/org/ezcode/codetest/domain/user/model/entity/AuthUser.java @@ -6,6 +6,7 @@ import org.ezcode.codetest.domain.user.model.enums.Tier; import org.ezcode.codetest.domain.user.model.enums.UserRole; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import lombok.Getter; @@ -29,7 +30,9 @@ public AuthUser(Long id, String username, String nickname, String email, UserRol } @Override - public Collection getAuthorities() { return List.of(() -> "ROLE_" + role.name()); } + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); + } @Override public String getPassword() { return ""; } diff --git a/src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java b/src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java index e63a4a61..9550c092 100644 --- a/src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java +++ b/src/main/java/org/ezcode/codetest/domain/user/model/entity/User.java @@ -9,9 +9,11 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -196,4 +198,12 @@ public void setGitPushStatus(boolean gitPushStatus) { public boolean getGitPushStatus() { return gitPushStatus; } + + public void modifyProfileImage(String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + } + + public void modifyUserRole(UserRole userRole) { + this.role = userRole; + } } diff --git a/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java b/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java index 362dac8b..8e48d279 100644 --- a/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java +++ b/src/main/java/org/ezcode/codetest/infrastructure/s3/S3Uploader.java @@ -61,9 +61,15 @@ public String upload(MultipartFile multipartFile, String dirName) { } // 이미지 삭제 - public void delete(String fileUrl) { + public void delete(String fileUrl, String dirName) { try { - String fileName = extractKeyFromProblemUrl(fileUrl); + String fileName = ""; + if (dirName.equalsIgnoreCase("problem")) { + fileName = extractKeyFromProblemUrl(fileUrl); + } + if (dirName.equalsIgnoreCase("profile")) { + fileName = extractKeyFromProfileUrl(fileUrl); + } amazonS3.deleteObject(bucket, fileName); // S3 내 이미지 객체 제거. log.info("S3에서 이미지 삭제 완료: {}", fileName); } catch (Exception e) { @@ -77,4 +83,9 @@ private String extractKeyFromProblemUrl(String fileUrl) { // S3 주소 포맷 기준으로 잘라내기 return fileUrl.substring(fileUrl.indexOf("problem/")); } + + // 프로필 이미지 URL 가져오기 + private String extractKeyFromProfileUrl(String profileFileUrl) { + return profileFileUrl.substring(profileFileUrl.indexOf("profile/")); + } } diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java index b89c8361..6ab534a2 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/OAuth2Controller.java @@ -1,6 +1,7 @@ package org.ezcode.codetest.presentation.usermanagement; import java.io.IOException; +import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -27,7 +28,7 @@ public class OAuth2Controller { 프론트엔드에서 GitHub 로그인 버튼 클릭 시 이 API를 먼저 호출 redirect_uri는 로그인 완료 후 accessToken과 refreshToken을 전달받을 프론트의 콜백 URL - 예시: GET /api/oauth2/authorize/github?redirect_uri=https://ezcode.my/oauth/callback + 예시: GET /api/oauth2/authorize/google?redirect_uri=https://ezcode.my """) @Parameters({ @Parameter(name = "redirect_uri", description = "프론트 콜백 URI", required = true, example = "https://ezcode.my/oauth/callback (이 uri는 예시이니 편하신걸로 바꾸심 됩니당)") @@ -39,10 +40,15 @@ public void redirectToProvider( HttpServletResponse response, @RequestParam(required = false) String redirect_uri ) throws IOException { - if (redirect_uri != null) { + if (redirect_uri != null && isValidRedirectUri(redirect_uri)) { request.getSession().setAttribute("redirect_uri", redirect_uri); } response.sendRedirect("/oauth2/authorization/" + provider); } + + private boolean isValidRedirectUri(String uri) { + List allowedDomains = List.of("http://localhost:8080", "http://localhost:3000","https://ezcode.my"); + return allowedDomains.stream().anyMatch(uri::startsWith); + } } diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java index dabc307d..1266aba5 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java @@ -3,7 +3,9 @@ import org.ezcode.codetest.application.usermanagement.user.dto.request.ModifyUserInfoRequest; import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest; import org.ezcode.codetest.application.usermanagement.user.dto.response.ChangeUserPasswordResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.GrantAdminRoleResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.UserInfoResponse; +import org.ezcode.codetest.application.usermanagement.user.dto.response.UserProfileImageResponse; import org.ezcode.codetest.application.usermanagement.user.dto.response.WithdrawUserResponse; import org.ezcode.codetest.application.usermanagement.user.service.UserService; import org.ezcode.codetest.domain.user.model.entity.AuthUser; @@ -12,10 +14,16 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -47,6 +55,28 @@ public ResponseEntity modifyUserInfo( return ResponseEntity.status(HttpStatus.OK).body(userService.modifyUserInfo(authUser, modifyUserInfoRequest)); } + //유저 프로필 이미지 등록 + @Operation( + summary = "프로필 이미지 등록", + description = "유저의 프로필 이미지를 등록합니다. 기존의 이미지가 있는 경우, 기존 이미지가 삭제되고 새로운 이미지로 교체됩니다.") + @PutMapping("/users/profile") + public ResponseEntity uploadUserProfileImage( + @AuthenticationPrincipal AuthUser authUser, + @RequestPart(value = "image", required = false) MultipartFile image + ){ + return ResponseEntity.status(HttpStatus.OK).body(userService.uploadUserProfileImage(authUser, image)); + } + + @Operation( + summary = "프로필 이미지 삭제", + description = "유저의 프로필 이미지를 삭제 후 기본 이미지로 대체됩니다.") + @DeleteMapping("/users/profile") + public ResponseEntity deleteUserProfileImage( + @AuthenticationPrincipal AuthUser authUser + ){ + return ResponseEntity.status(HttpStatus.OK).body(userService.deleteUserProfileImage(authUser)); + } + @Operation(summary = "비밀번호 변경", description = "기존 비밀번호와 새 비밀번호를 입력하여 비밀번호를 변경합니다.") @PutMapping("/users/password") public ResponseEntity modifyUserPassword( @@ -62,6 +92,14 @@ public ResponseEntity withdraw( @AuthenticationPrincipal AuthUser authUser ){ return ResponseEntity.status(HttpStatus.OK).body(userService.withdrawUser(authUser)); + } + @Operation(summary = "유저 권한 전환", description = "관리자 권한을 가지고 있는 유저는 다른 유저의 권한을 수정할 수 있습니다.") + @PostMapping("/admin/users/{userId}/grant-admin") + public ResponseEntity grantAdminRole( + @AuthenticationPrincipal AuthUser authUser, + @PathVariable Long userId + ){ + return ResponseEntity.status(HttpStatus.OK).body(userService.grantAdminRole(authUser, userId)); } }