Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file modified gradlew
100644 → 100755
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers("/api/auth/sign-out").permitAll()
.requestMatchers("/api/follows/**").authenticated()
.requestMatchers("/api/notifications/**").authenticated()
.requestMatchers(HttpMethod.PATCH, "/api/users/*").authenticated()
.requestMatchers(HttpMethod.GET, "/api/users/*").authenticated()
Comment thread
dstle marked this conversation as resolved.
.requestMatchers("/api/users").permitAll()
.requestMatchers("/api/auth/sign-in").permitAll()
.requestMatchers("/api/auth/csrf-token").permitAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static Follow create(User follower, User followee) {
return new Follow(follower, followee);
}

public boolean isOwnedBy(UUID userId) {
private boolean isOwnedBy(UUID userId) {
return follower.getId().equals(userId);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.codeit.team5.mopl.user.controller;

import com.codeit.team5.mopl.auth.security.details.MoplUserDetails;
import com.codeit.team5.mopl.binarycontent.support.MultipartFiles;
import com.codeit.team5.mopl.user.controller.api.UserApi;
import com.codeit.team5.mopl.user.dto.request.UserLockedUpdateRequest;
Expand All @@ -22,6 +23,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -56,12 +58,14 @@ public ResponseEntity<UserResponse> getUser(@PathVariable UUID userId) {
@Override
@PatchMapping(value = "/{userId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<UserResponse> updateUser(
@AuthenticationPrincipal MoplUserDetails userDetails,
@PathVariable UUID userId,
@Valid @RequestPart("request") UserUpdateRequest request,
@RequestPart(value = "image", required = false) MultipartFile image) {
log.info("User update request: PATCH /api/users/{}", userId);

UserResponse response = userService.update(userId, request, MultipartFiles.toImageResource(image));
UserResponse response = userService.update(
userDetails.getId(), userId, request, MultipartFiles.toImageResource(image));
Comment thread
dstle marked this conversation as resolved.

return ResponseEntity.ok(response);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.codeit.team5.mopl.user.controller.api;

import com.codeit.team5.mopl.auth.security.details.MoplUserDetails;
import com.codeit.team5.mopl.global.dto.suggestion.ErrorResponseSuggestion;
import com.codeit.team5.mopl.user.dto.request.UserLockedUpdateRequest;
import com.codeit.team5.mopl.user.dto.request.UserRegisterRequest;
Expand Down Expand Up @@ -81,6 +82,7 @@ class UpdateMultipartRequest {
encoding = @Encoding(name = "request", contentType = MediaType.APPLICATION_JSON_VALUE)
))
ResponseEntity<UserResponse> updateUser(
@Parameter(hidden = true) MoplUserDetails userDetails,
@Parameter(description = "사용자 ID", required = true) @PathVariable UUID userId,
@Parameter(hidden = true) UserUpdateRequest request,
@Parameter(hidden = true) MultipartFile image);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.codeit.team5.mopl.user.exception;

import java.util.Map;
import java.util.UUID;
import org.springframework.http.HttpStatus;

public class UserForbiddenException extends UserException {

public UserForbiddenException(UUID requesterId, UUID userId) {
super(HttpStatus.FORBIDDEN, "본인의 프로필만 수정할 수 있습니다.",
Map.of("requesterId", requesterId, "userId", userId));
}
}
13 changes: 10 additions & 3 deletions src/main/java/com/codeit/team5/mopl/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.codeit.team5.mopl.user.entity.User;
import com.codeit.team5.mopl.user.entity.UserRole;
import com.codeit.team5.mopl.user.exception.DuplicatedEmailException;
import com.codeit.team5.mopl.user.exception.UserForbiddenException;
import com.codeit.team5.mopl.user.exception.UserNotFoundException;
import com.codeit.team5.mopl.user.mapper.UserMapper;
import com.codeit.team5.mopl.user.repository.UserRepository;
Expand Down Expand Up @@ -75,15 +76,15 @@ public UserResponse getById(UUID userId) {
}

@Transactional
public UserResponse update(UUID userId, UserUpdateRequest request, FileRequest image) {
public UserResponse update(UUID currentUserId, UUID userId, UserUpdateRequest request, FileRequest image) {
validateOwner(currentUserId, userId);

User user = userRepository.findWithProfileImageById(userId)
.orElseThrow(() -> {
log.warn("User not found: userId={}", userId);
return new UserNotFoundException(userId);
});

// TODO: 인증 구현 후 본인 확인(현재 로그인 사용자 == userId) 추가, 불일치 시 403

user.updateName(request.name());
if (image != null) {
// TODO(고아 정리): 비정상적인 상태를 가진 BinaryContent를 배치로 정리 (DB/S3 누적 방지)
Expand Down Expand Up @@ -144,6 +145,12 @@ private BinaryContent storeProfileImage(UUID userId, FileRequest image) {
return profileImage;
}

private void validateOwner(UUID currentUserId, UUID userId) {
if (!currentUserId.equals(userId)) {
throw new UserForbiddenException(currentUserId, userId);
}
}

private User getUser(UUID userId) {
return userRepository.findById(userId)
.orElseThrow(() -> {
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ management:
health:
show-details: when-authorized

springdoc:
swagger-ui:
csrf:
enabled: true

# 이미지 저장소 전략 (구현체는 mopl.storage.type 값으로 선택)
# - local: 로컬/테스트용 저장
# - s3 : 배포용 S3 저장
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest(properties = {
"jwt.access-secret-key=abcdefghijklmnopqrstuvwxyz123456",
"jwt.refresh-secret-key=123456abcdefghijklmnopqrstuvwxyz",
"jwt.access-token-expiration-minutes=30",
"jwt.refresh-token-expiration-minutes=420"
})
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Import(TestcontainersConfiguration.class)
Expand Down
Loading
Loading