diff --git a/src/main/java/com/leets/X/domain/image/domain/Image.java b/src/main/java/com/leets/X/domain/image/domain/Image.java index 12ba864..6a9c054 100644 --- a/src/main/java/com/leets/X/domain/image/domain/Image.java +++ b/src/main/java/com/leets/X/domain/image/domain/Image.java @@ -2,6 +2,7 @@ import com.leets.X.domain.image.dto.request.ImageDto; import com.leets.X.domain.post.domain.Post; +import com.leets.X.domain.user.domain.User; import com.leets.X.global.common.domain.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; @@ -22,6 +23,10 @@ public class Image extends BaseTimeEntity { private String url; + @OneToOne + @JoinColumn(name = "user_id") + private User user; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_Id") private Post post; @@ -34,5 +39,17 @@ public static Image from(ImageDto dto, Post post) { .build(); } + public static Image from(ImageDto dto, User user) { + return Image.builder() + .name(dto.name()) + .url(dto.url()) + .user(user) + .build(); + } + + public void update(ImageDto dto) { + this.name = dto.name(); + this.url = dto.url(); + } } diff --git a/src/main/java/com/leets/X/domain/image/service/ImageService.java b/src/main/java/com/leets/X/domain/image/service/ImageService.java index ad7cf2c..4c98498 100644 --- a/src/main/java/com/leets/X/domain/image/service/ImageService.java +++ b/src/main/java/com/leets/X/domain/image/service/ImageService.java @@ -4,6 +4,7 @@ import com.leets.X.domain.image.dto.request.ImageDto; import com.leets.X.domain.image.repository.ImageRepository; import com.leets.X.domain.post.domain.Post; +import com.leets.X.domain.user.domain.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,4 +31,19 @@ public List save(List file, Post post) throws IOException return imageRepository.saveAll(imageList); } + @Transactional + public Image save(MultipartFile image, User user) throws IOException { + ImageDto imageDto = imageUploadService.uploadImage(image); + return imageRepository.save(Image.from(imageDto, user)); + } + + public ImageDto getImage(MultipartFile image) throws IOException { + return imageUploadService.uploadImage(image); + } + + @Transactional + public void delete(Image image) { + imageRepository.delete(image); + } + } diff --git a/src/main/java/com/leets/X/domain/image/service/ImageUploadService.java b/src/main/java/com/leets/X/domain/image/service/ImageUploadService.java index eba5bc8..b061c9a 100644 --- a/src/main/java/com/leets/X/domain/image/service/ImageUploadService.java +++ b/src/main/java/com/leets/X/domain/image/service/ImageUploadService.java @@ -63,6 +63,39 @@ public List uploadImages(List files) throws IOException return images; } + + public ImageDto uploadImage(MultipartFile image) throws IOException { + + String originalName = image.getOriginalFilename(); + String fileName = generateFileName(originalName); + + try { + // PutObjectRequest 생성 및 설정 + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(fileName) + .contentType(image.getContentType()) + .build(); + + // S3에 파일 업로드 + PutObjectResponse response = s3Client.putObject( + putObjectRequest, + RequestBody.fromInputStream(image.getInputStream(), image.getSize()) + ); + + // 업로드 성공 여부 확인 + if (response.sdkHttpResponse().isSuccessful()) { + // 업로드된 파일의 URL을 ImageDto로 추가 + return ImageDto.of(originalName, generateFileUrl(fileName)); + } else { + throw new S3UploadException(); + } + } catch (S3Exception e) { + throw new S3UploadException(); + } + } + + // S3에 저장된 파일 URL 생성 private String generateFileUrl(String fileName) { return String.format("https://%s.s3.%s.amazonaws.com/%s", bucketName, region, fileName); diff --git a/src/main/java/com/leets/X/domain/post/domain/Post.java b/src/main/java/com/leets/X/domain/post/domain/Post.java index 9721497..d16ebe1 100644 --- a/src/main/java/com/leets/X/domain/post/domain/Post.java +++ b/src/main/java/com/leets/X/domain/post/domain/Post.java @@ -48,10 +48,15 @@ public class Post extends BaseTimeEntity { private Integer views; + @Enumerated(EnumType.STRING) private IsDeleted isDeleted; private LocalDateTime deletedAt; + private Long replyCount; + + private Long repostCount; + // 좋아요 수를 관리하기 위한 필드 @Column(name = "like_count") @@ -111,5 +116,24 @@ public static Post create(User user, String content, Post parent) { public void addImage(List images) { this.images.addAll(images); // 기존 리스트에 이미지 추가 } + + public void increaseReplyCount() { + this.replyCount++; + } + + public void decreaseReplyCount() { + if(this.replyCount > 0L) { + this.replyCount--; + } + } + + public void increaseRepostCount(){ + this.repostCount++; + } + public void decreaseRepostCount() { + if(this.repostCount > 0L) { + this.repostCount--; + } + } } diff --git a/src/main/java/com/leets/X/domain/post/dto/mapper/PostMapper.java b/src/main/java/com/leets/X/domain/post/dto/mapper/PostMapper.java index 8e553f1..247e258 100644 --- a/src/main/java/com/leets/X/domain/post/dto/mapper/PostMapper.java +++ b/src/main/java/com/leets/X/domain/post/dto/mapper/PostMapper.java @@ -1,5 +1,7 @@ package com.leets.X.domain.post.dto.mapper; +import com.leets.X.domain.image.domain.Image; +import com.leets.X.domain.image.dto.request.ImageDto; import com.leets.X.domain.image.dto.response.ImageResponse; import com.leets.X.domain.like.repository.LikeRepository; import com.leets.X.domain.post.domain.Post; @@ -24,6 +26,8 @@ public PostResponseDto toPostResponseDto(Post post, User user, LikeRepository li .isDeleted(post.getIsDeleted()) .createdAt(post.getCreatedAt()) .user(toPostUserResponse(post.getUser())) + .replyCount(post.getReplyCount()) + .repostCount(post.getRepostCount()) .likeCount(post.getLikeCount()) .isLikedByUser(isLikedByUser(post, user, likeRepository)) .postType(postType) @@ -34,7 +38,7 @@ public PostResponseDto toPostResponseDto(Post post, User user, LikeRepository li .build(); } - public ParentPostResponseDto toParentPostResponseDto(Post post, User user, LikeRepository likeRepository, Type postType, Long repostingUserId) { + public ParentPostResponseDto toParentPostResponseDto(Post post, User user, LikeRepository likeRepository, Type postType, Long repostingUserId, String reposingUserName) { return ParentPostResponseDto.builder() .id(post.getId()) .content(post.getContent()) @@ -42,8 +46,11 @@ public ParentPostResponseDto toParentPostResponseDto(Post post, User user, LikeR .isDeleted(post.getIsDeleted()) .createdAt(post.getCreatedAt()) .user(toPostUserResponse(post.getUser())) + .replyCount(post.getReplyCount()) + .repostCount(post.getRepostCount()) .likeCount(post.getLikeCount()) .isLikedByUser(isLikedByUser(post, user, likeRepository)) + .repostingUserName(reposingUserName) .repostingUserId(repostingUserId) .postType(postType) .myPost(isMyPost(post, user)) @@ -53,7 +60,12 @@ public ParentPostResponseDto toParentPostResponseDto(Post post, User user, LikeR } public PostUserResponse toPostUserResponse(User user) { - return PostUserResponse.from(user); + ImageDto dto = null; + Image image = user.getImage(); + if(image != null){ + dto = ImageDto.of(image.getName(), image.getUrl()); + } + return PostUserResponse.from(user, dto); } public List toImageResponse(Post post) { diff --git a/src/main/java/com/leets/X/domain/post/dto/response/ParentPostResponseDto.java b/src/main/java/com/leets/X/domain/post/dto/response/ParentPostResponseDto.java index 37ac13d..828363e 100644 --- a/src/main/java/com/leets/X/domain/post/dto/response/ParentPostResponseDto.java +++ b/src/main/java/com/leets/X/domain/post/dto/response/ParentPostResponseDto.java @@ -16,7 +16,10 @@ public record ParentPostResponseDto( IsDeleted isDeleted, LocalDateTime createdAt, PostUserResponse user, + Long replyCount, + Long repostCount, Long likeCount, + String repostingUserName, Long repostingUserId, Type postType, Boolean myPost, diff --git a/src/main/java/com/leets/X/domain/post/dto/response/PostResponseDto.java b/src/main/java/com/leets/X/domain/post/dto/response/PostResponseDto.java index 45dea72..b078424 100644 --- a/src/main/java/com/leets/X/domain/post/dto/response/PostResponseDto.java +++ b/src/main/java/com/leets/X/domain/post/dto/response/PostResponseDto.java @@ -17,6 +17,8 @@ public record PostResponseDto( IsDeleted isDeleted, LocalDateTime createdAt, PostUserResponse user, + Long replyCount, + Long repostCount, Long likeCount, Boolean isLikedByUser, // 좋아요 여부 확인 Type postType, diff --git a/src/main/java/com/leets/X/domain/post/dto/response/PostUserResponse.java b/src/main/java/com/leets/X/domain/post/dto/response/PostUserResponse.java index 9e303fb..5b59bc6 100644 --- a/src/main/java/com/leets/X/domain/post/dto/response/PostUserResponse.java +++ b/src/main/java/com/leets/X/domain/post/dto/response/PostUserResponse.java @@ -1,18 +1,22 @@ package com.leets.X.domain.post.dto.response; +import com.leets.X.domain.image.dto.request.ImageDto; import com.leets.X.domain.user.domain.User; public record PostUserResponse( Long userId, String name, - String customId + String customId, + ImageDto profileImage + ) { - public static PostUserResponse from(User user) { + public static PostUserResponse from(User user, ImageDto profileImage) { return new PostUserResponse( user.getId(), user.getName(), - user.getCustomId() + user.getCustomId(), + profileImage ); } } diff --git a/src/main/java/com/leets/X/domain/post/service/PostService.java b/src/main/java/com/leets/X/domain/post/service/PostService.java index 1c8609d..ceab5db 100644 --- a/src/main/java/com/leets/X/domain/post/service/PostService.java +++ b/src/main/java/com/leets/X/domain/post/service/PostService.java @@ -11,7 +11,6 @@ import com.leets.X.domain.post.dto.request.PostRequestDTO; import com.leets.X.domain.post.dto.response.ParentPostResponseDto; import com.leets.X.domain.post.dto.response.PostResponseDto; -import com.leets.X.domain.post.dto.response.PostUserResponse; import com.leets.X.domain.post.exception.AlreadyLikedException; import com.leets.X.domain.post.exception.NotLikedException; import com.leets.X.domain.post.exception.PostNotFoundException; @@ -52,7 +51,7 @@ public List getAllParentPosts(String email) { return posts.stream() .filter(post -> !followedUserIds.contains(post.getUser().getId())) .map(post -> { - return postMapper.toParentPostResponseDto(post, user, likeRepository, Type.POST, null); + return postMapper.toParentPostResponseDto(post, user, likeRepository, Type.POST, null, null); }) .collect(Collectors.toList()); } @@ -141,6 +140,8 @@ public void createReply(Long parentId, PostRequestDTO postRequestDTO, List images = imageService.save(files, savedReply); savedReply.addImage(images); @@ -159,6 +160,7 @@ public void deletePost(Long postId, String email) { } post.delete(); + post.getParent().decreaseReplyCount(); } // 좋아요 취소 @@ -190,10 +192,10 @@ public Post findPost(Long postId) { .orElseThrow(PostNotFoundException::new); } - public PostUserResponse findUser(String email) { - User user = userService.find(email); - return PostUserResponse.from(user); - } +// public PostUserResponse findUser(String email) { +// User user = userService.find(email); +// return PostUserResponse.from(user); +// } } diff --git a/src/main/java/com/leets/X/domain/post/service/RepostService.java b/src/main/java/com/leets/X/domain/post/service/RepostService.java index e03e971..0e52367 100644 --- a/src/main/java/com/leets/X/domain/post/service/RepostService.java +++ b/src/main/java/com/leets/X/domain/post/service/RepostService.java @@ -38,6 +38,7 @@ public void rePost(Long postId, String email) { Repost repost = repostRepository.save(Repost.of(user, post)); user.addRepost(repost); + post.increaseReplyCount(); } public List getFollowingPost(String email) { @@ -81,14 +82,14 @@ private void check(Long userId, Long postId){ private List getUserPosts(User user, boolean isOwner) { return user.getPosts().stream() .filter(post -> post.getParent() == null) - .map(post -> postMapper.toParentPostResponseDto(post, user, likeRepository, Type.POST, null)) + .map(post -> postMapper.toParentPostResponseDto(post, user, likeRepository, Type.POST, null, null)) .collect(Collectors.toList()); } // 사용자의 리포스트를 가져오는 메서드 private List getUserReposts(User user, boolean isOwner) { return user.getReposts().stream() - .map(repost -> postMapper.toParentPostResponseDto(repost.getPost(), user, likeRepository, Type.REPOST, repost.getUser().getId())) + .map(repost -> postMapper.toParentPostResponseDto(repost.getPost(), user, likeRepository, Type.REPOST, repost.getUser().getId(), repost.getUser().getName())) .collect(Collectors.toList()); } @@ -98,7 +99,7 @@ private List getFollowingUsersPosts(User user) { .map(Follow::getFollowed) .flatMap(followedUser -> followedUser.getPosts().stream()) .filter(post -> post.getParent() == null) - .map(post -> postMapper.toParentPostResponseDto(post, user, likeRepository, Type.POST, null)) + .map(post -> postMapper.toParentPostResponseDto(post, user, likeRepository, Type.POST, null, null)) .collect(Collectors.toList()); } @@ -107,7 +108,7 @@ private List getFollowingUsersReposts(User user) { return user.getFollowingList().stream() .map(Follow::getFollowed) .flatMap(followedUser -> followedUser.getReposts().stream()) - .map(repost -> postMapper.toParentPostResponseDto(repost.getPost(), user, likeRepository, Type.REPOST, repost.getUser().getId())) + .map(repost -> postMapper.toParentPostResponseDto(repost.getPost(), user, likeRepository, Type.REPOST, repost.getUser().getId(), repost.getUser().getName())) .collect(Collectors.toList()); } diff --git a/src/main/java/com/leets/X/domain/user/controller/UserController.java b/src/main/java/com/leets/X/domain/user/controller/UserController.java index b021151..3bd8a9d 100644 --- a/src/main/java/com/leets/X/domain/user/controller/UserController.java +++ b/src/main/java/com/leets/X/domain/user/controller/UserController.java @@ -1,11 +1,11 @@ package com.leets.X.domain.user.controller; +import com.leets.X.domain.user.domain.enums.LoginStatus; import com.leets.X.domain.user.dto.request.UserInitializeRequest; import com.leets.X.domain.user.dto.request.UserSocialLoginRequest; import com.leets.X.domain.user.dto.request.UserUpdateRequest; import com.leets.X.domain.user.dto.response.UserProfileResponse; import com.leets.X.domain.user.dto.response.UserSocialLoginResponse; -import com.leets.X.domain.user.domain.enums.LoginStatus; import com.leets.X.domain.user.service.UserService; import com.leets.X.global.common.response.ResponseDto; import io.swagger.v3.oas.annotations.Operation; @@ -13,8 +13,12 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; import static com.leets.X.domain.user.controller.ResponseMessage.*; @@ -55,10 +59,12 @@ public ResponseDto getUserProfile(@PathVariable Long userId return ResponseDto.response(GET_PROFILE_SUCCESS.getCode(), GET_PROFILE_SUCCESS.getMessage(), userService.getProfile(userId, email)); } - @PatchMapping("/profile") + @PatchMapping(value="/profile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "유저 프로필 수정") - public ResponseDto updateProfile(@RequestBody @Valid UserUpdateRequest request, @AuthenticationPrincipal @Parameter(hidden = true) String email){ - userService.updateProfile(request, email); + public ResponseDto updateProfile(@RequestPart @Valid UserUpdateRequest request, + @RequestPart(value = "image", required = false) MultipartFile image, + @AuthenticationPrincipal @Parameter(hidden = true) String email) throws IOException { + userService.updateProfile(request, image, email); return ResponseDto.response(PROFILE_UPDATE_SUCCESS.getCode(), PROFILE_UPDATE_SUCCESS.getMessage()); } diff --git a/src/main/java/com/leets/X/domain/user/domain/User.java b/src/main/java/com/leets/X/domain/user/domain/User.java index f819441..affb771 100644 --- a/src/main/java/com/leets/X/domain/user/domain/User.java +++ b/src/main/java/com/leets/X/domain/user/domain/User.java @@ -1,6 +1,7 @@ package com.leets.X.domain.user.domain; import com.leets.X.domain.follow.domain.Follow; +import com.leets.X.domain.image.domain.Image; import com.leets.X.domain.like.domain.Like; import com.leets.X.domain.post.domain.Post; import com.leets.X.domain.post.domain.Repost; @@ -54,6 +55,9 @@ public class User extends BaseTimeEntity { private long followingCount = 0L; + @OneToOne(mappedBy = "user") + private Image image; + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true) private List posts = new ArrayList<>(); @@ -74,11 +78,12 @@ public void initProfile(UserInitializeRequest dto){ this.customId = dto.customId(); } - public void update(UserUpdateRequest dto){ + public void update(UserUpdateRequest dto, Image image){ this.name = dto.name(); this.introduce = dto.introduce(); this.location = dto.location(); this.webSite = dto.webSite(); + this.image = image; } public void addFollower(Follow follow) { diff --git a/src/main/java/com/leets/X/domain/user/dto/response/UserProfileResponse.java b/src/main/java/com/leets/X/domain/user/dto/response/UserProfileResponse.java index 8352d48..eb2d74f 100644 --- a/src/main/java/com/leets/X/domain/user/dto/response/UserProfileResponse.java +++ b/src/main/java/com/leets/X/domain/user/dto/response/UserProfileResponse.java @@ -1,5 +1,6 @@ package com.leets.X.domain.user.dto.response; +import com.leets.X.domain.image.dto.request.ImageDto; import com.leets.X.domain.user.domain.User; import lombok.Builder; @@ -16,10 +17,11 @@ public record UserProfileResponse( Long followingCount, Boolean isFollowing, LocalDateTime createdAt, - LocalDateTime updatedAt + LocalDateTime updatedAt, + ImageDto profileImage ) { // 정적 팩토리 메서드 - public static UserProfileResponse from(User user, Boolean isMyProfile, Boolean isFollowing) { + public static UserProfileResponse from(User user, Boolean isMyProfile, Boolean isFollowing, ImageDto image) { return UserProfileResponse.builder() .userId(user.getId()) .isMyProfile(isMyProfile) @@ -31,6 +33,7 @@ public static UserProfileResponse from(User user, Boolean isMyProfile, Boolean i .isFollowing(isFollowing) .createdAt(user.getCreatedAt()) .updatedAt(user.getUpdatedAt()) + .profileImage(image) .build(); } } diff --git a/src/main/java/com/leets/X/domain/user/service/UserService.java b/src/main/java/com/leets/X/domain/user/service/UserService.java index 88f7966..6045138 100644 --- a/src/main/java/com/leets/X/domain/user/service/UserService.java +++ b/src/main/java/com/leets/X/domain/user/service/UserService.java @@ -1,6 +1,9 @@ package com.leets.X.domain.user.service; import com.leets.X.domain.follow.domain.Follow; +import com.leets.X.domain.image.domain.Image; +import com.leets.X.domain.image.dto.request.ImageDto; +import com.leets.X.domain.image.service.ImageService; import com.leets.X.domain.user.domain.User; import com.leets.X.domain.user.dto.request.UserInitializeRequest; import com.leets.X.domain.user.dto.request.UserUpdateRequest; @@ -17,7 +20,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; import static com.leets.X.domain.user.domain.enums.LoginStatus.LOGIN; @@ -29,6 +34,7 @@ public class UserService { private final AuthService authService; + private final ImageService imageService; private final JwtProvider jwtProvider; private final UserRepository userRepository; @@ -64,10 +70,18 @@ public void initProfile(UserInitializeRequest dto, String email){ * 프로필 수정 */ @Transactional - public void updateProfile(UserUpdateRequest dto, String email){ + public void updateProfile(UserUpdateRequest dto, MultipartFile image, String email) throws IOException { User user = find(email); - - user.update(dto); + Image savedImage = user.getImage(); + // 이미지가 없다면 새로 생성해서 저장 + if(savedImage== null){ + savedImage = imageService.save(image, user); + } else if (image != null) { + ImageDto imageDto = imageService.getImage(image); + savedImage.update(imageDto); + } + // 기존 이미지가 있다면 ImageDto를 생성해서 기존 이미지를 업데이트 + user.update(dto, savedImage); } public UserProfileResponse getProfile(Long userId, String email){ @@ -75,7 +89,7 @@ public UserProfileResponse getProfile(Long userId, String email){ .orElseThrow(UserNotFoundException::new); boolean isMyProfile = user.getEmail().equals(email); boolean isFollowing = checkFollowing(user, email); - return UserProfileResponse.from(user, isMyProfile, isFollowing); + return UserProfileResponse.from(user, isMyProfile, isFollowing, getImage(user)); } // // @Transactional @@ -116,6 +130,14 @@ private boolean checkFollowing(User target, String email){ .anyMatch(follow -> follow.getFollower().getId().equals(source.getId())); } + private ImageDto getImage(User user){ + if(user.getImage() != null){ + Image image = user.getImage(); + return ImageDto.of(image.getName(), image.getUrl()); + } + return null; + } + /* * userRepository에서 사용자를 검색하는 메서드 * 공통으로 사용되는 부분이 많기 때문에 별도로 분리 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e894830..91b55a9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,11 @@ spring: profiles: active: local + servlet: + multipart: + enabled: true + max-file-size: 10MB + max-request-size: 10MB springdoc: swagger-ui: