From 0181656964cf8e339abfa94b9cd14a0ed0c58ca2 Mon Sep 17 00:00:00 2001 From: kwonhee1 Date: Fri, 10 Oct 2025 16:59:08 +0900 Subject: [PATCH] feat : social like, social list --- .../demo/follow/FollowRepository.java | 2 + .../follow/SelectSocialProfileService.java | 24 ++++++++ .../social/controller/SocialController.java | 18 ++++-- .../demo/social/dto/ResponseSocialDto.java | 16 ++++-- .../demo/social/dto/SocialLikeDto.java | 20 +++++++ .../demo/social/dto/SocialListDto.java | 25 ++++++++ .../demo/social/entity/SocialEntity.java | 3 + .../demo/social/entity/SocialLikeEntity.java | 28 +++++++++ .../IsSelectSocialLikedListInterface.java | 7 +++ .../repository/SocialLikeRepository.java | 10 ++++ .../social/repository/SocialRepository.java | 18 +++++- .../demo/social/service/SocialService.java | 57 +++++++++++++++++-- .../user/response/UserSocialProfileDto.java | 29 ++++++++++ .../demo/user/repository/UserRepository.java | 2 +- 14 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 src/main/java/NextLevel/demo/follow/SelectSocialProfileService.java create mode 100644 src/main/java/NextLevel/demo/social/dto/SocialLikeDto.java create mode 100644 src/main/java/NextLevel/demo/social/dto/SocialListDto.java create mode 100644 src/main/java/NextLevel/demo/social/entity/SocialLikeEntity.java create mode 100644 src/main/java/NextLevel/demo/social/repository/IsSelectSocialLikedListInterface.java create mode 100644 src/main/java/NextLevel/demo/social/repository/SocialLikeRepository.java create mode 100644 src/main/java/NextLevel/demo/user/dto/user/response/UserSocialProfileDto.java diff --git a/src/main/java/NextLevel/demo/follow/FollowRepository.java b/src/main/java/NextLevel/demo/follow/FollowRepository.java index 3f3c03a..a63a34f 100644 --- a/src/main/java/NextLevel/demo/follow/FollowRepository.java +++ b/src/main/java/NextLevel/demo/follow/FollowRepository.java @@ -11,4 +11,6 @@ public interface FollowRepository extends JpaRepository { @Query("select f from FollowEntity f where f.user.id = :userId and f.target.id = :targetId") Optional findByUserIdAndTargetId(@Param("userId") Long userId, @Param("targetId") Long targetId); + @Query("select count(f.id) from FollowEntity f where f.target.id = :userId") + Long followCount(@Param("userId") Long userId); } diff --git a/src/main/java/NextLevel/demo/follow/SelectSocialProfileService.java b/src/main/java/NextLevel/demo/follow/SelectSocialProfileService.java new file mode 100644 index 0000000..f2c1f11 --- /dev/null +++ b/src/main/java/NextLevel/demo/follow/SelectSocialProfileService.java @@ -0,0 +1,24 @@ +package NextLevel.demo.follow; + +import NextLevel.demo.user.dto.user.response.UserSocialProfileDto; +import NextLevel.demo.user.entity.UserEntity; +import NextLevel.demo.user.service.UserValidateService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class SelectSocialProfileService { + + private final FollowRepository followRepository; + private final UserValidateService validateService; + + public UserSocialProfileDto selectUserSocialProfile(long userId) { + UserEntity user = validateService.findUserWithUserId(userId); + return selectUserSocialProfile(user); + } + + public UserSocialProfileDto selectUserSocialProfile(UserEntity user) { + return UserSocialProfileDto.of(user, followRepository.followCount(user.getId())); + } +} diff --git a/src/main/java/NextLevel/demo/social/controller/SocialController.java b/src/main/java/NextLevel/demo/social/controller/SocialController.java index 78c6aea..31f1b20 100644 --- a/src/main/java/NextLevel/demo/social/controller/SocialController.java +++ b/src/main/java/NextLevel/demo/social/controller/SocialController.java @@ -2,6 +2,7 @@ import NextLevel.demo.common.SuccessResponse; import NextLevel.demo.social.dto.RequestSocialCreateDto; +import NextLevel.demo.social.dto.SocialLikeDto; import NextLevel.demo.social.service.SocialService; import NextLevel.demo.util.jwt.JWTUtil; import lombok.RequiredArgsConstructor; @@ -15,29 +16,36 @@ public class SocialController { private final SocialService socialService; - @PostMapping("/api1/social") + @PostMapping("/social/social") public ResponseEntity create(@ModelAttribute RequestSocialCreateDto dto) { dto.setUserId(JWTUtil.getUserIdFromSecurityContext()); socialService.create(dto, null); return ResponseEntity.ok().body(new SuccessResponse("success", null)); } - @PutMapping("/api1/social") + @PutMapping("/social/social") public ResponseEntity update(@ModelAttribute RequestSocialCreateDto dto) { dto.setUserId(JWTUtil.getUserIdFromSecurityContext()); socialService.update(dto, null); return ResponseEntity.ok().body(new SuccessResponse("success", null)); } - @DeleteMapping("/api1/social/{socialId}") + @DeleteMapping("/social/social/{socialId}") public ResponseEntity delete(@PathVariable("socialId") Long socialId) { socialService.delete(socialId, JWTUtil.getUserIdFromSecurityContext()); return ResponseEntity.ok().body(new SuccessResponse("success", null)); } @GetMapping("/public/social/{userId}") - public ResponseEntity list(@PathVariable("userId") Long userId) { - return ResponseEntity.ok().body(new SuccessResponse("success", socialService.list(userId))); + public ResponseEntity list(@PathVariable("userId") Long targetUserId) { + return ResponseEntity.ok().body(new SuccessResponse("success", socialService.list(targetUserId,JWTUtil.getUserIdFromSecurityContextCanNULL()))); + } + + @PostMapping("/social/social/like") + public ResponseEntity socialLike(@RequestBody SocialLikeDto dto) { + dto.setUserId(JWTUtil.getUserIdFromSecurityContext()); + socialService.socialLike(dto); + return ResponseEntity.ok().body(new SuccessResponse("success", null)); } } diff --git a/src/main/java/NextLevel/demo/social/dto/ResponseSocialDto.java b/src/main/java/NextLevel/demo/social/dto/ResponseSocialDto.java index fc592b2..47b2ac0 100644 --- a/src/main/java/NextLevel/demo/social/dto/ResponseSocialDto.java +++ b/src/main/java/NextLevel/demo/social/dto/ResponseSocialDto.java @@ -3,6 +3,7 @@ import NextLevel.demo.img.ImgDto; import NextLevel.demo.social.entity.SocialEntity; import NextLevel.demo.social.entity.SocialImgEntity; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -18,13 +19,16 @@ public class ResponseSocialDto { private Long id; private String text; private List imgs; + private Long socialLikeCount; + @JsonProperty("isLiked") + private boolean isSocialLike; // 내 좋아요 여부 - public static ResponseSocialDto of(SocialEntity entity) { - ResponseSocialDto dto = new ResponseSocialDto(); - dto.id = entity.getId(); - dto.text = entity.getText(); - dto.imgs = entity.getImgs().stream().map(SocialImgEntity::getImg).map(ImgDto::new).toList(); - return dto; + public ResponseSocialDto(SocialEntity entity, Long totalLikeCount, Long myLikeCount) { + id = entity.getId(); + text = entity.getText(); + imgs = entity.getImgs().stream().map(SocialImgEntity::getImg).map(ImgDto::new).toList(); + socialLikeCount = totalLikeCount != null ? totalLikeCount : 0L; + isSocialLike = myLikeCount != null && myLikeCount.equals(1L); } } diff --git a/src/main/java/NextLevel/demo/social/dto/SocialLikeDto.java b/src/main/java/NextLevel/demo/social/dto/SocialLikeDto.java new file mode 100644 index 0000000..503d540 --- /dev/null +++ b/src/main/java/NextLevel/demo/social/dto/SocialLikeDto.java @@ -0,0 +1,20 @@ +package NextLevel.demo.social.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Getter +@Setter +public class SocialLikeDto { + + @NotNull + private Long socialId; + + private Long userId; + + @NotNull + private Boolean like; +} diff --git a/src/main/java/NextLevel/demo/social/dto/SocialListDto.java b/src/main/java/NextLevel/demo/social/dto/SocialListDto.java new file mode 100644 index 0000000..984f73f --- /dev/null +++ b/src/main/java/NextLevel/demo/social/dto/SocialListDto.java @@ -0,0 +1,25 @@ +package NextLevel.demo.social.dto; + +import NextLevel.demo.user.dto.user.response.UserSocialProfileDto; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@NoArgsConstructor +@Getter +@Setter +public class SocialListDto { + + private UserSocialProfileDto user; + private List socials; + + public static SocialListDto of(UserSocialProfileDto user, List socials) { + SocialListDto dto = new SocialListDto(); + dto.user = user; + dto.socials = socials; + return dto; + } + +} diff --git a/src/main/java/NextLevel/demo/social/entity/SocialEntity.java b/src/main/java/NextLevel/demo/social/entity/SocialEntity.java index ad38478..1e1585f 100644 --- a/src/main/java/NextLevel/demo/social/entity/SocialEntity.java +++ b/src/main/java/NextLevel/demo/social/entity/SocialEntity.java @@ -31,6 +31,9 @@ public class SocialEntity extends BasedEntity { @JoinColumn(name = "user_id") private UserEntity user; + @OneToMany(mappedBy = "social", fetch = FetchType.LAZY, cascade = {}) + private List likes; + public void update(RequestSocialCreateDto dto) { if(dto.getText()!=null && !dto.getText().isEmpty()) this.text = dto.getText(); diff --git a/src/main/java/NextLevel/demo/social/entity/SocialLikeEntity.java b/src/main/java/NextLevel/demo/social/entity/SocialLikeEntity.java new file mode 100644 index 0000000..ddf14be --- /dev/null +++ b/src/main/java/NextLevel/demo/social/entity/SocialLikeEntity.java @@ -0,0 +1,28 @@ +package NextLevel.demo.social.entity; + +import NextLevel.demo.project.project.entity.ProjectEntity; +import NextLevel.demo.user.entity.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "social_like" , uniqueConstraints = {@UniqueConstraint(name="socialLikeUniqueConstraint", columnNames = {"user_id", "social_id"})}) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class SocialLikeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "social_id", nullable = false) + private SocialEntity social; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private UserEntity user; + +} diff --git a/src/main/java/NextLevel/demo/social/repository/IsSelectSocialLikedListInterface.java b/src/main/java/NextLevel/demo/social/repository/IsSelectSocialLikedListInterface.java new file mode 100644 index 0000000..f336fde --- /dev/null +++ b/src/main/java/NextLevel/demo/social/repository/IsSelectSocialLikedListInterface.java @@ -0,0 +1,7 @@ +package NextLevel.demo.social.repository; + +public interface IsSelectSocialLikedListInterface { + Long getSocialId(); + Long getTotalLikeCount(); + Long getMyLikeCount(); +} diff --git a/src/main/java/NextLevel/demo/social/repository/SocialLikeRepository.java b/src/main/java/NextLevel/demo/social/repository/SocialLikeRepository.java new file mode 100644 index 0000000..9a5e027 --- /dev/null +++ b/src/main/java/NextLevel/demo/social/repository/SocialLikeRepository.java @@ -0,0 +1,10 @@ +package NextLevel.demo.social.repository; + +import NextLevel.demo.social.entity.SocialLikeEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface SocialLikeRepository extends JpaRepository { + Optional findByUserIdAndSocialId(Long userId, Long socialId); +} diff --git a/src/main/java/NextLevel/demo/social/repository/SocialRepository.java b/src/main/java/NextLevel/demo/social/repository/SocialRepository.java index b08b8ec..c0969d2 100644 --- a/src/main/java/NextLevel/demo/social/repository/SocialRepository.java +++ b/src/main/java/NextLevel/demo/social/repository/SocialRepository.java @@ -1,5 +1,6 @@ package NextLevel.demo.social.repository; +import NextLevel.demo.social.dto.ResponseSocialDto; import NextLevel.demo.social.entity.SocialEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -11,7 +12,20 @@ @Repository public interface SocialRepository extends JpaRepository { - @Query("select s from SocialEntity s left join fetch s.imgs where s.user.id = :userId order by s.createdAt desc") - List findAllByUserId(@Param("userId") Long userId); + @Query("select s " + + "from SocialEntity s " + + "left join fetch s.imgs " + + "where s.user.id = :targetUserId " + + "order by s.createdAt desc " + ) + List findAllByUserId(@Param("targetUserId") Long targetUserId); + + @Query("select sl.id as socialId, count(distinct total_like) as totalLikeCount, count(distinct my_like) as myLikeCount " + + "from SocialEntity sl " + + "left join SocialLikeEntity total_like on total_like.social.id = sl.id " + + "left join SocialLikeEntity my_like on my_like.social.id = sl.id and my_like.user.id = :userId " + + "where sl.id in :socialIds " + + "group by sl ") + List isSelectSocialLikeList(@Param("socialIds") List socialIds, @Param("userId") Long userId); } diff --git a/src/main/java/NextLevel/demo/social/service/SocialService.java b/src/main/java/NextLevel/demo/social/service/SocialService.java index e5d0561..b0b9326 100644 --- a/src/main/java/NextLevel/demo/social/service/SocialService.java +++ b/src/main/java/NextLevel/demo/social/service/SocialService.java @@ -2,15 +2,22 @@ import NextLevel.demo.exception.CustomException; import NextLevel.demo.exception.ErrorCode; +import NextLevel.demo.follow.SelectSocialProfileService; import NextLevel.demo.img.entity.ImgEntity; import NextLevel.demo.img.service.ImgService; import NextLevel.demo.img.service.ImgTransaction; import NextLevel.demo.social.dto.RequestSocialCreateDto; import NextLevel.demo.social.dto.ResponseSocialDto; +import NextLevel.demo.social.dto.SocialLikeDto; +import NextLevel.demo.social.dto.SocialListDto; import NextLevel.demo.social.entity.SocialEntity; import NextLevel.demo.social.entity.SocialImgEntity; +import NextLevel.demo.social.entity.SocialLikeEntity; import NextLevel.demo.social.repository.SocialImgRepository; +import NextLevel.demo.social.repository.SocialLikeRepository; import NextLevel.demo.social.repository.SocialRepository; +import NextLevel.demo.social.repository.IsSelectSocialLikedListInterface; +import NextLevel.demo.user.dto.user.response.UserSocialProfileDto; import NextLevel.demo.user.entity.UserEntity; import NextLevel.demo.user.service.UserValidateService; import jakarta.persistence.EntityManager; @@ -20,8 +27,7 @@ import org.springframework.web.multipart.MultipartFile; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; +import java.util.*; @Service @RequiredArgsConstructor @@ -35,6 +41,8 @@ public class SocialService { private final SocialImgRepository socialImgRepository; private final EntityManager entityManager; + private final SocialLikeRepository socialLikeRepository; + private final SelectSocialProfileService selectSocialProfileService; @ImgTransaction @Transactional @@ -80,9 +88,48 @@ public void delete(Long socialId, Long userId) { socialRepository.deleteById(social.getId()); } - public List list(Long userId) { - List socials = socialRepository.findAllByUserId(userId); - return socials.stream().map(ResponseSocialDto::of).toList(); + public void socialLike( SocialLikeDto dto) { + SocialEntity social = socialRepository.findById(dto.getSocialId()).orElseThrow( + ()->{return new CustomException(ErrorCode.NOT_FOUND, "social");} + ); + Optional oldLikeOpt = socialLikeRepository.findByUserIdAndSocialId(dto.getUserId(), dto.getSocialId()); + if(dto.getLike() && oldLikeOpt.isEmpty()) { + // 좋야요를 누름 + socialLikeRepository.save(SocialLikeEntity + .builder() + .user(entityManager.getReference(UserEntity.class, dto.getUserId())) + .social(social) + .build()); + } + if(!dto.getLike() && oldLikeOpt.isPresent()) { + // 좋아요 취소 + socialLikeRepository.delete(oldLikeOpt.get()); + } + } + + public SocialListDto list(Long targetUserId, Long userId) { + UserEntity targetUser = userValidateService.findUserWithUserId(targetUserId); + + List socialEntityList = socialRepository.findAllByUserId(targetUserId); + List isSocialLikedList = socialRepository.isSelectSocialLikeList(socialEntityList.stream().map(SocialEntity::getId).toList(), userId); + + Map isSocialLikedMap = new HashMap<>(); + isSocialLikedList.forEach(isLikedInterface-> + isSocialLikedMap.put( + isLikedInterface.getSocialId(), + isLikedInterface + ) + ); + + List socials = socialEntityList.stream().map(entity-> + new ResponseSocialDto( + entity, + isSocialLikedMap.get(entity.getId()).getTotalLikeCount(), + isSocialLikedMap.get(entity.getId()).getMyLikeCount() + )).toList(); + + UserSocialProfileDto user = selectSocialProfileService.selectUserSocialProfile(targetUser); + return SocialListDto.of(user, socials); } private void saveImgs(List imgFiles, SocialEntity social, ArrayList imgPaths) { diff --git a/src/main/java/NextLevel/demo/user/dto/user/response/UserSocialProfileDto.java b/src/main/java/NextLevel/demo/user/dto/user/response/UserSocialProfileDto.java new file mode 100644 index 0000000..335fd87 --- /dev/null +++ b/src/main/java/NextLevel/demo/user/dto/user/response/UserSocialProfileDto.java @@ -0,0 +1,29 @@ +package NextLevel.demo.user.dto.user.response; + +import NextLevel.demo.img.ImgDto; +import NextLevel.demo.user.entity.UserEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@NoArgsConstructor +@Getter +@Setter +public class UserSocialProfileDto { + + private String name; + private String nickName; + private Long followCount; + private ImgDto img; + + public static UserSocialProfileDto of(UserEntity user, Long followCount) { + UserSocialProfileDto dto = new UserSocialProfileDto(); + dto.followCount = followCount; + dto.name = user.getName(); + dto.nickName = user.getNickName(); + dto.img = new ImgDto(user.getImg()); + return dto; + } + +} diff --git a/src/main/java/NextLevel/demo/user/repository/UserRepository.java b/src/main/java/NextLevel/demo/user/repository/UserRepository.java index 22e564a..93e1843 100644 --- a/src/main/java/NextLevel/demo/user/repository/UserRepository.java +++ b/src/main/java/NextLevel/demo/user/repository/UserRepository.java @@ -10,7 +10,7 @@ public interface UserRepository extends JpaRepository { - @Query("select u from UserEntity u left join fetch u.userDetail where u.id = :userId") + @Query("select u from UserEntity u left join fetch u.userDetail left join fetch u.img where u.id = :userId") Optional findUserFullInfoByUserId(@Param("userId")Long userId); @Query("select u from UserEntity u where u.nickName = :nickName")