Skip to content

Commit b1c7c85

Browse files
committed
Feat&Ref: 파일 첨부 관련 기능 추가
1 parent 8d0a307 commit b1c7c85

File tree

12 files changed

+173
-36
lines changed

12 files changed

+173
-36
lines changed

src/main/java/com/back/domain/file/entity/AttachmentMapping.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
@Getter
1010
@NoArgsConstructor
1111
public class AttachmentMapping extends BaseEntity {
12-
@OneToOne(fetch = FetchType.LAZY, mappedBy = "attachmentMapping", cascade = CascadeType.ALL, orphanRemoval = true)
12+
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
13+
@JoinColumn(name = "attachment_id", nullable = false)
1314
private FileAttachment fileAttachment;
1415

1516
@Enumerated(EnumType.STRING)

src/main/java/com/back/domain/file/entity/FileAttachment.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
import lombok.NoArgsConstructor;
88
import org.springframework.web.multipart.MultipartFile;
99

10-
import java.util.ArrayList;
11-
import java.util.List;
12-
1310
@Entity
1411
@Getter
1512
@NoArgsConstructor
@@ -29,8 +26,7 @@ public class FileAttachment extends BaseEntity {
2926
@JoinColumn(name = "uploaded_by")
3027
private User user;
3128

32-
@OneToOne(fetch = FetchType.LAZY)
33-
@JoinColumn(name = "attachmentMapping_id")
29+
@OneToOne(mappedBy = "fileAttachment", fetch = FetchType.LAZY)
3430
private AttachmentMapping attachmentMapping;
3531

3632
public FileAttachment(

src/main/java/com/back/domain/file/repository/AttachmentMappingRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
public interface AttachmentMappingRepository extends JpaRepository<AttachmentMapping, Long> {
1111
List<AttachmentMapping> findAllByEntityTypeAndEntityId(EntityType entityType, Long entityId);
12+
Optional<AttachmentMapping> findByEntityTypeAndEntityId(EntityType entityType, Long entityId);
1213

1314
void deleteAllByEntityTypeAndEntityId(EntityType entityType, Long entityId);
1415
}

src/main/java/com/back/domain/file/repository/FileAttachmentRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
import com.back.domain.file.entity.FileAttachment;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import java.util.Optional;
7+
68
public interface FileAttachmentRepository extends JpaRepository<FileAttachment, Long> {
9+
Optional<FileAttachment> findByPublicURL(String publicUrl);
710
}

src/main/java/com/back/domain/user/account/controller/AccountController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import com.back.domain.board.post.dto.PostListResponse;
66
import com.back.domain.user.account.controller.docs.AccountControllerDocs;
77
import com.back.domain.user.account.dto.ChangePasswordRequest;
8-
import com.back.domain.user.account.dto.UpdateUserProfileRequest;
8+
import com.back.domain.user.account.dto.UserProfileRequest;
99
import com.back.domain.user.account.dto.UserDetailResponse;
1010
import com.back.domain.user.account.service.AccountService;
1111
import com.back.global.common.dto.RsData;
@@ -42,7 +42,7 @@ public ResponseEntity<RsData<UserDetailResponse>> getMyInfo(
4242
@PatchMapping
4343
public ResponseEntity<RsData<UserDetailResponse>> updateMyProfile(
4444
@AuthenticationPrincipal CustomUserDetails user,
45-
@Valid @RequestBody UpdateUserProfileRequest request
45+
@Valid @RequestBody UserProfileRequest request
4646
) {
4747
UserDetailResponse updated = accountService.updateUserProfile(user.getUserId(), request);
4848
return ResponseEntity

src/main/java/com/back/domain/user/account/controller/docs/AccountControllerDocs.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.back.domain.board.common.dto.PageResponse;
55
import com.back.domain.board.post.dto.PostListResponse;
66
import com.back.domain.user.account.dto.ChangePasswordRequest;
7-
import com.back.domain.user.account.dto.UpdateUserProfileRequest;
7+
import com.back.domain.user.account.dto.UserProfileRequest;
88
import com.back.domain.user.account.dto.UserDetailResponse;
99
import com.back.global.common.dto.RsData;
1010
import com.back.global.security.user.CustomUserDetails;
@@ -307,7 +307,7 @@ ResponseEntity<RsData<UserDetailResponse>> getMyInfo(
307307
})
308308
ResponseEntity<RsData<UserDetailResponse>> updateMyProfile(
309309
@AuthenticationPrincipal CustomUserDetails user,
310-
@Valid @RequestBody UpdateUserProfileRequest request
310+
@Valid @RequestBody UserProfileRequest request
311311
);
312312

313313
@Operation(

src/main/java/com/back/domain/user/account/dto/UpdateUserProfileRequest.java renamed to src/main/java/com/back/domain/user/account/dto/UserProfileRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* @param bio 사용자의 자기소개
1414
* @param birthDate 사용자의 생년월일
1515
*/
16-
public record UpdateUserProfileRequest(
16+
public record UserProfileRequest(
1717
@NotBlank @Size(max = 20) String nickname,
1818
String profileImageUrl,
1919
@Size(max = 255) String bio,

src/main/java/com/back/domain/user/account/service/AccountService.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
import com.back.domain.board.common.dto.PageResponse;
66
import com.back.domain.board.post.dto.PostListResponse;
77
import com.back.domain.board.post.repository.PostRepository;
8+
import com.back.domain.file.entity.AttachmentMapping;
9+
import com.back.domain.file.entity.EntityType;
10+
import com.back.domain.file.entity.FileAttachment;
11+
import com.back.domain.file.repository.AttachmentMappingRepository;
12+
import com.back.domain.file.repository.FileAttachmentRepository;
13+
import com.back.domain.file.service.FileService;
814
import com.back.domain.user.account.dto.ChangePasswordRequest;
9-
import com.back.domain.user.account.dto.UpdateUserProfileRequest;
15+
import com.back.domain.user.account.dto.UserProfileRequest;
1016
import com.back.domain.user.account.dto.UserDetailResponse;
1117
import com.back.domain.user.common.entity.User;
1218
import com.back.domain.user.common.entity.UserProfile;
@@ -23,6 +29,8 @@
2329
import org.springframework.stereotype.Service;
2430
import org.springframework.transaction.annotation.Transactional;
2531

32+
import java.util.Optional;
33+
2634
@Service
2735
@RequiredArgsConstructor
2836
@Transactional
@@ -31,6 +39,9 @@ public class AccountService {
3139
private final UserProfileRepository userProfileRepository;
3240
private final CommentRepository commentRepository;
3341
private final PostRepository postRepository;
42+
private final FileAttachmentRepository fileAttachmentRepository;
43+
private final AttachmentMappingRepository attachmentMappingRepository;
44+
private final FileService fileService;
3445
private final PasswordEncoder passwordEncoder;
3546

3647
/**
@@ -54,7 +65,7 @@ public UserDetailResponse getUserInfo(Long userId) {
5465
* 3. UserProfile 업데이트
5566
* 4. UserDetailResponse 변환 및 반환
5667
*/
57-
public UserDetailResponse updateUserProfile(Long userId, UpdateUserProfileRequest request) {
68+
public UserDetailResponse updateUserProfile(Long userId, UserProfileRequest request) {
5869

5970
// 사용자 조회 및 상태 검증
6071
User user = getValidUser(userId);
@@ -71,10 +82,48 @@ public UserDetailResponse updateUserProfile(Long userId, UpdateUserProfileReques
7182
profile.setBio(request.bio());
7283
profile.setBirthDate(request.birthDate());
7384

85+
// 프로필 이미지 및 매핑 업데이트
86+
updateProfileImage(userId, request.profileImageUrl());
87+
7488
// UserDetailResponse로 변환하여 반환
7589
return UserDetailResponse.from(user);
7690
}
7791

92+
/**
93+
* 프로필 이미지 및 매핑 교체 로직
94+
* - 기존 이미지(S3 + FileAttachment + Mapping) 삭제 후 새 매핑 생성
95+
*/
96+
private void updateProfileImage(Long userId, String newImageUrl) {
97+
98+
// 기존 매핑 및 파일 삭제
99+
attachmentMappingRepository.findByEntityTypeAndEntityId(EntityType.PROFILE, userId)
100+
.ifPresent(existingMapping -> {
101+
FileAttachment oldAttachment = existingMapping.getFileAttachment();
102+
if (oldAttachment != null) {
103+
fileService.deleteFile(oldAttachment.getId(), userId);
104+
}
105+
attachmentMappingRepository.delete(existingMapping);
106+
});
107+
108+
// 새 이미지가 없는 경우
109+
if (newImageUrl == null || newImageUrl.isBlank()) {
110+
return;
111+
}
112+
113+
// 새 파일 조회 및 검증
114+
FileAttachment newAttachment = fileAttachmentRepository
115+
.findByPublicURL(newImageUrl)
116+
.orElseThrow(() -> new CustomException(ErrorCode.FILE_NOT_FOUND));
117+
118+
if (!newAttachment.getUser().getId().equals(userId)) {
119+
throw new CustomException(ErrorCode.FILE_ACCESS_DENIED);
120+
}
121+
122+
// 새 매핑 생성 및 저장
123+
AttachmentMapping newMapping = new AttachmentMapping(newAttachment, EntityType.PROFILE, userId);
124+
attachmentMappingRepository.save(newMapping);
125+
}
126+
78127
/**
79128
* 비밀번호 변경 서비스
80129
* 1. 사용자 조회 및 상태 검증
@@ -114,6 +163,9 @@ public void deleteUser(Long userId) {
114163
// 사용자 조회 및 상태 검증
115164
User user = getValidUser(userId);
116165

166+
// 프로필 이미지 및 매핑 삭제
167+
deleteProfileImage(userId);
168+
117169
// 상태 변경 (soft delete)
118170
user.setUserStatus(UserStatus.DELETED);
119171

@@ -133,6 +185,20 @@ public void deleteUser(Long userId) {
133185
}
134186
}
135187

188+
/**
189+
* 프로필 이미지 및 매핑 삭제
190+
*/
191+
private void deleteProfileImage(Long userId) {
192+
attachmentMappingRepository.findByEntityTypeAndEntityId(EntityType.PROFILE, userId)
193+
.ifPresent(mapping -> {
194+
FileAttachment attachment = mapping.getFileAttachment();
195+
if (attachment != null) {
196+
fileService.deleteFile(attachment.getId(), userId);
197+
}
198+
attachmentMappingRepository.delete(mapping);
199+
});
200+
}
201+
136202
/**
137203
* 내 게시글 목록 조회 서비스
138204
* 1. 사용자 조회 및 상태 검증

src/test/java/com/back/domain/board/post/controller/PostControllerTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.back.domain.board.post.repository.PostRepository;
99
import com.back.domain.file.entity.FileAttachment;
1010
import com.back.domain.file.repository.FileAttachmentRepository;
11+
import com.back.domain.file.service.FileService;
1112
import com.back.domain.user.common.entity.User;
1213
import com.back.domain.user.common.entity.UserProfile;
1314
import com.back.domain.user.common.enums.UserStatus;
@@ -19,6 +20,7 @@
1920
import org.springframework.beans.factory.annotation.Autowired;
2021
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
2122
import org.springframework.boot.test.context.SpringBootTest;
23+
import org.springframework.boot.test.mock.mockito.MockBean;
2224
import org.springframework.http.MediaType;
2325
import org.springframework.mock.web.MockMultipartFile;
2426
import org.springframework.security.crypto.password.PasswordEncoder;
@@ -64,6 +66,9 @@ class PostControllerTest {
6466
@Autowired
6567
private ObjectMapper objectMapper;
6668

69+
@MockBean
70+
private FileService fileService;
71+
6772
private String generateAccessToken(User user) {
6873
return testJwtTokenProvider.createAccessToken(user.getId(), user.getUsername(), user.getRole().name());
6974
}

src/test/java/com/back/domain/board/post/service/PostServiceTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.back.domain.file.entity.FileAttachment;
1616
import com.back.domain.file.repository.AttachmentMappingRepository;
1717
import com.back.domain.file.repository.FileAttachmentRepository;
18+
import com.back.domain.file.service.FileService;
1819
import com.back.domain.user.common.entity.User;
1920
import com.back.domain.user.common.entity.UserProfile;
2021
import com.back.domain.user.common.enums.UserStatus;
@@ -25,6 +26,7 @@
2526
import org.junit.jupiter.api.Test;
2627
import org.springframework.beans.factory.annotation.Autowired;
2728
import org.springframework.boot.test.context.SpringBootTest;
29+
import org.springframework.boot.test.mock.mockito.MockBean;
2830
import org.springframework.data.domain.PageRequest;
2931
import org.springframework.data.domain.Pageable;
3032
import org.springframework.data.domain.Sort;
@@ -59,6 +61,9 @@ class PostServiceTest {
5961
@Autowired
6062
private AttachmentMappingRepository attachmentMappingRepository;
6163

64+
@MockBean
65+
private FileService fileService;
66+
6267
// ====================== 게시글 생성 테스트 ======================
6368

6469
@Test

0 commit comments

Comments
 (0)